SyndaiSign inStart free

Authentication

API keys, the browser login the CLI and MCP use, scopes and presets, actions only a person can take, rate limits and key rotation.

Every call to the Syndai coding API uses an API key. A key belongs to one workspace. Its scopes say what it may do.

Send the key

Put the key in the Authorization header as a bearer token, on every request:

curl -s https://api.syndai.ai/api/v1/external/coding/repos \
  -H "Authorization: Bearer $SYNDAI_API_KEY"

There is no query-string or body form. Keys start with sk_syndai_live_. A missing, wrong or revoked key gets 401 with code INVALID_TOKEN.

Get a key

There are two ways.

  • In the app. Open Settings, then Developer, then API keys, and click Create key. Name it and pick one access preset. The key is shown once. Syndai keeps only a hash, so a lost key cannot be shown again. Revoke it and make a new one.
  • From a browser login. syndai login and syndai mcp install never ask you to paste a key. A person approves them in the browser, and the key is made then. See Browser login.

GET /api/v1/external/coding/me describes the key you call with: customer_id, key_id, key_name, scopes and expires_at. DELETE /api/v1/external/coding/me revokes it. That is what syndai logout does.

Scopes

A key can only do what its scopes allow. Give each key the fewest it needs. You cannot add scopes to a key later. Make a new key.

scopeallows
coding:repos:readlist the workspace's projects
coding:runs:createstart runs (POST /runs)
coding:runs:readread runs, their events and timelines
coding:runs:respondanswer a run's questions
coding:runs:cancelstop or cancel runs
coding:runs:ratifyconfirm a run's acceptance criteria
coding:criteria:editedit a drafted criterion before it is confirmed
coding:spec:request-changesmark up a drafted run spec and ask for a revision
coding:webhooks:readlist webhook endpoints and deliveries
coding:webhooks:writecreate, disable, test and rotate webhook endpoints
coding:webhooks:redeliversend a delivery again
coding:schedules:managecreate and manage schedules that repeat a run
coding:members:readlist members and invites
coding:members:writeinvite or remove members
coding:policy:readread workspace and project policy
coding:policy:writechange workspace and project policy
coding:apps:readread a project's published app
coding:apps:publishpublish an app and edit a live site's text
coding:apps:unpublishtake a published app offline
coding:secrets:readlist environments and secret names, never a value
coding:secrets:writeset, import and delete secrets
coding:tools:readlist custom skills and MCP servers
coding:tools:writeadd, edit, rescan and delete custom skills and MCP servers
coding:secrets:revealread secret values (see Secret values)

Your plan sets which scopes your workspace may use. A key cannot hold a scope the plan leaves out. Each call is checked against the plan at that moment too. So if your plan drops a scope, keys that list it lose that access.

When a call is refused:

  • the key lacks the scope: 403 INVALID_SCOPE;
  • the plan lacks the scope: 403 PLAN_UPGRADE_REQUIRED.

Presets

The app offers presets, so you pick a job and not a list. A preset only grants the scopes your plan allows.

presetscopes
Run lifecyclecoding:repos:read, coding:runs:create, coding:runs:read, coding:runs:respond, coding:runs:cancel, coding:runs:ratify, coding:criteria:edit
Webhook deliverycoding:webhooks:read, coding:webhooks:redeliver
Webhook adminthe two above, plus coding:webhooks:write
Policy admincoding:policy:read, coding:policy:write
App publishingcoding:apps:read, coding:apps:publish, coding:apps:unpublish
Connections (read-only)coding:secrets:read, coding:tools:read
Connections admincoding:secrets:read, coding:secrets:write, coding:tools:read, coding:tools:write
Team admincoding:members:read, coding:members:write
Owner CLI and MCPcoding:repos:read, coding:runs:create, coding:runs:read, coding:runs:respond, coding:runs:cancel, coding:apps:read, coding:apps:publish, coding:policy:read, coding:policy:write, coding:schedules:manage
Full entitled accessevery scope in the table above except coding:secrets:reveal

Owner CLI and MCP is the set a browser login grants.

Browser login

The CLI and syndai mcp install log in with a device code (RFC 8628), like gh auth login.

  1. The client calls POST /api/v1/cli/device. It needs no key and allows 10 calls a minute per IP. The answer has a device_code, a short user_code, interval, expires_in and verification_uri_complete (https://app.syndai.ai/cli?code=…). The client may send scopes to ask for more than the owner set.
  2. An owner or admin of the workspace opens that page. It shows the code, the IP address and time Syndai saw, and the client and computer names. The client wrote those names itself, so they do not prove who sent it. Approving picks the workspace. A member or viewer gets 403 ROLE_REQUIRED.
  3. The client polls POST /api/v1/cli/device/token with {"device_code": "…"} every interval seconds (5). Until there is a decision, it gets 400 with {"error": "authorization_pending"}. Polling too fast gets slow_down, so wait 5 seconds longer. A denial is access_denied. A code that expired after 10 minutes, or was already used, is expired_token.
  4. Once approved, the poll returns api_key, customer_id, key_id and expires_at, just once. The key lasts 90 days. It is named after the client and the computer, such as CLI · laptop.

A browser login never grants coding:members:read, coding:members:write or coding:apps:unpublish. Each MCP client gets a key of its own, so you can revoke one without the others.

Secret values

coding:secrets:reveal is in no preset. No key made in the app can hold it. Only a browser login that a person approves can grant it, and the page says so.

POST /api/v1/external/coding/secrets/resolve returns one environment's values, merged the way a run sees them. Each value it returns writes one SECRET_READ audit row that names the key. The response has Cache-Control: no-store.

A production environment needs one more step. A person must approve this key in the browser in the last 10 minutes:

  1. The key calls POST /api/v1/external/coding/cli/production-access. It needs coding:secrets:reveal. The answer has a device_code and verification_uri_complete.
  2. An owner or admin of the key's workspace opens the page and allows it.
  3. The key polls POST /api/v1/external/coding/cli/production-access/token with {"device_code": "…"}. On approval it gets {"allowed_until": "…"}. Waiting, denied and expired codes use the same errors as the browser login.
  4. The resolve call sends that device_code as production_grant.

Without a fresh grant, resolve answers 403 PRODUCTION_STEP_UP_REQUIRED and returns no values. The CLI keeps that code in memory and never writes it to disk. So another holder of the same key has nothing to send.

Actions only a person can take

A key is access you hand to a tool. The CLI, an MCP client and CI all hold one, and so can any agent that reads the file it is stored in. Some actions need a person signed in at app.syndai.ai:

  • landing a held change anyway (merge anyway, or go live anyway);
  • making a project's protected files looser, such as removing a pattern or adding a ! exemption;
  • editing a protected file's text.

A key that tries one gets 403 HUMAN_APPROVAL_REQUIRED with a review_url. That is the page in the app where a person decides. Nothing changes.

Keys can still retry or close a held change, put a change live that is not held, and add protected-file patterns. Buying credits and managing keys stay in the app.

Why: an owner's agent reads text that other people wrote, such as members' requests and run output. A click in the browser is the one thing that agent does not hold.

Rate limits

Each workspace can make 600 keyed calls a minute, across all its keys. More keys do not raise the limit. Each route also has its own cap. A few of them:

routeper minute
POST /runs120
GET /runs/{id}240
GET /runs/{id}/events60
POST /runs/{id}/responses120
POST /webhooks30

Whichever limit you hit first applies. A limited call gets 429 with code RATE_LIMITED and a Retry-After header in seconds. These limits are separate from your plan's credits and run concurrency. See Errors for how to retry.

Rotate a key

Rotate keys on a schedule, and at once if one may have leaked.

  1. Make a new key with the same preset.
  2. Put the new key in your deployment.
  3. Check that your calls work.
  4. Revoke the old key in the app.

Revoking takes effect at once. Calls with the old key then get 401 INVALID_TOKEN. A key nobody uses for a year is revoked by Syndai, and the workspace owners are told.

Keep keys out of source code. Use environment variables or a secrets manager.