Skip to content
Open Markdown

Poll for the device authorization result ​

POST /v1/public/device/token

  • Operation ID: exchangeDeviceCode
  • Tags: Device authorization
  • Base URL: http://localhost:8787

Poll with grant_type=urn:ietf:params:oauth:grant-type:device_code and the device_code. Wait at least interval seconds between polls (starts at 5). Polling faster returns 400 slow_down with a widened interval (+5s each time, max 60s). Other 400 errors: authorization_pending (user has not responded yet; keep polling at the returned interval), expired_token (stop and restart), access_denied (user denied or revoked), invalid_grant (unknown, already consumed or not yet claimable code), unsupported_grant_type, invalid_request (malformed body). On success the response is single use: pass token_hash to GoTrue to obtain a session. A per-IP guard of 30 polls per minute also answers 400 slow_down. 500 server_error means token issuance failed; the code is not consumed, so retry.

Workflow: Integration guide.

Authentication: none (public).

Authentication ​

  • Auth: none (public endpoint, no token required).

Request body ​

Required: yes.

Content type: application/json

Schema: object

FieldTypeRequiredDescription
grant_typestringyes-
device_codestringyesMin length: 1

Responses ​

200 ​

Approved; one-time login token.

  • Header x-request-id: Request identifier; quote it when reporting an error. Content type: application/json

Schema: object (DeviceTokenResponse)

FieldTypeRequiredDescription
token_hashstringyesGoTrue magic-link hashed token; exchange it with GoTrue verifyOtp (type: magiclink).
typestringyesOne of: "magiclink"
expires_inintegeryesSeconds the token_hash stays valid (60).

Errors ​

400 ​

OAuth-style error (see description).

  • Header x-request-id: Request identifier; quote it when reporting an error. Content type: application/json

Schema: object (DeviceOAuthError)

FieldTypeRequiredDescription
errorstringyesRFC 8628 style error string.. One of: "invalid_request", "unsupported_grant_type", "authorization_pending", "slow_down", "expired_token", "access_denied", "invalid_grant", "server_error"
intervalintegernoCurrent polling interval in seconds; set for authorization_pending and slow_down.

Example

json
{
  "error": "authorization_pending",
  "interval": 5
}

500 ​

Token issuance failed (server_error).

  • Header x-request-id: Request identifier; quote it when reporting an error. Content type: application/json

Schema: object (DeviceOAuthError), fields as described above.

503 ​

Maintenance can refuse this write before authentication (degraded blocks session start and checkout; lockdown blocks other public/user writes).

  • Header x-request-id: Request identifier; quote it when reporting an error.
  • Header Retry-After: Maintenance retry delay in seconds; present for panic_mode. Example: 30. Content type: application/json

Schema: object (MaintenanceErrorBody)

FieldTypeRequiredDescription
errorstringyesOne of: "panic_mode"
levelstringyesOne of: "degraded", "lockdown"
reasonstring | nullyes-

Maintenance lockdown

json
{
  "error": "panic_mode",
  "level": "lockdown",
  "reason": "Scheduled maintenance"
}

Examples ​

curl ​

bash
curl -X POST 'http://localhost:8787/v1/public/device/token' \
  -H 'Content-Type: application/json' \
  --data '{"grant_type":"example","device_code":"example"}'

TypeScript ​

ts
const response = await fetch("http://localhost:8787/v1/public/device/token", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "grant_type": "example",
    "device_code": "example"
  }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const data = response.status === 204 || response.status === 205
  ? undefined
  : response.headers.get("content-type")?.includes("json")
    ? await response.json()
    : await response.text();

Try this endpoint

Requests go directly from your browser to the API. Send request executes this endpoint against the selected server.

POST/v1/public/device/token

Your token stays in this tab's memory and is sent only when you press Send request.

Request body (required)

Built for developers. Ready for agents.