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
| Flow | Used by | Token form | Mint via |
|---|---|---|---|
| Bearer token | Server-to-server REST integrations; direct MCP clients (Claude Code, scripts) | cai_sk_live_<32 hex> | Dashboard → Settings → Integrations → API Keys |
| OAuth 2.1 with PKCE | Claude desktop and other MCP clients with a "Connect to..." UI | Same cai_sk_live_* shape, returned via authorisation-code exchange | Standard 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/projects3. 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
- Client registers dynamically via
/oauth/register(or uses pre-issued client credentials). - Client redirects the user to
/oauth/authorizewith a PKCE code challenge. - 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.
- Construction AI redirects back with an authorisation code.
- Client exchanges the code at
/oauth/tokenfor an access token (and a refresh token). - 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
.envfiles 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
| Symptom | Cause | Fix |
|---|---|---|
401 unauthorized | Invalid, expired, or revoked token | Verify the token verbatim (no whitespace, full prefix included). Check it's not been revoked in the dashboard. |
403 forbidden — API key lacks read scope | Token created without the scope this endpoint requires | Edit the key in the dashboard to add the scope, or create a new key with the right scopes |
403 forbidden — impersonate:user required | Set X-User-Id to a user other than the linked user, key lacks impersonate:user | Either drop the header (token acts as its linked user) or add the scope at key creation |
429 rate_limited | Hit the per-key RPM or daily cap | Honour the Retry-After header. See Rate limits. |
403 forbidden — API key does not have access to this project | Key has allowed_projects set and the requested project isn't in the list | Add 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.