Docs
Authentication

Authentication

Construction AI exposes two authenticated surfaces — bearer-token auth for the REST API and OAuth 2.1 with PKCE for MCP clients. Same tokens, same audit, two flows.

Construction AI's two surfaces share a common identity layer. A token authenticated through one flow is honoured on the other (where scopes permit). This page covers both flows, plus token lifecycle, refresh, revocation, and the impersonation rules introduced by the §9.5 hardening gateway.

The two flows at a glance

FlowUsed byToken formMint via
Bearer tokenServer-to-server REST integrations; direct MCP clients (Claude Code, scripts)cai_sk_live_<32 hex>Dashboard → Settings → Integrations → API Keys
OAuth 2.1 with PKCEClaude desktop and other MCP clients with a "Connect to..." UISame cai_sk_live_* shape, returned via authorisation-code exchangeStandard OAuth 2.1 flow (see below)

Both flows produce a token that downstream code treats identically. The auth layer doesn't distinguish how a token was minted — only what scopes it carries and which user it's linked to.

Bearer token flow (REST integrations)

The simpler path. Use this when you control the consumer (a vendor server, a script, an internal tool).

1. Mint a key

In the dashboard: Settings → Integrations → API Keys → Create new key.

You'll be prompted for:

  • Name — a label you'll recognise later
  • Scopes — what the key is allowed to do (see Scopes & permissions)
  • Allowed projects (optional) — restrict to specific projects
  • Rate limit — RPM and daily caps (defaults 60 / 10,000)
  • Expiry (optional) — when the key stops working

The plaintext key is shown once. We store only a SHA-256 hash; if you lose the plaintext, you create a new key.

2. Use it

curl -H "Authorization: Bearer cai_sk_live_..." \
     https://app.constructionai.io/api/construction-pm/projects

3. Rotate or revoke

  • Rotate: create a new key with the same scopes, switch your integration to it, then revoke the old one. Rotation has zero downtime — both keys are valid until the old one is revoked.
  • Revoke: in the dashboard, click Revoke next to the key. Effect is immediate; the next request with that token returns 401 unauthorized.

There is no auto-rotation. Bearer tokens are long-lived by design (intended for server-to-server flows where short-lived tokens add friction without proportionate security gain). If you need short-lived tokens, use the OAuth flow.

OAuth 2.1 flow (MCP clients)

Use this when the consumer is an AI agent (Claude desktop, ChatGPT custom connector, internal corporate agent) that expects to perform a standard OAuth handshake.

Discovery

Construction AI publishes the standard OAuth metadata documents:

  • Protected resource metadata (RFC 9728): GET /.well-known/oauth-protected-resource
  • Authorisation server metadata (RFC 8414): GET /.well-known/oauth-authorization-server

Most MCP clients fetch these automatically — point them at the platform's base URL and the rest is configuration.

Endpoints

  • Authorise: GET /oauth/authorize
  • Token exchange: POST /oauth/token
  • Dynamic client registration (RFC 7591): POST /oauth/register
  • Revoke: POST /oauth/revoke

Flow

  1. Client registers dynamically via /oauth/register (or uses pre-issued client credentials).
  2. Client redirects the user to /oauth/authorize with a PKCE code challenge.
  3. User signs in to the Construction AI dashboard if not already, sees the consent screen listing the requested scopes (with explicit risk warnings on sensitive ones), and approves.
  4. Construction AI redirects back with an authorisation code.
  5. Client exchanges the code at /oauth/token for an access token (and a refresh token).
  6. Access token format is the same cai_sk_live_* shape — usable against either surface.

Access token lifetime

OAuth access tokens are issued with a configurable expiry (default 90 days, choices: 30 / 60 / 90 / 365 / never). The refresh token is rotate-on-use per RFC 6819 — every refresh invalidates the previous refresh token and issues a fresh pair. If two clients both try to use the same refresh token, the second gets invalid_grant and the grant is flagged for review.

Pattern 1 only (for now)

OAuth tokens are per-user, per-install. A six-person team using Claude desktop = six separate consent flows, one per user per machine. This is by design — gives us per-user auditability and mirrors the web-app permission model exactly. Shared org-level tokens are explicitly not supported because they break per-user audit and revocation.

Token storage best practices

Whichever flow you use, the token is a credential. Treat it like one:

  • Never commit tokens to source control. Use environment variables, secret managers (AWS Secrets Manager, Vault, etc.), or local .env files in .gitignore.
  • Never share tokens between services. Mint a separate key per consumer. Compromise blast radius is then bounded to one consumer.
  • Never paste tokens into Slack, Discord, or any shared channel. If a token leaks into a shared channel, revoke it and mint a new one. Treat the leak as compromised.
  • Audit your usage. Every authenticated call is logged. The audit log shows: which key was used, which user it acted as, which endpoint, when. Use it.

Identity and impersonation

Every key is linked to a specific user at creation time (linked_user_id). By default, the key acts as that user — calls resolve to their permissions, their audit trail, their context.

If you need a key to act as different users (a vendor integration where your customer's user identity matters per-call), set the X-User-Id header on the request. Construction AI honours this header only if the key holds the impersonate:user scope. Without that scope, the header is rejected with 403 forbidden:

{
  "success": false,
  "error": "forbidden",
  "message": "X-User-Id specifies a different user than the key is linked to; the impersonate:user scope is required to act as another user."
}

The X-User-Id header value must be a valid UUID of a user in the same organisation as the key. Cross-organisation impersonation is impossible regardless of scope.

This is a hardening introduced as part of the §9.5 security gateway. The default-off semantics mean a leaked key without impersonate:user is bounded to one user identity, not the entire organisation.

Common failure modes

SymptomCauseFix
401 unauthorizedInvalid, expired, or revoked tokenVerify the token verbatim (no whitespace, full prefix included). Check it's not been revoked in the dashboard.
403 forbidden — API key lacks read scopeToken created without the scope this endpoint requiresEdit the key in the dashboard to add the scope, or create a new key with the right scopes
403 forbidden — impersonate:user requiredSet X-User-Id to a user other than the linked user, key lacks impersonate:userEither drop the header (token acts as its linked user) or add the scope at key creation
429 rate_limitedHit the per-key RPM or daily capHonour the Retry-After header. See Rate limits.
403 forbidden — API key does not have access to this projectKey has allowed_projects set and the requested project isn't in the listAdd the project to the key's allow-list or use an unrestricted key

Where to go next

  • Scopes & permissions — the full scope catalogue and how scopes interact with HTTP methods.
  • Rate limits — defaults, headers, 429 handling.
  • Errors — the full error shape and code reference.