16  AutoDesk Tandem Overview

Published

January 1, 2026

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:

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

Tip

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_token

16.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 B

16.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:  2

facility$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 Unit

The Linking Sensor Streams chapter shows how to pull time-series readings for elements like this one.

16.6 Joining BIM Properties with Twin Elements

The objectId is the bridge between the two worlds. Here’s how to build a combined data frame that pairs every BIM property (from getData()) with the Tandem classification and display name for the same element:

library(dplyr)

# Pull BIM properties for all objects
bim_resp <- getData(guid = myGuid, urn = myEncodedUrn, token = myToken)
bim_df   <- lapply(bim_resp$content$data$collection, function(obj) {
  data.frame(
    objectid   = obj$objectid,
    layer      = obj$properties[["Layer and Material"]][["Layer"]],
    stringsAsFactors = FALSE
  )
}) |> do.call(what = rbind)

# Pull Tandem classifications for the same IDs
twin_df <- lapply(bim_df$objectid, function(id) {
  resp <- request(
    paste0("https://tandem.autodesk.com/api/v1/twins/", facilityId, "/elements/", id)
  ) |>
    req_auth_bearer_token(myToken) |>
    req_perform()
  el <- resp_body_json(resp)
  data.frame(
    objectid       = id,
    display_name   = el$displayName,
    classification = el$classification,
    stringsAsFactors = FALSE
  )
}) |> do.call(what = rbind)

# Join on objectId
combined <- left_join(bim_df, twin_df, by = "objectid")
combined
#>   objectid    layer          display_name              classification
#> 1        2   A-SITE         Site Boundary            Architecture:Site
#> 2        3   A-BLDG  Main Building Outline        Architecture:Building
#> 3       42  M-HVAC    Level 2 HVAC Unit    MEP:HVAC:Air Handling Unit
Tip

For facilities with hundreds of elements, batch the Tandem lookups using lapply() with a small Sys.sleep(0.2) between calls to stay within the rate limit.