graph LR A[BIM Model\nRVT / DWG file] --> B[Model Derivative API\ngetObjectTree / getData] A --> C[Tandem\nDigital Twin] C --> D[Sensor Streams\ntemperature / CO₂ / energy] B -- objectId --> C D -- time-series data --> E[R Analysis\nhttr2 + dplyr] B -- metadata --> E
16 AutoDesk Tandem Overview
A BIM model is a snapshot of a building at design time. A digital twin takes that same model and connects it to the building’s live operational data — temperature sensors, occupancy counters, energy meters — so you can see what’s actually happening inside, not just what was drawn. AutoDesk Tandem is APS’s platform for building and querying digital twins.
This chapter introduces Tandem concepts and shows how to talk to the Tandem REST API directly from R using httr2 (Wickham 2024) and a standard APS token from getToken(). See (Autodesk, Inc. 2024) for the full API reference.
The relationship between a BIM model and a Tandem twin looks like this:
Tandem requires an active AutoDesk Tandem subscription in addition to an APS app registration. Authentication uses the same OAuth Bearer token as the rest of this book — no extra credential setup needed.
16.1 Twin vs. BIM Model
| BIM Model | Digital Twin | |
|---|---|---|
| Primary data | Geometry + properties | Geometry + live sensor streams |
| Update frequency | Per design revision | Continuous / real-time |
| Primary use | Design & construction | Operations & maintenance |
| APS API | Model Derivative | Tandem REST |
| R entry point | getObjectTree(), getData() |
httr2::request() |
16.2 Authentication
The Tandem API accepts the same Bearer token as all other APS services:
library(AutoDeskR)
library(httr2)
resp <- getToken(id = Sys.getenv("client_id"),
secret = Sys.getenv("client_secret"),
scope = "data:read")
myToken <- resp$content$access_token16.3 Listing Facilities
A facility in Tandem is the digital twin of a building or site — the top-level container for model data and sensor streams. List all facilities your token can access:
facilities_resp <- request("https://tandem.autodesk.com/api/v1/groups") |>
req_auth_bearer_token(myToken) |>
req_perform()
facilities <- resp_body_json(facilities_resp)
facilities_df <- lapply(facilities, function(f) {
data.frame(id = f$id, name = f$name, stringsAsFactors = FALSE)
}) |> do.call(what = rbind)
facilities_df
#> id name
#> 1 urn:adsk.dtdm:facility.abc123def456 Office Block A
#> 2 urn:adsk.dtdm:facility.xyz789uvw012 Warehouse Site B16.4 Facility Details
facilityId <- "urn:adsk.dtdm:facility.abc123def456"
facility_resp <- request(
paste0("https://tandem.autodesk.com/api/v1/twins/", facilityId)
) |>
req_auth_bearer_token(myToken) |>
req_perform()
facility <- resp_body_json(facility_resp)
cat("Name: ", facility$displayName, "\n")
#> Name: Office Block A
cat("Created: ", facility$createTime, "\n")
#> Created: 2025-11-04T09:22:14Z
cat("Linked URNs:", length(facility$links), "\n")
#> Linked URNs: 2facility$links contains the APS Model Derivative URNs of the BIM models embedded in this twin — the same URNs you’d pass to getObjectTree() or getData(), which is what makes the objectId the key that links the two worlds together.
16.5 Looking Up an Element
Any objectId from getData() can be looked up in Tandem to find the corresponding physical element and its classification:
element_resp <- request(
paste0("https://tandem.autodesk.com/api/v1/twins/",
facilityId, "/elements/", 42L) # objectId 42
) |>
req_auth_bearer_token(myToken) |>
req_perform()
element <- resp_body_json(element_resp)
cat("Name: ", element$displayName, "\n")
#> Name: Level 2 HVAC Unit
cat("Classification:", element$classification, "\n")
#> Classification: MEP:HVAC:Air Handling UnitThe Linking Sensor Streams chapter shows how to pull time-series readings for elements like this one.