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:
| Value | What it is |
|---|---|
| Client ID | Identifies your key. Not secret. |
| Client Secret | The password for your key. Keep it secret — anyone with it can access your data. |
| Scope | Tells 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:
- Exchange your three values for an access token (valid ~60 minutes).
- Send that token as a Bearer token in the
Authorizationheader 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_tokenimport 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
| Message | Cause | Fix |
|---|---|---|
AADSTS7000215: Invalid client secret provided | Wrong or expired secret | Re-copy the Client Secret; if it's old, generate a new one. |
AADSTS700016: Application … was not found | Wrong Client ID | Re-copy the Client ID exactly (watch a letter O vs a zero 0). |
AADSTS1002012: … must have a scope value with /.default suffixed | Scope is missing the /.default suffix | Append /.default to your Scope value (see Step 1). |
AADSTS70011 / 90014: scope … is not valid / is missing | Wrong or empty Scope | Use 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
POSTto an entity endpoint (e.g./building) takes a JSON array of records, validates them, and queues them. A200means your records were accepted and validated — not that they are live in the platform yet. A400means one or more records failed validation. - Track progress with the batch. Every submission returns a
batchId. PollGET /batch/{batchId}/inboundto see each record's status; a record is fully onboarded when it reachesCompleted(orPublishedfor 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
400response lists the failed records infailedMessages, 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 Code | Description |
|---|---|
| 200 | Success — records accepted and validated |
| 400 | Validation error — one or more records failed; see failedMessages |
| 401 | Authentication error — credentials missing, incorrect or expired |
| 403 | The authenticated user has no access to this resource |
| 404 | Invalid endpoint, or the requested resource does not exist |
| 405 | The HTTP method used for this endpoint is invalid |
| 429 | Too many requests — the rate limit was exceeded |
| 5xx | A server error occurred and the request couldn't be completed |
| 503 | Temporary server issue — please try again |
Rate limit
The API allows 100 requests per minute per credential. If you exceed it you'll receive a 429 — wait 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
| Symptom | Likely cause | Fix |
|---|---|---|
Step 1 returns an AADSTS… error | Wrong/expired Client Secret, Client ID, or Scope | See "If Step 1 fails" above. |
API call returns 401 | Token expired (>~60 min) | Request a new token (Step 1). |
API call returns 403 | Your key isn't permitted for that operation | Contact your BuildingMinds representative. |
A 200 submission but data isn't visible | Ingestion is asynchronous | Poll GET /batch/{batchId}/inbound until records reach Completed / Published. |
