Appearance
Start a device authorization
POST /v1/public/device/code
- Operation ID:
createDeviceCode - Tags: Device authorization
- Base URL:
http://localhost:8787
RFC 8628 style. The device calls this unauthenticated, shows user_code / verification_uri to the user, then polls POST /v1/public/device/token with device_code. The user approves at the portal (POST /v1/me/device/approve). Codes live 900 seconds. Limited to 3 requests per minute per client IP (429 rate_limited). An invalid body returns 400 { "error": "invalid_request" }.
Workflow: Integration guide.
Authentication: none (public).
Rate limit: 3 requests per 60s per ip.
Authentication
- Auth: none (public endpoint, no token required).
Rate limit
3 requests per 60 seconds per ip. Exceeding it returns 429.
Request body
Required: yes.
Content type: application/json
Schema: object
| Field | Type | Required | Description |
|---|---|---|---|
user_data | object | no | - |
client_name | string | no | Max length: 200 |
Example
json
{
"client_name": "Example CLI"
}Responses
200
Device and user codes issued.
- Header
x-request-id: Request identifier; quote it when reporting an error. Content type:application/json
Schema: object (DeviceCodeResponse)
| Field | Type | Required | Description |
|---|---|---|---|
device_code | string | yes | Secret polled by the device; 64 hex chars. Never shown to the user. |
user_code | string | yes | Short code the user types at verification_uri, formatted XXXX-XXXX. |
verification_uri | string<uri> | yes | - |
verification_uri_complete | string<uri> | yes | verification_uri with the user code prefilled. |
expires_in | integer | yes | Seconds until the code expires (900). |
interval | integer | yes | Minimum seconds between token polls (5). |
Errors
400
Malformed body (OAuth-style error, not the standard error body).
- Header
x-request-id: Request identifier; quote it when reporting an error. Content type:application/json
Schema: object (DeviceOAuthError)
| Field | Type | Required | Description |
|---|---|---|---|
error | string | yes | RFC 8628 style error string.. One of: "invalid_request", "unsupported_grant_type", "authorization_pending", "slow_down", "expired_token", "access_denied", "invalid_grant", "server_error" |
interval | integer | no | Current polling interval in seconds; set for authorization_pending and slow_down. |
429
Rate limit exceeded (rate_limited).
- Header
x-request-id: Request identifier; quote it when reporting an error. Content type:application/json
Schema: object (ErrorBody)
| Field | Type | Required | Description |
|---|---|---|---|
error | string | yes | Stable machine-readable error code, e.g. forbidden, not_found. |
details | any | no | Optional structured context. For 403 it is { perm }. |
Error response
json
{
"error": "rate_limited"
}500
Unexpected server error (internal). No details are returned.
- Header
x-request-id: Request identifier; quote it when reporting an error. Content type:application/json
Schema: object (ErrorBody), fields as described above.
Error response
json
{
"error": "internal"
}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)
| Field | Type | Required | Description |
|---|---|---|---|
error | string | yes | One of: "panic_mode" |
level | string | yes | One of: "degraded", "lockdown" |
reason | string | null | yes | - |
Maintenance lockdown
json
{
"error": "panic_mode",
"level": "lockdown",
"reason": "Scheduled maintenance"
}Examples
curl
bash
curl -X POST 'http://localhost:8787/v1/public/device/code' \
-H 'Content-Type: application/json' \
--data '{"client_name":"Example CLI"}'TypeScript
ts
const response = await fetch("http://localhost:8787/v1/public/device/code", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"client_name": "Example CLI"
}),
});
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();