scribasedocs

Control API /v1

Device login (OAuth device flow)

A CLI or coding agent that has no browser of its own gets a token without anyone pasting a secret: it asks for a code, a person approves it in the console, and the agent's next poll receives a scoped, expiring agent token (scb_agt_…). This is the OAuth 2.0 Device Authorization Grant (RFC 8628). scribase login, npx scribase login and npx scribase init implement the client side; see Why you never need to paste a secret key.

Text
agent                          control API                     person (console)
  | POST /v1/oauth/device/code  |                                     |
  |---------------------------->| device_code, user_code, URL         |
  |<----------------------------|                                     |
  | shows URL + SCRB-7F3K-Q2MX  |                                     |
  |                             |  /device?user_code=... (signed in)  |
  | POST /v1/oauth/token (poll) |<------------------------------------|
  | authorization_pending       |  lookup, then approve or deny       |
  | ...                         |                                     |
  | POST /v1/oauth/token        |                                     |
  | 200 access_token=scb_agt_…  |                                     |

POST /v1/oauth/device/code

Anonymous. The body is application/x-www-form-urlencoded (as RFC 8628 specifies) or JSON.

Field Notes
client_id Optional. Identifies the client, e.g. scribase-cli
client_name Optional. Shown to the person approving, e.g. Claude Code on laptop (1 to 80 printable characters)
scope Optional, space-separated. See below. Default project:write sql:write migrations:write functions:deploy branches:write

Requested scopes: org:read, project:read, project:write, project:admin (the person picks the project), project:<id>:read|write|admin (a specific project), sql:read, sql:write, migrations:write, functions:deploy, branches:write, keys:secret. The person can grant less than was requested, never more.

Terminal
curl -X POST "$SCRIBASE_API_URL/v1/oauth/device/code" \
  -d client_id=scribase-cli \
  --data-urlencode 'client_name=Claude Code' \
  --data-urlencode 'scope=project:write sql:write migrations:write'

Response 200:

JSON
{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "SCRB-7F3K-Q2MX",
  "verification_uri": "https://console.scribase.com/device",
  "verification_uri_complete": "https://console.scribase.com/device?user_code=SCRB-7F3K-Q2MX",
  "expires_in": 600,
  "interval": 5
}

User codes use only unambiguous characters (no 0/O, 1/I/L), and the console accepts them with or without the SCRB- prefix, dashes or capitals. Keep device_code private to the polling process; show the person only the URL and user_code. Creation is rate limited per client address (429 with Retry-After).

POST /v1/oauth/token

Anonymous. Poll no faster than interval seconds.

Field Value
grant_type urn:ietf:params:oauth:grant-type:device_code
device_code From the step above
client_id Optional, the same as above

While waiting, and on failure, the answer is 400 with an OAuth error:

error Meaning What the client does
authorization_pending Nobody has approved yet Poll again after interval
slow_down Polled too fast Add 5 seconds to the interval (the response carries the new interval)
access_denied The person clicked Deny Stop
expired_token Ten minutes passed Start over
invalid_grant Unknown code, or its token was already issued Stop
unsupported_grant_type Wrong grant_type Fix the request

Once approved, the next poll answers 200, exactly once:

JSON
{
  "access_token": "scb_agt_1p-rw-sqlw-mig-x2609260412_3f9a...",
  "token_type": "Bearer",
  "expires_in": 28800,
  "scope": "project:web:write sql:write migrations:write",
  "organization_id": "acme",
  "project_id": "web",
  "token_id": "7d1e..."
}

The token is an ordinary agent token: bound to the organization, limited to the approved scopes, listed, audited and revocable under Account > Access tokens. A second poll with the same device_code gets invalid_grant.

Console routes

These back the console's /device page. They need a signed-in console session; agent tokens are refused.

POST /v1/oauth/device/lookup

{ "user_code": "SCRB-7F3K-Q2MX" } returns the pending request: client name, requested scopes, the address it came from, when it was made and when it expires. Lookups are rate limited per person, so codes cannot be guessed.

POST /v1/oauth/device/approve

Field Notes
user_code The code shown by the agent
organization_id You must be a member
project_id A project in that organization, or * for every project
level read, write or admin, at most what was requested
capabilities Subset of the requested sql:read, sql:write, migrations:write, functions:deploy, branches:write, keys:secret
ttl 1 hour to 30 days: 1h, 8h (default), 24h, 7d, 30d

POST /v1/oauth/device/deny

{ "user_code": "SCRB-7F3K-Q2MX" }. The agent's next poll gets access_denied.

Every approval and denial is recorded: who, which client, from which address, which scopes, and the id of the token that was issued.