Authentication

Get a key

Keys are created in the web app, under your session: Account → API keys → New key. Give it a name and choose its scopes. The full token is shown once; the database holds only a hash, so no screen, support flow or dump can show it again.

export SCRIPTRIP_KEY=sk_live_a1b2c3_9tJqK4mWv2ZpX7cRnB6yQeL1sD8hF0gA

A key is three parts joined by underscores: sk_live, a six-character prefix you will see in the key list, and a 32-character secret. The sk_live_ shape exists so a leaked credential is recognisable at a glance in a diff or a log.

Send it

curl -sS https://<host>/v1/runs?limit=1 -H "Authorization: Bearer $SCRIPTRIP_KEY"
{"data":[{"id":"0195c8e4-8d02-7c19-b3e7-5a1f9d20c68b","status":"done","…":"…"}],"has_more":true,"next_cursor":"eyJjIjoi…"}

No header, a revoked key or an unknown token is 401 unauthenticated with an empty detail — on purpose: distinguishing no such key from revoked would be an oracle.

Scopes

A key carries a subset of five scopes; each route declares the scope it needs. A missing scope is 403 forbidden — never 404 — and detail.required_scope names the one you need.

| Scope | Permits | |---|---| | runs:read | GET /v1/runs, GET /v1/runs/{id}, GET /v1/runs/{id}/transcript | | runs:write | POST /v1/runs, POST /v1/runs/{id}/cancel, DELETE /v1/runs/{id} | | uploads:write | Every /v1/uploads endpoint, including the read — a client that writes parts can always read what landed | | destinations:read | GET /v1/destinations, GET /v1/destinations/{id}/deliveries | | destinations:write | POST/PATCH/DELETE /v1/destinations, POST /v1/destinations/{id}/test, and — together with runs:readPOST /v1/runs/{id}/deliver |

The default for a new key is runs:read and runs:write. There is no admin scope and there will not be one: a bearer key can never reach another account's data.

curl -sS -X POST https://<host>/v1/destinations -H "Authorization: Bearer $SCRIPTRIP_KEY" \
  -H "Content-Type: application/json" -d '{"name":"x","url":"https://example.org/hook"}'
{"error":{"code":"forbidden","message":"This key does not have the destinations:write scope.","detail":{"required_scope":"destinations:write","scopes":["runs:read","runs:write"]},"request_id":"req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"}}

GET /v1/health needs no key and no scope.

Two things a key never does

It never becomes a session. The web app authenticates with a cookie; the API authenticates with a key; neither can escalate into the other. If a request carries both, the key wins and the cookie is ignored, so pasting a cURL command into a logged-in browser cannot execute it with more authority than the key has.

It never reaches the admin surface. No scope, header, body field or query parameter makes a bearer request an admin request.

When it leaks

Revoke it in the account screen. Revocation takes effect on the next request — there is no cache. Then create a new one. To rotate without an outage, do it in that order reversed: create the new key first, deploy it, then revoke the old one. Two keys can be live at once for exactly this.

Do not commit a key, log it, or put it in a URL. Our side never writes it to a log, an analytics event or an error report; the only place it exists after creation is your environment.