Skip to content

Authentication

Two bearer formats. PATs are the simplest path for scripting the REST API. OAuth 2.1 + PKCE is for MCP clients, which run the flow for you.

Personal Access Tokens

A PAT is a long-lived bearer (strac_pat_*) an admin mints with a Cognito session. The server stores only its SHA-256 hash — the secret is returned exactly once. PATs work on both /api/v1/* and the MCP endpoint /mcp — same scopes, same revocation. OAuth below remains the right fit for interactive clients (Claude Code, Cursor) that can open a browser.

Mint a PAT

POST /api/companies/{companyId}/pats with a Cognito ID token. Role gate: owner / admin (to mint write scopes).

Body (Zod-strict — extra keys rejected)

FieldTypeDescription
labelrequiredstring, 1–80 charsShows in the admin UI and the audit log.
scopesrequiredstring[] (non-empty)Least-privilege scope set; each must be a canonical scope.
expiresInDaysrequiredintegerToken lifetime in days. 0 = never expires (service accounts).03090365

Request:

bashPOST /api/companies/{companyId}/pats
curl -X POST 'https://comply.strac.io/api/companies/comp_demo/pats' \  -H 'Authorization: Bearer eyJraWQ...<cognito-id-token>' \  -H 'Content-Type: application/json' \  -d '{  "label": "CI deploy bot",  "scopes": [    "compliance:read",    "documents:write"  ],  "expiresInDays": 90}'

Response:

json
{  "success": true,  "data": {    "token": "strac_pat_xxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxx",    "pat": {      "patId": "<sha256-hash>",      "prefix": "strac_pat_a1b2",      "label": "CI deploy bot",      "scope": "compliance:read documents:write",      "expiresAt": 1787616000,      "email": "ci@example.com",      "createdAt": "2026-05-27T08:18:14.536Z",      "createdBy": "ci@example.com",      "lastUsedAt": null,      "revokedAt": null,      "status": "active"    }  }}

The secret is shown once

The token field is returned exactly once at mint time and is unrecoverable afterward — the server keeps only its hash. Save it immediately (e.g. into your CI secret store).

Using a PAT

Send it as a bearer token against the v1 base URL. The companyId in the path is validated against the token's organization — cross-tenant requests are rejected.

bash
curl 'https://comply.strac.io/api/v1/companies/comp_demo/controls' \  -H 'Authorization: Bearer strac_pat_xxxxxxxxxxxx'

List and revoke

GET /api/companies/{companyId}/pats lists token metadata (prefix, label, scope, status, expiry, last-used), never the secret. POST /api/companies/{companyId}/pats/{patId}/revoke revokes immediately, with no grace period. An optional reason lands in the audit log.

OAuth 2.1 + PKCE (MCP)

The MCP server speaks OAuth 2.1 with PKCE. Access tokens are mcp_at_* (1-hour TTL); refresh tokens are mcp_rt_* and rotate on every use with a sliding 30-day window. You authenticate once and stay signed in for as long as the client is in use; only a full month of inactivity requires signing in again. Existing MCP clients (Claude Code, Cursor, the official SDK) run the whole flow automatically. You usually never touch the endpoints below.

Discovery handshake

A spec-compliant client needs no URLs hard-coded. It discovers them:

  1. Unauthenticated POST /mcp401 with a resource_metadata pointer.
  2. Fetch protected-resource metadata (RFC 9728) at /.well-known/oauth-protected-resource; read authorization_servers[0].
  3. Fetch authorization-server metadata (RFC 8414) for the authorize / token / registration endpoints.
  4. Dynamic-register (RFC 7591), open the browser at the authorize endpoint → consent on comply.strac.io → exchange the code for mcp_at_*.

Dynamic client registration

bash
curl -X POST 'https://mcp.comply.strac.io/oauth/register' \  -H 'Content-Type: application/json' \  -d '{    "client_name": "My MCP Client",    "redirect_uris": ["http://127.0.0.1:8765/callback"],    "grant_types": ["authorization_code", "refresh_token"],    "scope": "compliance:read"  }'

Redirect URI rules

https://* is universally allowed. http://127.0.0.1:* and http://[::1]:* are allowed for local dev. Other schemes (file:, app:) are rejected to defend against custom-scheme hijacks.

PAT or OAuth?

Scripting the REST API from CI, a cron, or a backend job → PAT. Connecting an interactive AI client (Claude Code, Cursor, Claude Desktop) → OAuth, handled by the client. See the MCP page for per-client connect snippets.