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.