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

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.

  1. In BIMRelay, open your profile menu and choose Integrations, or go to app.bimrelay.com/integrations. Select Create API token.
  2. 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.
  3. Copy the token. It is shown only once. Store it in a password manager or secrets store, and load it into the BIMRELAY_API_TOKEN environment variable to run the examples in this guide.

List the projects the token can read:

cURL
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:

JSON
{
  "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

Permissions and their scopes
ScopeShown in BIMRelay asGrants
projects:readWorkspace and project names and detailsRead workspace names and project details
workbooks:readWorkbook sheets and rowsCurrent workbook status, sheet schemas and rows
reviews:readIssues and Asset review recommendationsExisting validation Issues and Asset review findings
exports:readExisting exports and download linksExisting Excel exports and temporary download links for completed files
workbooks:writeEdit workbook cellsEdit existing cells; requires workbook read permission and your Editor or Manager role
workbooks:rows:writeCreate and delete manual rowsManage manual rows; requires workbook reads and your Editor or Manager role
workbooks:exclusions:writeExclude and include generated rowsManage 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
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
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'
JSON
{
  "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 "".

  • search matches 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.

  1. Use List sheets to discover column keys and their editing rules. For choices and references, load one cell's options when needed.
  2. Get the full row, without a columns[] selection. Copy its etag exactly, including the surrounding quotes. The same strong validator is returned in the HTTP ETag header. Weak tags beginning with W/ cannot be used for editing.
  3. Send a PATCH to that row with only the columns to change, an If-Match header containing the ETag, and a unique Idempotency-Key.
Edit one rowcURL
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:

JSON
{
  "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.

Preview a manual-row deletioncURL
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
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.

Request limits
LimitValue
Page size100 by default; 1–200 allowed. Cell options use fixed 100-item pages
Returned cellsUp to 20,000 per row read (rows × columns). Request fewer rows or select columns for wide sheets
Column selectionAll columns by default, including custom columns. An explicit selection allows up to 100 column keys
Sheet schemaUp to 200 sheets and 5,000 columns
Response size1 MiB for reads; 256 KiB for edits
Request rate120 requests per user per minute, shared by all of the user’s REST and MCP connections
Free workspace allowance20 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 operationsOne per user. Send requests sequentially
Processing timeFive seconds per request. Narrow expensive searches
Cursor lifetime15 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:

JSON
{
  "error": {
    "code": "insufficient_scope",
    "message": "This connection needs workbooks:read",
    "request_id": "example-request-id"
  }
}
Error codes
StatusCodeWhat to do
400idempotency_key_requiredSend an Idempotency-Key when saving edits.
403permission_deniedManual rows and cell editing require Editor or Manager; exclusions require Manager.
409workbook_busy, project_read_only, cell_locked, edit_conflictWait for updates or active editing to finish. Archived projects cannot be edited. Read the row again before resubmitting.
409idempotency_conflictUse a new key for a different edit; identical retries must retain their body and ETags.
412precondition_failedRead the full row or exclusion again and review its newer state.
422invalid_value, invalid_column, invalid_reference, invalid_exclusion, generated_row, contact_in_useCorrect the value, column, reference or exclusion choice. Exclude generated rows instead of deleting them; change Contact replacement mappings in Settings. No changes were saved.
428precondition_requiredSend the full row’s ETag, or the exclusion’s ETag when including again, in If-Match.
413request_too_large, edit_too_largeUse a smaller cell batch. Make large row cascades and dependent renames in the app.
400invalid_request, invalid_column, invalid_cursorCorrect the parameters or column keys. For an expired or mismatched cursor, restart paging.
401invalid_tokenCheck the token, its expiry and that it was created for the REST API. Replace an expired personal token; refresh or reconnect an OAuth application.
403insufficient_scopeCreate or approve a connection with the required scope.
404not_foundCheck IDs, the connection’s projects and your current membership. Unavailable and unauthorized resources both return 404.
409workbook_changedRestart the request without a cursor to read the current workbook.
409export_not_readyCheck the export’s status. Request a download link only after the export has completed.
413response_too_largeRequest fewer rows or columns for reads, or edit fewer cells for writes.
413schema_too_largeThe workbook exceeds the supported schema size, so smaller pages will not help. Contact support.
429rate_limited, read_busySend one request at a time and wait for the number of seconds in Retry-After.
429monthly_quota_exceededThe 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.
429idempotency_capacity_exceededWait for older retry results to expire. Previously saved retries and previews remain available.
503query_timeoutNarrow the search or filters before retrying.
503temporarily_unavailableRetry with backoff, respecting Retry-After.
500NoneAn 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 connection details

GET/me

Check the token's user, permissions and expiry.

No scope required. Works with every connection.

No parameters.

Example requestcURL
curl --fail-with-body \
  "https://api.bimrelay.com/api/v1/me" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN"
Example response
JSON
{
  "user": {
    "id": "00000000-0000-4000-8000-000000000008",
    "name": "Alex Example"
  },
  "scopes": [
    "projects:read",
    "workbooks:read"
  ],
  "expires_at": "2026-11-06T12:00:00Z"
}
Response schema
JSON
{
  "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
}

List workspaces

GET/workspaces

List workspaces containing projects this connection can currently read.

Required scopes: projects:read

Only workspace IDs and names are returned. Seeing a workspace does not grant access to its other projects.

Parameters
NameTypeDescription
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
Example requestcURL
curl --fail-with-body --get \
  "https://api.bimrelay.com/api/v1/workspaces" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
  --data-urlencode 'limit=20'
Example response
JSON
{
  "data": [
    {
      "id": "00000000-0000-4000-8000-000000000002",
      "name": "Example workspace"
    }
  ],
  "next_cursor": null
}
Response schema
JSON
{
  "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 a workspace

GET/workspaces/{workspace_id}

Read the name of a workspace containing an accessible project.

Required scopes: projects:read

Parameters
NameTypeDescription
workspace_idRequiredpathUUIDWorkspace ID returned by List workspaces. On List projects, restricts results to approved projects in that workspace.
Example requestcURL
curl --fail-with-body \
  "https://api.bimrelay.com/api/v1/workspaces/$BIMRELAY_WORKSPACE_ID" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN"
Example response
JSON
{
  "id": "00000000-0000-4000-8000-000000000002",
  "name": "Example workspace"
}
Response schema
JSON
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "name"
  ],
  "additionalProperties": false
}

List projects

GET/projects

List the approved projects you can currently access.

Required scopes: projects:read

Optionally pass workspace_id to list only approved projects in that workspace. An unavailable workspace returns 404; the filter never broadens project access.

Parameters
NameTypeDescription
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
workspace_idqueryUUIDWorkspace ID returned by List workspaces. On List projects, restricts results to approved projects in that workspace.
Example requestcURL
curl --fail-with-body --get \
  "https://api.bimrelay.com/api/v1/projects" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN" \
  --data-urlencode 'limit=20'
Example response
JSON
{
  "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
JSON
{
  "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 a project

GET/projects/{project_id}

Read the details of one approved project.

Required scopes: projects:read

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
Example requestcURL
curl --fail-with-body \
  "https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN"
Example response
JSON
{
  "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
JSON
{
  "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 a workbook

GET/projects/{project_id}/workbook

Read the status of the current workbook for a project.

Required scopes: workbooks:read

Use the same project_id to list worksheets, read rows, list Issues and recommendations, or download existing exports.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
Example requestcURL
curl --fail-with-body \
  "https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN"
Example response
JSON
{
  "project_id": "00000000-0000-4000-8000-000000000001",
  "updating": false,
  "latest_version_number": 2
}
Response schema
JSON
{
  "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
}

List sheets and columns

GET/projects/{project_id}/workbook/sheets

Discover current worksheet keys, column keys and row counts, including custom columns.

Required scopes: workbooks:read

row_count is the full sheet count, not the count after a search. Use the returned keys in subsequent requests.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
Example requestcURL
curl --fail-with-body \
  "https://api.bimrelay.com/api/v1/projects/$BIMRELAY_PROJECT_ID/workbook/sheets" \
  -H "Authorization: Bearer $BIMRELAY_API_TOKEN"
Example response
JSON
{
  "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
JSON
{
  "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
}

List or search rows

GET/projects/{project_id}/workbook/sheets/{sheet_key}/rows

Read current worksheet rows, with optional search, filters and selected columns.

Required scopes: workbooks:read

Search matches cell values across the sheet, including columns omitted from the response. Multiple exact filters are combined with AND.

Parameters
NameTypeDescription
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
searchquerystringCase-insensitive substring search across cell values. Up to 200 characters.
columns[]queryarray of stringsRepeat 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]queryobjectExact, 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_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
Example requestcURL
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
JSON
{
  "data": [
    {
      "values": {
        "name": "Pump 01",
        "maintenance_priority": "High"
      },
      "key": "component:example",
      "source": "generated"
    }
  ],
  "next_cursor": null
}
Response schema
JSON
{
  "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
}

Create a manual row

POST/projects/{project_id}/workbook/sheets/{sheet_key}/rows

Add a user-created row with standard or custom column values.

Required scopes: workbooks:read, workbooks:rows:write

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
Idempotency-KeyheaderstringUnique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours.
valuesRequiredbodyobjectInitial column values as strings, null or exact references. An empty object creates a blank row; omitted columns use the normal defaults.
previewbodybooleanSet true to inspect changes without saving. Defaults to false. Applying rechecks all guards.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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 a row

GET/projects/{project_id}/workbook/sheets/{sheet_key}/rows/{row_key}

Read one current row using its key, not its displayed Name.

Required scopes: workbooks:read

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
row_keyRequiredpathstringThe row’s key, percent-encoded as a path segment.
columns[]queryarray of stringsRepeat 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.
Example requestcURL
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
JSON
{
  "row": {
    "values": {
      "name": "Pump 01",
      "maintenance_priority": "High"
    },
    "key": "component:example",
    "source": "generated"
  },
  "etag": "\"br-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""
}
Response schema
JSON
{
  "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
}

Edit cells in a row

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.

Required scopes: workbooks:read, workbooks:write

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
row_keyRequiredpathstringThe row’s key, percent-encoded as a path segment.
If-MatchRequiredheaderstringExact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application.
Idempotency-KeyheaderstringUnique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours.
valuesRequiredbodyobjectMap column keys to strings, null to clear, or {references: [{sheet_key, row_key}]} for exact reference selections. Omitted columns stay unchanged.
previewbodybooleanSet true to inspect changes without saving. Defaults to false. Applying rechecks all guards.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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 a manual row

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.

Required scopes: workbooks:read, workbooks:rows:write

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
row_keyRequiredpathstringThe row’s key, percent-encoded as a path segment.
If-MatchRequiredheaderstringExact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application.
reference_actionquerystringFor Contact deletion, explicitly choose clear or reassign. Other sheets do not accept this choice.Allowed: clear, reassign
replacement_row_keyquerystringSame-sheet replacement row key. Required for reassign; Contact deletion permits another Contact, while exclusions require another generated Type or Space.
previewquerybooleanSet true to inspect changes without saving. Defaults to false. Applying rechecks all guards.
Idempotency-KeyheaderstringUnique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours.
Example requestcURL
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
JSON
{
  "applied": true,
  "affected": {
    "rows_created": 0,
    "rows_removed": 1,
    "cells_updated": 0
  },
  "validation_pending": true
}
Response schema
JSON
{
  "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
}

List cell options

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.

Required scopes: workbooks:read

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
row_keyRequiredpathstringThe row’s key, percent-encoded as a path segment.
column_keyRequiredpathstringColumn key returned by List sheets.
searchquerystringCase-insensitive substring search across cell values. Up to 200 characters.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
Example requestcURL
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
JSON
{
  "data": [
    {
      "value": "Circulation Pump",
      "reference": {
        "sheet_key": "type",
        "row_key": "type:circulation-pump"
      }
    }
  ],
  "next_cursor": null
}
Response schema
JSON
{
  "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
}

Edit a batch of cells

POST/projects/{project_id}/workbook/cells/batch

Apply up to 100 cell edits across existing rows. Every edit succeeds or none is saved.

Required scopes: workbooks:read, workbooks:write

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
Idempotency-KeyheaderstringUnique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours.
rowsRequiredbodyarray of objectsRows to edit, each with sheet_key, row_key, if_match and values. Include each row once; at most 100 cells across the batch.
previewbodybooleanSet true to inspect changes without saving. Defaults to false. Applying rechecks all guards.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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
}

Exclude a generated row

POST/projects/{project_id}/workbook/sheets/{sheet_key}/exclusions

Keep this individual model-derived row out of the workbook through future model updates.

Required scopes: workbooks:read, workbooks:exclusions:write

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
If-MatchRequiredheaderstringExact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application.
Idempotency-KeyheaderstringUnique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours.
row_keyRequiredbodystringThe row’s key, exactly as returned by List or search rows. Do not URL-encode JSON values.
previewbodybooleanSet true to inspect changes without saving. Defaults to false. Applying rechecks all guards.
component_actionbodystringType: 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_keybodystringSame-sheet replacement row key. Required for reassign; Contact deletion permits another Contact, while exclusions require another generated Type or Space.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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
}

List excluded rows

GET/projects/{project_id}/workbook/sheets/{sheet_key}/exclusions

Read the individual exclusions shown in the sheet’s Settings, including validators for Include again.

Required scopes: workbooks:read

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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
}

Include an excluded row again

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.

Required scopes: workbooks:read, workbooks:exclusions:write

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
sheet_keyRequiredpathstringSheet key returned by List sheets, such as component.
exclusion_idRequiredpathUUIDExclusion ID returned by List excluded rows or Exclude a generated row.
If-MatchRequiredheaderstringExact ETag from a full Get row response, or from List excluded rows when including again. Required for destructive previews and application.
previewquerybooleanSet true to inspect changes without saving. Defaults to false. Applying rechecks all guards.
Idempotency-KeyheaderstringUnique request key, up to 128 characters. Required when saving. Reuse with the identical request to retry within 24 hours.
Example requestcURL
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
JSON
{
  "applied": true,
  "exclusion_id": "00000000-0000-4000-8000-000000000005",
  "updating": true
}
Response schema
JSON
{
  "type": "object",
  "properties": {
    "applied": {
      "type": "boolean"
    },
    "exclusion_id": {
      "type": "string"
    },
    "updating": {
      "type": "boolean"
    }
  },
  "required": [
    "applied",
    "exclusion_id",
    "updating"
  ],
  "additionalProperties": false
}

List Issues

GET/projects/{project_id}/workbook/issues

Read existing validation findings for a workbook.

Required scopes: reviews:read

This endpoint reads existing findings; it does not run validation.

Parameters
NameTypeDescription
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
project_idRequiredpathUUIDProject ID returned by List projects.
statusquerystringReturn only findings with this status. Omit for all statuses.Allowed: open, ignored, resolved
severityquerystringReturn only findings with this severity. Omit for all severities.Allowed: error, warning
sheet_keyquerystringReturn findings for one sheet key.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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
}

List Asset review recommendations

GET/projects/{project_id}/workbook/asset-review/recommendations

Read existing Asset review recommendations and their affected assets or attributes.

Required scopes: reviews:read

Available only when the workspace allows Asset review. Reading findings does not start a review or change selections.

Parameters
NameTypeDescription
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
project_idRequiredpathUUIDProject ID returned by List projects.
statusquerystringReturn only findings with this status. Omit for all statuses.Allowed: open, addressed, ignored, no_longer_flagged
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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
}

List exports

GET/projects/{project_id}/workbook/exports

List exports that have already been requested in BIMRelay.

Required scopes: exports:read

This endpoint lists existing exports; it does not create them.

Parameters
NameTypeDescription
limitqueryintegerPage size. Defaults to 100; allowed range 1–200.
cursorquerystringOpaque next_cursor from the previous page. Keep other query options unchanged.
project_idRequiredpathUUIDProject ID returned by List projects.
Example requestcURL
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
JSON
{
  "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
JSON
{
  "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 an export download link

GET/projects/{project_id}/workbook/exports/{export_id}/download

Get a temporary URL for an existing completed Excel export.

Required scopes: exports:read

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.

Parameters
NameTypeDescription
project_idRequiredpathUUIDProject ID returned by List projects.
export_idRequiredpathUUIDExport ID returned by List exports.
Example requestcURL
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
JSON
{
  "url": "https://storage.example.com/example.xlsx?temporary=example",
  "expires_at": "2026-10-07T12:05:00Z"
}
Response schema
JSON
{
  "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

OAuth endpoints
ItemURL
Authorization server metadatahttps://api.bimrelay.com/.well-known/oauth-authorization-server
Protected resource metadatahttps://api.bimrelay.com/.well-known/oauth-protected-resource/api/v1
Authorization endpointhttps://api.bimrelay.com/oauth/authorize
Token endpointhttps://api.bimrelay.com/oauth/token
Revocation endpointhttps://api.bimrelay.com/oauth/revoke
Resource indicatorhttps://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:

GET /oauth/authorize parameters
ParameterValue
response_typecode
client_idYour registered client ID
redirect_uriOne of your registered callback URLs
scopeSpace-separated scopes, such as projects:read workbooks:read
stateA cryptographically random value that you verify on return
code_challengeBase64url-encoded SHA-256 hash of the PKCE code verifier, without padding
code_challenge_methodS256
resourcehttps://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
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:

JSON
{
  "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
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.