BuildingMinds API

Looking for the full API reference? Switch to the API Endpoints section instead.

The BuildingMinds API lets you onboard your portfolio's Digital Twin — buildings, spaces, meters and more — into the BuildingMinds platform from your own systems, over a secure REST interface. Data is exchanged as JSON.

This guide takes you from your credentials to a confirmed connection, then explains how ingestion works. Every request is made over SSL. This service is enabled by your BuildingMinds Key Account representative.

Base URL

https://api.onbuildingminds.com/api/digitaltwinintegration

Before you start

Your BuildingMinds Key Account representative will have given you three values:

ValueWhat it is
Client IDIdentifies your key. Not secret.
Client SecretThe password for your key. Keep it secret — anyone with it can access your data.
ScopeTells the login server which BuildingMinds environment your key is for. When you request a token, append /.default to this value (see Step 1).

How authentication works

Authentication uses OAuth 2.0 client-credentials. In plain terms: you exchange your three values for a short-lived access token (a string that proves who you are), then send that token with every request. The model is always two steps:

  1. Exchange your three values for an access token (valid ~60 minutes).
  2. Send that token as a Bearer token in the Authorization header of every request.

There is no separate refresh token — when a call returns 401, simply request a new token.

Step 1 — Get an access token

Choose your environment. Each example requests a token and stores it in a variable for the next step. Note: the scope must be your Scope value with /.default appended — the client-credentials flow requires it.

TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/buildingminds.onmicrosoft.com/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=<YOUR_CLIENT_ID>" \
  --data-urlencode "client_secret=<YOUR_CLIENT_SECRET>" \
  --data-urlencode "scope=<YOUR_SCOPE>/.default" \
  --data-urlencode "grant_type=client_credentials" \
  | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
$body = @{
  client_id     = '<YOUR_CLIENT_ID>'
  client_secret = '<YOUR_CLIENT_SECRET>'
  scope         = '<YOUR_SCOPE>/.default'
  grant_type    = 'client_credentials'
}
$response = Invoke-RestMethod -Method Post `
  -Uri 'https://login.microsoftonline.com/buildingminds.onmicrosoft.com/oauth2/v2.0/token' `
  -ContentType 'application/x-www-form-urlencoded' -Body $body
$token = $response.access_token
import requests

resp = requests.post(
    "https://login.microsoftonline.com/buildingminds.onmicrosoft.com/oauth2/v2.0/token",
    data={
        "client_id": "<YOUR_CLIENT_ID>",
        "client_secret": "<YOUR_CLIENT_SECRET>",
        "scope": "<YOUR_SCOPE>/.default",
        "grant_type": "client_credentials",
    },
)
resp.raise_for_status()
token = resp.json()["access_token"]

What comes back:

{
  "token_type": "Bearer",
  "expires_in": 3599,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs..."
}
📘

Prefer a GUI? In Postman, open a request → Authorization tab → Type OAuth 2.0 → Grant Type Client Credentials; set Access Token URL to the token endpoint above, Client ID / Client Secret to your values, and Scope to <YOUR_SCOPE>/.default. Click Get New Access Token.

If Step 1 fails

MessageCauseFix
AADSTS7000215: Invalid client secret providedWrong or expired secretRe-copy the Client Secret; if it's old, generate a new one.
AADSTS700016: Application … was not foundWrong Client IDRe-copy the Client ID exactly (watch a letter O vs a zero 0).
AADSTS1002012: … must have a scope value with /.default suffixedScope is missing the /.default suffixAppend /.default to your Scope value (see Step 1).
AADSTS70011 / 90014: scope … is not valid / is missingWrong or empty ScopeUse the Scope value as provided, with /.default appended.

Step 2 — Validate your connection

Confirm your token works with a read-only call. GET /batch lists your recent ingestion batches and changes nothing — the safest way to verify your setup before you send any data.

curl "https://api.onbuildingminds.com/api/digitaltwinintegration/batch" \
  -H "Authorization: Bearer $TOKEN"
Invoke-RestMethod -Method Get `
  -Uri 'https://api.onbuildingminds.com/api/digitaltwinintegration/batch' `
  -Headers @{ Authorization = "Bearer $token" }
import requests

r = requests.get(
    "https://api.onbuildingminds.com/api/digitaltwinintegration/batch",
    headers={"Authorization": f"Bearer {token}"},
)
print(r.status_code, r.json())

A 200 response means your credentials work. If you haven't onboarded anything yet, the list may be empty — that is still a success.

How ingestion works

Onboarding data is asynchronous, and it's worth understanding before you build:

  • Submitting is validate-then-queue. A POST to an entity endpoint (e.g. /building) takes a JSON array of records, validates them, and queues them. A 200 means your records were accepted and validated — not that they are live in the platform yet. A 400 means one or more records failed validation.
  • Track progress with the batch. Every submission returns a batchId. Poll GET /batch/{batchId}/inbound to see each record's status; a record is fully onboarded when it reaches Completed (or Published for some entity types).
  • Order doesn't matter. You do not need to send parent entities before their children. If a record references a parent that hasn't arrived yet, it is held and resolved automatically once the parent is onboarded.
  • Errors are per record. A 400 response lists the failed records in failedMessages, each with the original record and a validation message, so you can correct and re-send just those.
  • Re-sending is safe by external ID. There is no idempotency key; records are matched on the IDs you provide, so re-sending the same entity (same ID) updates it rather than creating a duplicate. Keep your IDs stable, and let a batch finish before re-sending it.

Error handling

The API returns an HTTP status code for every request. 2xx indicates success; 4xx/5xx indicate an error, accompanied by an explanatory message.

Status CodeDescription
200Success — records accepted and validated
400Validation error — one or more records failed; see failedMessages
401Authentication error — credentials missing, incorrect or expired
403The authenticated user has no access to this resource
404Invalid endpoint, or the requested resource does not exist
405The HTTP method used for this endpoint is invalid
429Too many requests — the rate limit was exceeded
5xxA server error occurred and the request couldn't be completed
503Temporary server issue — please try again

Rate limit

The API allows 100 requests per minute per credential. If you exceed it you'll receive a 429wait about a minute and retry. You can include many records in a single array per request, so prefer fewer, larger submissions over many small ones.

Token lifetime & security

  • Lifetime: ~60 minutes. When a call returns 401, request a fresh token (Step 1). There is no refresh token.
  • Rotate the Client Secret periodically, and immediately if it may have been exposed.
  • The Client ID and Scope are not sensitive; the Client Secret is — treat it like a password.
  • All requests are made over SSL.
  • For BuildingMinds' security, certifications, and compliance information, see the BuildingMinds Trust Center.

Troubleshooting

SymptomLikely causeFix
Step 1 returns an AADSTS… errorWrong/expired Client Secret, Client ID, or ScopeSee "If Step 1 fails" above.
API call returns 401Token expired (>~60 min)Request a new token (Step 1).
API call returns 403Your key isn't permitted for that operationContact your BuildingMinds representative.
A 200 submission but data isn't visibleIngestion is asynchronousPoll GET /batch/{batchId}/inbound until records reach Completed / Published.