On this page
Developer documentation
BIMRelay REST API
Read projects and current COBie 2.4 workbook data, retrieve Issues and exports, edit cells, and manage manual rows and individual exclusions from your scripts and applications.
Use the API to feed COBie data into facility management and asset systems, build project dashboards, check workbook completeness in your own tooling, or retrieve existing Excel exports. Every request is checked against the projects you approved and your current BIMRelay membership.
- Base URL
https://api.bimrelay.com/api/v1- Format
- JSON over HTTPS. GET reads, PATCH cell edits, POST creation and DELETE removal.
- Authentication
- Bearer token: a personal API token or an OAuth access token
- Contract
- OpenAPI 3.1 document
Editing is opt-in. Read access is the default. Separate permissions allow cell edits, manual row management and individual generated-row exclusions. Other settings, memberships, extractions, reviews and exports cannot be changed or started. To connect an AI assistant instead of a script, use the BIMRelay MCP server.
Quick start
Create a personal API token and make your first request in a few minutes.
- In BIMRelay, open your profile menu and choose Integrations, or go to app.bimrelay.com/integrations. Select Create API token.
- Name the token and set Use with to REST API. Under Permissions, select Workspace and project names and details and Workbook sheets and rows. Choose the projects to share and an expiry, then select Create token.
- Copy the token. It is shown only once. Store it in a password manager or secrets store, and load it into the
BIMRELAY_API_TOKENenvironment variable to run the examples in this guide.
List the projects the token can read:
curl --fail-with-body \
'https://api.bimrelay.com/api/v1/projects?limit=20' \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"A successful request returns 200 OK and a page of projects:
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000001",
"workspace_id": "00000000-0000-4000-8000-000000000002",
"name": "Example project",
"status": "active",
"description": "Facilities handover for the civic center.",
"classification_system": "omniclass"
}
],
"next_cursor": null
}To browse by workspace, use List workspaces, then pass its id as workspace_id to List projects. Workspaces expose only their IDs and names; their other projects stay private unless explicitly shared with this connection.
Next, use a project id to read its workbook. A next_cursor of null means there are no more pages. IDs and values in this guide are illustrative.
Authentication and scopes
Authenticate every request with a Bearer token in the Authorization header. The API accepts two kinds of token, and both use the same endpoints, scopes and project checks:
- Personal API tokens for your own scripts, data pipelines and internal tools. Create them in Integrations.
- OAuth access tokens for applications that other BIMRelay users connect to their own accounts. See OAuth for connected apps.
Personal API tokens
- A token works only with the interface chosen when it was created. REST API tokens do not work with the MCP server, and MCP tokens do not work with this API.
- Tokens expire after 7, 30 or 90 days and are shown only once. If you lose a token, revoke it and create another.
- If your last sign-in was more than ten minutes ago, BIMRelay asks you to sign in again before creating a token. You receive a security email whenever a token is created.
- Each token can include up to 100 projects, and an account can have up to 50 active connections.
- Revoked tokens stop working immediately. When you reset your password, you can also choose to revoke all API tokens and connected integrations.
Treat tokens like passwords. Send them only in the Authorization header, and keep them out of URLs, source control, logs and client-side browser code. Sessions from the BIMRelay web app cannot call this API.
Scopes
| Scope | Shown in BIMRelay as | Grants |
|---|---|---|
projects:read | Workspace and project names and details | Read workspace names and project details |
workbooks:read | Workbook sheets and rows | Current workbook status, sheet schemas and rows |
reviews:read | Issues and Asset review recommendations | Existing validation Issues and Asset review findings |
exports:read | Existing exports and download links | Existing Excel exports and temporary download links for completed files |
workbooks:write | Edit workbook cells | Edit existing cells; requires workbook read permission and your Editor or Manager role |
workbooks:rows:write | Create and delete manual rows | Manage manual rows; requires workbook reads and your Editor or Manager role |
workbooks:exclusions:write | Exclude and include generated rows | Manage individual exclusions; requires workbook reads and your Manager role |
Read scopes are independent, so projects:read does not include workbook access. Every write permission additionally requires workbooks:read to read rows before editing. Request only the scopes your integration needs. Get connection details works with any scope and reports the token’s scopes and expiry.
Project access
A connection reads only the projects selected for it, and only while you remain a member of each one. Every request is checked against your current membership, so removing you from a project stops access immediately. Workspace names are visible only while the connection can read at least one granted project in that workspace. Workspace membership alone does not grant integration access. Every project role, including Read-only, can read. Archived projects remain readable. Projects created later are not added to an existing token; create a new token to include them. Asset review findings are unavailable when the workspace has turned off Asset review.
Read a workbook
Use a project ID to discover sheets and read their rows. These requests need workbooks:read and always return the current workbook. Set BIMRELAY_PROJECT_ID to a project id from List projects.
1. Discover sheet and column keys
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"The response lists each sheet’s key, row_count and columns. Sheet keys are lowercase COBie worksheet names, such as component, type and space. Standard column keys use snake case, such as type_name and serial_number. Custom columns appear in the same list: use their returned keys rather than deriving keys from display labels. The COBie 2.4 worksheet reference explains what each sheet contains.
2. Read or search rows
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=25' \
--data-urlencode 'search=Pump'{
"data": [
{
"values": {
"name": "Pump 01",
"maintenance_priority": "High"
},
"key": "component:example",
"source": "generated"
}
],
"next_cursor": null
}Each row has a key and a values object that maps column keys to strings or null, including numeric and date columns. All standard and custom columns are returned by default. Blank cells are included as null; an explicitly empty string remains "".
searchmatches a case-insensitive substring in any cell value in the sheet, including columns you did not request.columns[]selects returned columns; it does not change which rows match. Repeat it for each column, up to 100 keys. For example, add--data-urlencode 'columns[]=name'. Request fewer rows or select columns when a wide sheet exceeds the response limits.filters[column_key]matches an exact, case-sensitive value. Multiple filters are combined with AND. For example, add--data-urlencode 'filters[name]=Pump 01'. An empty string matches an empty value, not null.
To fetch one row, pass its key to Get a row, percent-encoded as a single path segment, with the same project and sheet. Use the returned key rather than the displayed Name.
Get a workbook optionally tells you whether it is updating and includes latest_version_number for reference. The number is null before initial generation; you do not need it to read data.
Edit workbook cells
Editing is opt-in. Choose Edit workbook cells (workbooks:write) together with Workbook sheets and rows (workbooks:read) when creating a connection. You must be an Editor or Manager on an active project. Existing read-only connections keep their permissions.
- Use List sheets to discover column keys and their
editingrules. For choices and references, load one cell's options when needed. - Get the full row, without a
columns[]selection. Copy itsetagexactly, including the surrounding quotes. The same strong validator is returned in the HTTPETagheader. Weak tags beginning withW/cannot be used for editing. - Send a
PATCHto that row with only the columns to change, anIf-Matchheader containing the ETag, and a uniqueIdempotency-Key.
curl --fail-with-body --request PATCH \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows/$BIMRELAY_ENCODED_ROW_KEY" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H "If-Match: $BIMRELAY_ROW_ETAG" \
-H "Idempotency-Key: $BIMRELAY_EDIT_KEY" \
-H "Content-Type: application/json" \
--data-binary '{"values":{"description":"Pump serving level 2"}}'Set BIMRELAY_ENCODED_ROW_KEY to the percent-encoded row key, BIMRELAY_ROW_ETAG to the full row's ETag, and BIMRELAY_EDIT_KEY to a new unique identifier for this edit. Send cell values as strings, including numbers and dates. null clears a cell; omitted columns stay unchanged. Standard and custom columns use their returned keys.
Choices, references and validation
The API follows the grid's rules. Invalid reference selections and values outside a fixed choice list are rejected. Incomplete values, invalid numbers and unreadable dates may be saved and flagged in Issues. Required columns can be cleared while you work. A successful edit is not a claim that the workbook is ready for handover.
For an exact reference selection, use the reference returned by the options endpoint. This avoids ambiguity when names repeat or contain commas:
{
"values": {
"space": {
"references": [
{
"sheet_key": "space",
"row_key": "space:level-2-plant-room"
}
]
}
}
}A single-reference cell accepts zero or one row; a multiple-reference cell accepts up to 100. An empty references array clears the cell. Plain reference text uses the grid's case-insensitive resolver; when names repeat, the first row in sheet order is selected. Prefer exact row references. For dynamic references, the target follows the row's selected SheetName. Changing that selection can clear dependent cells. Renaming a referenced row updates its dependent labels.
Preview and batches
Add "preview": true to either editing request to evaluate it without saving. The result lists direct and dependent changes; requested: false marks changes made by the workbook rules. Preview still requires current ETags, but no idempotency key. Apply the request with preview omitted or false and a fresh idempotency key. Application checks permissions, ETags and editing locks again.
Edit a batch of cells accepts up to 100 directly edited cells across existing rows. Each row appears once, with its own if_match and values. The entire batch succeeds or none is saved. Rows are processed in the supplied order; select references by row key when a batch also renames their targets. Errors identify the affected coordinates in error.details.
Conflicts and safe retries
A changed row returns 412 precondition_failed; read it again and review the newer values before resubmitting. Missing ETags return 428 precondition_required. Busy workbooks and cells being edited in the app return 409. Edits record you and the integration name in the app's history, preserve overrides through regeneration, and update open workbooks. validation_pending means the values were saved and background checks of Issues are pending.
After a connection failure or uncertain response, retry with the same values, row and reference order, ETags and idempotency key. JSON object property order does not matter. Saved results are replayed for 24 hours without editing again. A different request with the same key returns 409 idempotency_conflict. A new edit needs a new key. This protection is per connection.
Saved retry results are limited to 10,000 requests or 32 MiB per user across all connections. At capacity, new edits return 429 idempotency_capacity_exceeded until older results expire. Previously saved requests can still be retried, and previews remain available.
Requests are limited to 64 KiB, 100 edited cells and 1,000 affected cells including dependencies. Each submitted value is limited to 10,000 characters; batch rows together may contain at most 10,000 stored cells. Editing responses are limited to 256 KiB. Large renames may need to be made in the app. Cell editing does not add or remove rows. See Manage worksheet rows for separately permitted row actions.
Manage worksheet rows
Rows have a source of manual or generated. Manual rows are user-created content. Generated rows come from workbook generation: removing one means remembering an exclusion so a model update does not bring it back.
Create and delete manual rows
Enable Create and delete manual rows (workbooks:rows:write) alongside workbook reads. Your current project role must be Editor or Manager. Create a manual row accepts a values object using the same column keys, strings, nulls and exact references as cell edits. An empty object creates a blank row with normal defaults. Omitted columns use those defaults; standard and custom columns are supported.
Creation returns HTTP 201, the row and its ETag. It may also create your author Contact, just as Add row does in the app. Use the returned row key for future reads, edits and deletion. Incomplete content is allowed and checked through Issues.
Before deleting a manual row, read its full ETag and send preview=true in the query to see the number of rows removed and cells updated. Contact deletion requires an explicit reference_action: clear leaves references blank, or reassign moves them to the Contact identified by replacement_row_key. Generated rows cannot be deleted through this endpoint.
Exclude and include generated rows
Enable Exclude and include generated rows (workbooks:exclusions:write) alongside workbook reads. These actions require your current Manager role. Exclude a generated row records an individual exclusion in Settings → Sheets. It does not create an asset rule or exclude every occurrence of a parameter.
Type and Space exclusions require component_action. For Type, choose exclude, clear_type or reassign. For Space, choose exclude, resolve_space or reassign. The resolve option uses another detected Room or Space where available, otherwise leaves the generated Space blank. Reassignment needs another generated row from the same sheet. Explicitly edited Component references retain their ordinary override behavior. Floor, Contact and Facility are outside this exclusion API.
The exclusion response includes an ID and the effect counts. List excluded rows finds exclusions made through either the API or the app. To include one again, send its exclusion ID and current exclusion ETag. HTTP 202 confirms that the exclusion is removed and a workbook update is pending. Poll Get workbook until updating is false, then read the rows again. Other selection settings and available model sources still determine what returns.
Preview, conflicts and retries
All four actions support previews; previews save no rows, exclusions, audit events or jobs. For DELETE requests, send preview=true and any Contact reference choices as query parameters, with no request body. A plain DELETE needs no Content-Type header. POST requests use preview: true in their JSON body. A preview of creation has a temporary row key: use the actual creation response for subsequent calls. Destructive previews require the same current ETag as application. Apply with a new Idempotency-Key; retry an uncertain result with the identical request and key for up to 24 hours.
curl --fail-with-body --get --request DELETE \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows/manual%3A00000000-0000-4000-8000-000000000006" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'If-Match: "br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"' \
--data-urlencode 'preview=true'Actions are atomic and blocked while models or workbook settings are updating. Browser editing locks on affected cells are respected. Synchronous actions are limited to 200 affected rows, 1,000 updated cells in surviving rows and 10,000 reference memberships; large actions must be made in the app. The row limit includes generated dependent rows removed by an exclusion. Cells in removed rows have a separate 10,000-cell limit; loaded values and rebuilt reference labels are capped at 256 KiB. Background validation refreshes Issues after saving.
Pagination and limits
List endpoints return a page of results in data and a next_cursor. While next_cursor is not null, pass it to the same endpoint to fetch the next page:
curl --fail-with-body --get \
'https://api.bimrelay.com/api/v1/projects' \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=20' \
--data-urlencode "cursor=$BIMRELAY_CURSOR"Set BIMRELAY_CURSOR to the previous response’s next_cursor, and keep every other parameter, including search, filters and columns, unchanged. Use next_cursor, not the number of records returned, to detect the last page. Cursors expire after 15 minutes and work only with the connection that received them. Responses do not include totals; List sheets reports each sheet’s full row_count.
Ordering and consistency
Rows and reference options are returned in sheet order; suggested choice lists use their configured order. Other lists use ascending ID order. There is no sort parameter. The API reads the current workbook only. Values can change between pages as collaborators edit them. If the workbook is rebuilt while you are reading it, the API returns 409 workbook_changed. Restart the request without a cursor to read the current workbook.
Request limits
Every authorized operation counts toward a free workspace’s monthly allowance, including each page, preview, failed operation and retry. Authentication, request-schema, scope and project/workspace-access failures do not count. Requests for a project or workspace count only against that workspace; requests without either target (such as /me or unfiltered project lists) count once in every accessible workspace granted to the connection. OAuth, MCP initialization and tool discovery do not use the monthly allowance.
Check usage in Integrations. The allowance resets at 00:00 UTC on the first of each month. At the limit, requests return 429 monthly_quota_exceeded; upgrade the workspace or wait until the reset. Creating another token does not reset usage.
| Limit | Value |
|---|---|
| Page size | 100 by default; 1–200 allowed. Cell options use fixed 100-item pages |
| Returned cells | Up to 20,000 per row read (rows × columns). Request fewer rows or select columns for wide sheets |
| Column selection | All columns by default, including custom columns. An explicit selection allows up to 100 column keys |
| Sheet schema | Up to 200 sheets and 5,000 columns |
| Response size | 1 MiB for reads; 256 KiB for edits |
| Request rate | 120 requests per user per minute, shared by all of the user’s REST and MCP connections |
| Free workspace allowance | 20 API requests and MCP tool calls combined per calendar month (UTC), shared by all members and connections. Paid workspaces have no monthly request cap |
| Concurrent operations | One per user. Send requests sequentially |
| Processing time | Five seconds per request. Narrow expensive searches |
| Cursor lifetime | 15 minutes |
Errors
Errors return an HTTP status code and a JSON body with a machine-readable code, a human-readable message and a request_id:
{
"error": {
"code": "insufficient_scope",
"message": "This connection needs workbooks:read",
"request_id": "example-request-id"
}
}| Status | Code | What to do |
|---|---|---|
| 400 | idempotency_key_required | Send an Idempotency-Key when saving edits. |
| 403 | permission_denied | Manual rows and cell editing require Editor or Manager; exclusions require Manager. |
| 409 | workbook_busy, project_read_only, cell_locked, edit_conflict | Wait for updates or active editing to finish. Archived projects cannot be edited. Read the row again before resubmitting. |
| 409 | idempotency_conflict | Use a new key for a different edit; identical retries must retain their body and ETags. |
| 412 | precondition_failed | Read the full row or exclusion again and review its newer state. |
| 422 | invalid_value, invalid_column, invalid_reference, invalid_exclusion, generated_row, contact_in_use | Correct the value, column, reference or exclusion choice. Exclude generated rows instead of deleting them; change Contact replacement mappings in Settings. No changes were saved. |
| 428 | precondition_required | Send the full row’s ETag, or the exclusion’s ETag when including again, in If-Match. |
| 413 | request_too_large, edit_too_large | Use a smaller cell batch. Make large row cascades and dependent renames in the app. |
| 400 | invalid_request, invalid_column, invalid_cursor | Correct the parameters or column keys. For an expired or mismatched cursor, restart paging. |
| 401 | invalid_token | Check the token, its expiry and that it was created for the REST API. Replace an expired personal token; refresh or reconnect an OAuth application. |
| 403 | insufficient_scope | Create or approve a connection with the required scope. |
| 404 | not_found | Check IDs, the connection’s projects and your current membership. Unavailable and unauthorized resources both return 404. |
| 409 | workbook_changed | Restart the request without a cursor to read the current workbook. |
| 409 | export_not_ready | Check the export’s status. Request a download link only after the export has completed. |
| 413 | response_too_large | Request fewer rows or columns for reads, or edit fewer cells for writes. |
| 413 | schema_too_large | The workbook exceeds the supported schema size, so smaller pages will not help. Contact support. |
| 429 | rate_limited, read_busy | Send one request at a time and wait for the number of seconds in Retry-After. |
| 429 | monthly_quota_exceeded | The free workspace has used its 20 monthly API/MCP requests. Upgrade or wait until error.details.resets_at. Details also identify workspace_id, limit and used. Retry-After gives seconds until the reset. |
| 429 | idempotency_capacity_exceeded | Wait for older retry results to expire. Previously saved retries and previews remain available. |
| 503 | query_timeout | Narrow the search or filters before retrying. |
| 503 | temporarily_unavailable | Retry with backoff, respecting Retry-After. |
| 500 | None | An unexpected server error. Retry with backoff and contact support if it persists. A structured error body is not guaranteed. |
All 429 and 503 responses include a Retry-After header in seconds. Retry those with a bounded number of attempts, not an indefinite loop, and change the request before retrying other 4xx errors. When you contact support, share the request_id, never a token or private workbook content.
Endpoint reference
Methods are shown below; paths are relative to the base URL. Example requests use BIMRELAY_API_TOKEN and ID variables set from earlier responses. The OpenAPI 3.1 document contains the complete request and response contract, which you can use to generate a typed client.
GET/me
Check the token's user, permissions and expiry.
No parameters.
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/me" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"user": {
"id": "00000000-0000-4000-8000-000000000008",
"name": "Alex Example"
},
"scopes": [
"projects:read",
"workbooks:read"
],
"expires_at": "2026-11-06T12:00:00Z"
}Response schema
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
},
"scopes": {
"type": "array",
"items": {
"type": "string"
}
},
"expires_at": {
"type": "string",
"format": "date-time"
}
},
"required": [
"user",
"scopes",
"expires_at"
],
"additionalProperties": false
}GET/workspaces
List workspaces containing projects this connection can currently read.
Only workspace IDs and names are returned. Seeing a workspace does not grant access to its other projects.
| Name | Type | Description |
|---|---|---|
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/workspaces" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=20'Example response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000002",
"name": "Example workspace"
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}GET/workspaces/{workspace_id}
Read the name of a workspace containing an accessible project.
| Name | Type | Description |
|---|---|---|
workspace_idRequired | pathUUID | Workspace ID returned by List workspaces. On List projects, restricts results to approved projects in that workspace. |
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/workspaces/$BIMRELAY_WORKSPACE_ID" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"id": "00000000-0000-4000-8000-000000000002",
"name": "Example workspace"
}Response schema
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}GET/projects
List the approved projects you can currently access.
Optionally pass workspace_id to list only approved projects in that workspace. An unavailable workspace returns 404; the filter never broadens project access.
| Name | Type | Description |
|---|---|---|
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
workspace_id | queryUUID | Workspace ID returned by List workspaces. On List projects, restricts results to approved projects in that workspace. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=20'Example response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000001",
"workspace_id": "00000000-0000-4000-8000-000000000002",
"name": "Example project",
"status": "active",
"description": "Facilities handover for the civic center.",
"classification_system": "omniclass"
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"workspace_id": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"classification_system": {
"type": "string",
"enum": [
"omniclass",
"uniclass"
]
},
"status": {
"type": "string"
}
},
"required": [
"id",
"workspace_id",
"name",
"description",
"classification_system",
"status"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}GET/projects/{project_id}
Read the details of one approved project.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"id": "00000000-0000-4000-8000-000000000001",
"workspace_id": "00000000-0000-4000-8000-000000000002",
"name": "Example project",
"status": "active",
"description": "Facilities handover for the civic center.",
"classification_system": "omniclass"
}Response schema
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"workspace_id": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"classification_system": {
"type": "string",
"enum": [
"omniclass",
"uniclass"
]
},
"status": {
"type": "string"
}
},
"required": [
"id",
"workspace_id",
"name",
"description",
"classification_system",
"status"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook
Read the status of the current workbook for a project.
Use the same project_id to list worksheets, read rows, list Issues and recommendations, or download existing exports.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"project_id": "00000000-0000-4000-8000-000000000001",
"updating": false,
"latest_version_number": 2
}Response schema
{
"type": "object",
"properties": {
"project_id": {
"type": "string"
},
"updating": {
"type": "boolean"
},
"latest_version_number": {
"type": [
"integer",
"null"
],
"minimum": 1
}
},
"required": [
"project_id",
"updating",
"latest_version_number"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/sheets
Discover current worksheet keys, column keys and row counts, including custom columns.
row_count is the full sheet count, not the count after a search. Use the returned keys in subsequent requests.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"sheets": [
{
"key": "component",
"name": "Component",
"row_count": 25,
"columns": [
{
"key": "name",
"name": "Name",
"data_type": "string",
"required": true,
"editing": {
"kind": "value",
"allow_custom_value": true
}
},
{
"key": "maintenance_priority",
"name": "Maintenance Priority",
"data_type": "string",
"required": false,
"editing": {
"kind": "value",
"allow_custom_value": true
}
}
]
}
]
}Response schema
{
"type": "object",
"properties": {
"sheets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"name": {
"type": "string"
},
"row_count": {
"type": "integer",
"minimum": 0
},
"columns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"name": {
"type": "string"
},
"data_type": {
"type": "string"
},
"required": {
"type": "boolean"
},
"editing": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"value",
"choice",
"reference"
]
},
"allow_custom_value": {
"type": "boolean"
},
"multiple": {
"type": "boolean"
},
"target_sheet": {
"type": [
"string",
"null"
]
},
"target_column": {
"type": [
"string",
"null"
]
},
"depends_on": {
"type": "string"
}
},
"required": [
"kind",
"allow_custom_value"
],
"additionalProperties": false
}
},
"required": [
"key",
"name",
"data_type",
"required",
"editing"
],
"additionalProperties": false
}
}
},
"required": [
"key",
"name",
"row_count",
"columns"
],
"additionalProperties": false
}
}
},
"required": [
"sheets"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/sheets/{sheet_key}/rows
Read current worksheet rows, with optional search, filters and selected columns.
Search matches cell values across the sheet, including columns omitted from the response. Multiple exact filters are combined with AND.
| Name | Type | Description |
|---|---|---|
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
search | querystring | Case-insensitive substring search across cell values. Up to 200 characters. |
columns[] | queryarray of strings | Repeat for each column key to return, up to 100 keys. Omit to return all columns, including custom columns. Row reads are limited to 20,000 cells. An empty selection is not allowed. |
filters[column_key] | queryobject | Exact, case-sensitive column values; up to 20 filters combined with AND. Example: filters[name]=Pump 01. An empty string matches an empty value, not null. |
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=25' \
--data-urlencode 'search=Pump'Example response
{
"data": [
{
"values": {
"name": "Pump 01",
"maintenance_priority": "High"
},
"key": "component:example",
"source": "generated"
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"source": {
"type": "string",
"enum": [
"manual",
"generated"
]
},
"values": {
"type": "object",
"additionalProperties": {
"type": [
"string",
"null"
]
}
}
},
"required": [
"key",
"source",
"values"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}POST/projects/{project_id}/workbook/sheets/{sheet_key}/rows
Add a user-created row with standard or custom column values.
Returns HTTP 201 on creation, or 200 for preview. Requires workbooks:rows:write and workbooks:read plus Editor or Manager access. Use Idempotency-Key to avoid duplicate rows after uncertain responses. The response includes all sheet columns; this illustrative example shows two. An empty values object creates a blank row with normal defaults. A new author Contact may also be created. Preview keys are illustrative and do not identify saved rows.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
Idempotency-Key | headerstring | Unique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours. |
valuesRequired | bodyobject | Initial column values as strings, null or exact references. An empty object creates a blank row; omitted columns use the normal defaults. |
preview | bodyboolean | Set true to inspect changes without saving. Defaults to false. Applying rechecks all guards. |
curl --fail-with-body --request POST \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'Idempotency-Key: example-create-001' \
-H "Content-Type: application/json" \
--data-binary '{"values":{"name":"Additional pump","maintenance_priority":"High"}}'Example response
{
"applied": true,
"row": {
"key": "manual:00000000-0000-4000-8000-000000000006",
"source": "manual",
"values": {
"name": "Additional pump",
"maintenance_priority": "High"
}
},
"etag": "\"br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"",
"affected": {
"rows_created": 1,
"rows_removed": 0,
"cells_updated": 2
},
"validation_pending": true
}Response schema
{
"type": "object",
"properties": {
"applied": {
"type": "boolean"
},
"row": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"source": {
"type": "string",
"enum": [
"manual",
"generated"
]
},
"values": {
"type": "object",
"additionalProperties": {
"type": [
"string",
"null"
]
}
}
},
"required": [
"key",
"source",
"values"
],
"additionalProperties": false
},
"etag": {
"type": "string"
},
"affected": {
"type": "object",
"properties": {
"rows_created": {
"type": "integer",
"minimum": 0
},
"rows_removed": {
"type": "integer",
"minimum": 0
},
"cells_updated": {
"type": "integer",
"minimum": 0
}
},
"required": [
"rows_created",
"rows_removed",
"cells_updated"
],
"additionalProperties": false
},
"validation_pending": {
"type": "boolean"
}
},
"required": [
"applied",
"row",
"etag",
"affected",
"validation_pending"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/sheets/{sheet_key}/rows/{row_key}
Read one current row using its key, not its displayed Name.
The ETag is also returned in the HTTP ETag header. Read without columns before editing, then copy it exactly into If-Match. It is opaque and bound to this connection.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
row_keyRequired | pathstring | The row’s key, percent-encoded as a path segment. |
columns[] | queryarray of strings | Repeat for each column key to return, up to 100 keys. Omit to return all columns, including custom columns. Row reads are limited to 20,000 cells. An empty selection is not allowed. |
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows/component%3Aexample" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"row": {
"values": {
"name": "Pump 01",
"maintenance_priority": "High"
},
"key": "component:example",
"source": "generated"
},
"etag": "\"br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""
}Response schema
{
"type": "object",
"properties": {
"row": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"source": {
"type": "string",
"enum": [
"manual",
"generated"
]
},
"values": {
"type": "object",
"additionalProperties": {
"type": [
"string",
"null"
]
}
}
},
"required": [
"key",
"source",
"values"
],
"additionalProperties": false
},
"etag": {
"type": "string"
}
},
"required": [
"row",
"etag"
],
"additionalProperties": false
}PATCH/projects/{project_id}/workbook/sheets/{sheet_key}/rows/{row_key}
Update existing cells in one row atomically. Only supplied columns change; null clears a value.
Requires workbooks:write and an Editor or Manager role on an active project. Send If-Match and Idempotency-Key as headers. Set preview: true to evaluate the same changes without saving. Preview still needs If-Match; it needs no idempotency key. Validation runs after saving; validation_pending does not mean the edit failed.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
row_keyRequired | pathstring | The row’s key, percent-encoded as a path segment. |
If-MatchRequired | headerstring | Exact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application. |
Idempotency-Key | headerstring | Unique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours. |
valuesRequired | bodyobject | Map column keys to strings, null to clear, or {references: [{sheet_key, row_key}]} for exact reference selections. Omitted columns stay unchanged. |
preview | bodyboolean | Set true to inspect changes without saving. Defaults to false. Applying rechecks all guards. |
curl --fail-with-body --request PATCH \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows/component%3Aexample" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'If-Match: "br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"' \
-H 'Idempotency-Key: example-edit-001' \
-H "Content-Type: application/json" \
--data-binary '{"values":{"description":"Circulation pump serving level 2"}}'Example response
{
"applied": true,
"changes": [
{
"sheet_key": "component",
"row_key": "component:example",
"column_key": "description",
"old_value": "Circulation pump",
"value": "Circulation pump serving level 2",
"requested": true
}
],
"rows": [
{
"sheet_key": "component",
"row_key": "component:example",
"etag": "\"br-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\""
}
],
"validation_pending": true
}Response schema
{
"type": "object",
"properties": {
"applied": {
"type": "boolean"
},
"changes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sheet_key": {
"type": "string"
},
"row_key": {
"type": "string"
},
"column_key": {
"type": "string"
},
"old_value": {
"type": [
"string",
"null"
]
},
"value": {
"type": [
"string",
"null"
]
},
"requested": {
"type": "boolean"
}
},
"required": [
"sheet_key",
"row_key",
"column_key",
"old_value",
"value",
"requested"
],
"additionalProperties": false
}
},
"rows": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sheet_key": {
"type": "string"
},
"row_key": {
"type": "string"
},
"etag": {
"type": "string"
}
},
"required": [
"sheet_key",
"row_key",
"etag"
],
"additionalProperties": false
}
},
"validation_pending": {
"type": "boolean"
}
},
"required": [
"applied",
"changes",
"rows",
"validation_pending"
],
"additionalProperties": false
}DELETE/projects/{project_id}/workbook/sheets/{sheet_key}/rows/{row_key}
Delete a user-created row and settle references to it. Generated rows must be excluded instead.
Requires workbooks:rows:write and workbooks:read plus Editor or Manager access. Preview first with the row’s If-Match: REST uses preview=true in the query, while MCP uses preview: true. REST DELETE has no body; Contact reference choices also go in the query. Contact deletion requires reference_action clear or reassign; reassign also requires another Contact’s replacement_row_key. A Contact used by a replacement mapping must be changed in Settings first.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
row_keyRequired | pathstring | The row’s key, percent-encoded as a path segment. |
If-MatchRequired | headerstring | Exact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application. |
reference_action | querystring | For Contact deletion, explicitly choose clear or reassign. Other sheets do not accept this choice.Allowed: clear, reassign |
replacement_row_key | querystring | Same-sheet replacement row key. Required for reassign; Contact deletion permits another Contact, while exclusions require another generated Type or Space. |
preview | queryboolean | Set true to inspect changes without saving. Defaults to false. Applying rechecks all guards. |
Idempotency-Key | headerstring | Unique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours. |
curl --fail-with-body --request DELETE \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows/manual%3A00000000-0000-4000-8000-000000000006" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'If-Match: "br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"' \
-H 'Idempotency-Key: example-delete-001'Example response
{
"applied": true,
"affected": {
"rows_created": 0,
"rows_removed": 1,
"cells_updated": 0
},
"validation_pending": true
}Response schema
{
"type": "object",
"properties": {
"applied": {
"type": "boolean"
},
"affected": {
"type": "object",
"properties": {
"rows_created": {
"type": "integer",
"minimum": 0
},
"rows_removed": {
"type": "integer",
"minimum": 0
},
"cells_updated": {
"type": "integer",
"minimum": 0
}
},
"required": [
"rows_created",
"rows_removed",
"cells_updated"
],
"additionalProperties": false
},
"validation_pending": {
"type": "boolean"
}
},
"required": [
"applied",
"affected",
"validation_pending"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/sheets/{sheet_key}/rows/{row_key}/cells/{column_key}/options
Load choices or reference rows for one cell, in pages of 100.
Reference choices include stable row keys so duplicate or comma-containing names can be selected unambiguously. An empty list on a free-text column means there are no suggested choices, not that the cell is uneditable.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
row_keyRequired | pathstring | The row’s key, percent-encoded as a path segment. |
column_keyRequired | pathstring | Column key returned by List sheets. |
search | querystring | Case-insensitive substring search across cell values. Up to 200 characters. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/rows/component%3Aexample/cells/type_name/options" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'search=Pump'Example response
{
"data": [
{
"value": "Circulation Pump",
"reference": {
"sheet_key": "type",
"row_key": "type:circulation-pump"
}
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string"
},
"reference": {
"type": "object",
"properties": {
"sheet_key": {
"type": "string"
},
"row_key": {
"type": "string"
}
},
"required": [
"sheet_key",
"row_key"
],
"additionalProperties": false
}
},
"required": [
"value"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}POST/projects/{project_id}/workbook/cells/batch
Apply up to 100 cell edits across existing rows. Every edit succeeds or none is saved.
Include each row once with its ETag and values. Send Idempotency-Key as a header. Set preview: true to inspect direct and dependent changes without saving. Retry an uncertain result with the exact same body, ETags and idempotency key within 24 hours; a different edit needs a new key.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
Idempotency-Key | headerstring | Unique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours. |
rowsRequired | bodyarray of objects | Rows to edit, each with sheet_key, row_key, if_match and values. Include each row once; at most 100 cells across the batch. |
preview | bodyboolean | Set true to inspect changes without saving. Defaults to false. Applying rechecks all guards. |
curl --fail-with-body --request POST \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/cells/batch" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'Idempotency-Key: example-batch-001' \
-H "Content-Type: application/json" \
--data-binary '{"rows":[{"sheet_key":"component","row_key":"component:example","if_match":"\"br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"","values":{"description":"Circulation pump serving level 2"}}]}'Example response
{
"applied": true,
"changes": [
{
"sheet_key": "component",
"row_key": "component:example",
"column_key": "description",
"old_value": "Circulation pump",
"value": "Circulation pump serving level 2",
"requested": true
}
],
"rows": [
{
"sheet_key": "component",
"row_key": "component:example",
"etag": "\"br-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\""
}
],
"validation_pending": true
}Response schema
{
"type": "object",
"properties": {
"applied": {
"type": "boolean"
},
"changes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sheet_key": {
"type": "string"
},
"row_key": {
"type": "string"
},
"column_key": {
"type": "string"
},
"old_value": {
"type": [
"string",
"null"
]
},
"value": {
"type": [
"string",
"null"
]
},
"requested": {
"type": "boolean"
}
},
"required": [
"sheet_key",
"row_key",
"column_key",
"old_value",
"value",
"requested"
],
"additionalProperties": false
}
},
"rows": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sheet_key": {
"type": "string"
},
"row_key": {
"type": "string"
},
"etag": {
"type": "string"
}
},
"required": [
"sheet_key",
"row_key",
"etag"
],
"additionalProperties": false
}
},
"validation_pending": {
"type": "boolean"
}
},
"required": [
"applied",
"changes",
"rows",
"validation_pending"
],
"additionalProperties": false
}POST/projects/{project_id}/workbook/sheets/{sheet_key}/exclusions
Keep this individual model-derived row out of the workbook through future model updates.
Requires workbooks:exclusions:write and workbooks:read plus Manager access. Send the row’s If-Match. Type choices: exclude, clear_type or reassign. Space choices: exclude, resolve_space or reassign. Reassign requires replacement_row_key from the same sheet, and a generated replacement. Does not create an asset rule. Floor, Contact and Facility use separate app workflows and are not supported here. Include again uses the returned exclusion ID.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
If-MatchRequired | headerstring | Exact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application. |
Idempotency-Key | headerstring | Unique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours. |
row_keyRequired | bodystring | The row’s key, exactly as returned by List or search rows. Do not URL-encode JSON values. |
preview | bodyboolean | Set true to inspect changes without saving. Defaults to false. Applying rechecks all guards. |
component_action | bodystring | Type: exclude, clear_type or reassign. Space: exclude, resolve_space or reassign. Required for Type and Space exclusions only.Allowed: exclude, clear_type, resolve_space, reassign |
replacement_row_key | bodystring | Same-sheet replacement row key. Required for reassign; Contact deletion permits another Contact, while exclusions require another generated Type or Space. |
curl --fail-with-body --request POST \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/exclusions" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'If-Match: "br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"' \
-H 'Idempotency-Key: example-exclude-001' \
-H "Content-Type: application/json" \
--data-binary '{"row_key":"component:example"}'Example response
{
"applied": true,
"exclusion": {
"id": "00000000-0000-4000-8000-000000000005",
"name": "Pump 01",
"component_action": null,
"replacement_name": null,
"dependent_row_count": 0,
"etag": "\"br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""
},
"affected": {
"rows_created": 0,
"rows_removed": 1,
"cells_updated": 0
},
"validation_pending": true
}Response schema
{
"type": "object",
"properties": {
"applied": {
"type": "boolean"
},
"exclusion": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"component_action": {
"type": [
"string",
"null"
]
},
"replacement_name": {
"type": [
"string",
"null"
]
},
"dependent_row_count": {
"type": "integer",
"minimum": 0
},
"etag": {
"type": "string"
}
},
"required": [
"id",
"name",
"component_action",
"replacement_name",
"dependent_row_count",
"etag"
],
"additionalProperties": false
},
"affected": {
"type": "object",
"properties": {
"rows_created": {
"type": "integer",
"minimum": 0
},
"rows_removed": {
"type": "integer",
"minimum": 0
},
"cells_updated": {
"type": "integer",
"minimum": 0
}
},
"required": [
"rows_created",
"rows_removed",
"cells_updated"
],
"additionalProperties": false
},
"validation_pending": {
"type": "boolean"
}
},
"required": [
"applied",
"exclusion",
"affected",
"validation_pending"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/sheets/{sheet_key}/exclusions
Read the individual exclusions shown in the sheet’s Settings, including validators for Include again.
Requires workbooks:read. Exclusion IDs remain usable while an exclusion is active, even after model updates. The list does not include broad asset or parameter selection rules, Floor settings or removed Contact mappings.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/exclusions" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=25'Example response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000005",
"name": "Pump 01",
"component_action": null,
"replacement_name": null,
"dependent_row_count": 0,
"etag": "\"br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"component_action": {
"type": [
"string",
"null"
]
},
"replacement_name": {
"type": [
"string",
"null"
]
},
"dependent_row_count": {
"type": "integer",
"minimum": 0
},
"etag": {
"type": "string"
}
},
"required": [
"id",
"name",
"component_action",
"replacement_name",
"dependent_row_count",
"etag"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}DELETE/projects/{project_id}/workbook/sheets/{sheet_key}/exclusions/{exclusion_id}
Remove an individual exclusion and schedule the workbook update that can bring its row back.
Requires workbooks:exclusions:write and workbooks:read plus Manager access. Use the exclusion’s etag from List excluded rows, not a previous row ETag. HTTP 202 means the exclusion was removed and the workbook update is pending; poll Get workbook until updating is false. Preview returns HTTP 200 and saves nothing. REST DELETE uses preview=true in the query with no body; MCP uses preview: true. A row returns only if its model source and other selection settings allow it.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
sheet_keyRequired | pathstring | Sheet key returned by List sheets, such as component. |
exclusion_idRequired | pathUUID | Exclusion ID returned by List excluded rows or Exclude a generated row. |
If-MatchRequired | headerstring | Exact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application. |
preview | queryboolean | Set true to inspect changes without saving. Defaults to false. Applying rechecks all guards. |
Idempotency-Key | headerstring | Unique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours. |
curl --fail-with-body --request DELETE \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets/component/exclusions/$BIMRELAY_EXCLUSION_ID" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
-H 'If-Match: "br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"' \
-H 'Idempotency-Key: example-include-001'Example response
{
"applied": true,
"exclusion_id": "00000000-0000-4000-8000-000000000005",
"updating": true
}Response schema
{
"type": "object",
"properties": {
"applied": {
"type": "boolean"
},
"exclusion_id": {
"type": "string"
},
"updating": {
"type": "boolean"
}
},
"required": [
"applied",
"exclusion_id",
"updating"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/issues
Read existing validation findings for a workbook.
This endpoint reads existing findings; it does not run validation.
| Name | Type | Description |
|---|---|---|
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
project_idRequired | pathUUID | Project ID returned by List projects. |
status | querystring | Return only findings with this status. Omit for all statuses.Allowed: open, ignored, resolved |
severity | querystring | Return only findings with this severity. Omit for all severities.Allowed: error, warning |
sheet_key | querystring | Return findings for one sheet key. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/issues" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=20' \
--data-urlencode 'status=open'Example response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000009",
"code": "missing_value",
"severity": "warning",
"status": "open",
"message": "A value needs review.",
"location": {
"sheet_key": "component",
"row_key": "component:example",
"column_key": "description"
}
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"code": {
"type": "string"
},
"severity": {
"type": "string"
},
"status": {
"type": "string"
},
"message": {
"type": "string"
},
"location": {
"type": "object",
"properties": {
"sheet_key": {
"type": [
"string",
"null"
]
},
"row_key": {
"type": [
"string",
"null"
]
},
"column_key": {
"type": [
"string",
"null"
]
}
},
"required": [
"sheet_key",
"row_key",
"column_key"
],
"additionalProperties": false
}
},
"required": [
"id",
"code",
"severity",
"status",
"message",
"location"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/asset-review/recommendations
Read existing Asset review recommendations and their affected assets or attributes.
Available only when the workspace allows Asset review. Reading findings does not start a review or change selections.
| Name | Type | Description |
|---|---|---|
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
project_idRequired | pathUUID | Project ID returned by List projects. |
status | querystring | Return only findings with this status. Omit for all statuses.Allowed: open, addressed, ignored, no_longer_flagged |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/asset-review/recommendations" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=20' \
--data-urlencode 'status=open'Example response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000010",
"status": "open",
"title": "Consider excluding reference geometry",
"explanation": "Review whether these objects belong in the handover.",
"recommendation": {
"action": "exclude",
"target": "asset"
},
"affected": [
{
"category": "Generic Models",
"family": "Reference Geometry",
"type": "Coordination",
"included_count": 3,
"total_count": 3
}
],
"last_reviewed_at": "2026-10-07T12:00:00Z"
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string"
},
"title": {
"type": "string"
},
"explanation": {
"type": "string"
},
"recommendation": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"include",
"exclude"
]
},
"target": {
"type": "string",
"enum": [
"asset",
"attribute"
]
}
},
"required": [
"action",
"target"
],
"additionalProperties": false
},
"affected": {
"type": "array",
"items": {
"type": "object",
"properties": {
"category": {
"type": "string"
},
"family": {
"type": "string"
},
"type": {
"type": "string"
},
"parameter": {
"type": "string"
},
"unit": {
"type": "string"
},
"included_count": {
"type": "integer",
"minimum": 0
},
"total_count": {
"type": "integer",
"minimum": 0
}
},
"required": [],
"additionalProperties": false
}
},
"last_reviewed_at": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"status",
"title",
"explanation",
"recommendation",
"affected",
"last_reviewed_at"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/exports
List exports that have already been requested in BIMRelay.
This endpoint lists existing exports; it does not create them.
| Name | Type | Description |
|---|---|---|
limit | queryinteger | Page size. Defaults to 100; allowed range 1–200. |
cursor | querystring | Opaque next_cursor from the previous page. Keep other query options unchanged. |
project_idRequired | pathUUID | Project ID returned by List projects. |
curl --fail-with-body --get \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/exports" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
--data-urlencode 'limit=20'Example response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000012",
"status": "completed",
"format": "xlsx",
"created_at": "2026-10-07T12:00:00Z",
"completed_at": "2026-10-07T12:00:00Z"
}
],
"next_cursor": null
}Response schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string"
},
"format": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"completed_at": {
"type": [
"string",
"null"
],
"format": "date-time"
}
},
"required": [
"id",
"status",
"format",
"created_at",
"completed_at"
],
"additionalProperties": false
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data",
"next_cursor"
],
"additionalProperties": false
}GET/projects/{project_id}/workbook/exports/{export_id}/download
Get a temporary URL for an existing completed Excel export.
Download from the returned URL within five minutes. Treat it as a secret; it remains usable until expiry even if the integration is revoked. An incomplete or failed export returns 409 export_not_ready.
| Name | Type | Description |
|---|---|---|
project_idRequired | pathUUID | Project ID returned by List projects. |
export_idRequired | pathUUID | Export ID returned by List exports. |
curl --fail-with-body \
"https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/exports/$BIMRELAY_EXPORT_ID/download" \
-H "Authorization: Bearer $BIMRELAY_API_TOKEN"Example response
{
"url": "https://storage.example.com/example.xlsx?temporary=example",
"expires_at": "2026-10-07T12:05:00Z"
}Response schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"expires_at": {
"type": "string",
"format": "date-time"
}
},
"required": [
"url",
"expires_at"
],
"additionalProperties": false
}OAuth for connected apps (optional)
Use OAuth when you build an application that other BIMRelay users connect to their own accounts. Each user signs in to BIMRelay, reviews the requested permissions, chooses projects and approves the requested access. Your application never handles their password or a copied token. For your own scripts, a personal API token is simpler.
BIMRelay supports the authorization code grant with S256 PKCE, which every client must use, together with rotating refresh tokens and resource indicators (RFC 8707). Dynamic client registration and the client credentials, password and implicit grants are not supported.
Register your application
Email support@bimrelay.com with your application’s name, whether it is a public client, such as a desktop or mobile app, or a confidential server application, and its exact callback URLs. You receive a client ID, plus a client secret for confidential clients. Keep the secret on your server; it cannot be shown again.
Callback URLs must use HTTPS. Local tools may instead register HTTP loopback callbacks. With an IP loopback address such as http://127.0.0.1/callback, the port can vary at runtime, but the host, path and query must match exactly.
Endpoints
| Item | URL |
|---|---|
| Authorization server metadata | https://api.bimrelay.com/.well-known/oauth-authorization-server |
| Protected resource metadata | https://api.bimrelay.com/.well-known/oauth-protected-resource/api/v1 |
| Authorization endpoint | https://api.bimrelay.com/oauth/authorize |
| Token endpoint | https://api.bimrelay.com/oauth/token |
| Revocation endpoint | https://api.bimrelay.com/oauth/revoke |
| Resource indicator | https://api.bimrelay.com/api/v1 |
Read these values from the authorization server metadata rather than hard-coding them. API responses with status 401 or 403 include a WWW-Authenticate header that points to the protected resource metadata.
1. Request authorization
Generate a random state and a PKCE code verifier, then send the user’s browser to the authorization endpoint with these URL-encoded query parameters:
| Parameter | Value |
|---|---|
response_type | code |
client_id | Your registered client ID |
redirect_uri | One of your registered callback URLs |
scope | Space-separated scopes, such as projects:read workbooks:read |
state | A cryptographically random value that you verify on return |
code_challenge | Base64url-encoded SHA-256 hash of the PKCE code verifier, without padding |
code_challenge_method | S256 |
resource | https://api.bimrelay.com/api/v1 |
BIMRelay then redirects to your callback with code, state and iss, or with error=access_denied if the user cancels. Before using the code, verify that state matches your request and that iss matches the issuer in the authorization server metadata.
2. Exchange the code for tokens
POST form-encoded parameters to the token endpoint, repeating the same redirect_uri and resource. This example is for a public client:
curl --fail-with-body 'https://api.bimrelay.com/oauth/token' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "client_id=$BIMRELAY_CLIENT_ID" \
--data-urlencode "code=$BIMRELAY_AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=$BIMRELAY_REDIRECT_URI" \
--data-urlencode "code_verifier=$BIMRELAY_PKCE_VERIFIER" \
--data-urlencode 'resource=https://api.bimrelay.com/api/v1'Confidential clients also authenticate with HTTP Basic or with client_secret in the form body. The response contains an access token that is valid for 15 minutes and a refresh token:
{
"access_token": "<access token>",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "<refresh token>",
"scope": "projects:read workbooks:read",
"created_at": 1791403200
}Send the access token in the Authorization header, exactly like a personal API token. Authorization codes expire after five minutes and can be exchanged only once. Never retry a code exchange: replaying a used code revokes the tokens it issued. If an exchange fails without a clear response, start a new authorization.
3. Refresh and revoke
To get a new access token, POST grant_type=refresh_token, the refresh token, your client authentication and the same resource to the token endpoint:
curl --fail-with-body 'https://api.bimrelay.com/oauth/token' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode "client_id=$BIMRELAY_CLIENT_ID" \
--data-urlencode "refresh_token=$BIMRELAY_REFRESH_TOKEN" \
--data-urlencode 'resource=https://api.bimrelay.com/api/v1'Each refresh returns a new refresh token and invalidates the previous one. Store the replacement before using it, and never refresh concurrently: reusing a spent refresh token revokes the whole connection. A refresh can narrow scopes but cannot add them.
A connection expires 30 days after the user approves it, however often it is refreshed. After expiry or revocation, send the user through authorization again. Users can revoke a connection at any time in Integrations, and your application can revoke one by POSTing the token and its client authentication to the revocation endpoint.
OAuth endpoints return standard OAuth error responses, such as invalid_grant, rather than the API error object. Use an OAuth library that supports PKCE and the resource parameter.