Authorization
Approval happens where the user is signed in
Device flow lets a command-line tool request authority, show the user a short verification code, and poll for the result while the user approves or denies the request in a trusted browser. The tool never receives the user's web credentials.
Keep the request narrow
An ordinary interactive tool requests only the capabilities it needs; it does not choose a
workspace. The signed-in browser shows the workspaces where the user may grant those capabilities,
and approval binds one of them. When exactly one workspace is eligible it is selected
automatically; otherwise the user chooses. The issued key contains the resulting ws: scopes, so
the tool learns its workspace boundary from the approval result instead of predicting it.
Callers that are already bound to one workspace may use the explicit ws: scope form. That form
asks the browser to confirm a preselected boundary rather than presenting a workspace picker. In
either form, show the requested authority next to the verification code so the user can compare the
terminal request with the approval screen.
Sync devices use the same flow
duetfs login --sync requests a sync-device grant through the same code-and-poll
sequence. The scopes are the user-wide pair user:files:read and user:files:write instead of a
workspace pick, and the request carries a device name — asserted by the requesting tool, not
verified — so the approval screen and the web device list can say which machine is asking. The
browser approval is the sole gate on issuing this third, user-wide request form, which is why the
screen names the breadth plainly: file read and write on all your workspaces from that named device,
never agent execution or administration. A tool asking for a sync grant that is not a sync tool
deserves a denial.
The issued credential is the device identity from then on — a new approval mints a new device. Approve the flow once per machine rather than copying a key between machines, so that per-device revocation and per-device activity attribution stay meaningful.
Poll politely
Treat pending, approved, denied, and expired as distinct states. Honor the declared polling interval and rate-limit response, stop on denial or expiry, and store an issued key using the operating system's credential storage.
Codes are temporary
Verification codes are short-lived correlation values, not credentials. Do not log the resulting API key, echo it back to a browser, or reuse a code after it reaches a terminal state.
Device
5 operations /v1 /feedbackSubmit anonymous product feedback; accepted asynchronously and rate limited.
- Scope
public- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "content": { "type": "string", "minLength": 1 }, "source": { "type": "string", "maxLength": 128 } }, "required": [ "content" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": {}, "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /auth /email /startEmail a sign-in code to a human so an agent holding no credential can finish sign-in on their behalf.
- Scope
public- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "email": { "type": "string", "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" }, "client_name": { "type": "string", "minLength": 1, "maxLength": 64 } }, "required": [ "email" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "request_id": { "type": "string", "pattern": "^req_[a-f0-9]{32}$" }, "expires_in": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "request_id", "expires_in" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /auth /email /verifyExchange the emailed code for a workspace-scoped API key, registering the human if they are new.
- Scope
public- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "request_id": { "type": "string", "pattern": "^req_[a-f0-9]{32}$" }, "code": { "type": "string", "pattern": "^[A-Z2-9]{4}-[A-Z2-9]{4}$" }, "name": { "type": "string", "minLength": 1, "maxLength": 200 }, "workspace_name": { "type": "string", "minLength": 1, "maxLength": 100 }, "workspace_slug": { "type": "string" } }, "required": [ "request_id", "code" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "access_token": { "type": "string", "pattern": "^duet_sk_.*" }, "token_type": { "type": "string", "const": "bearer" }, "prefix": { "type": "string", "pattern": "^duet_sk_.*" }, "scopes": { "minItems": 1, "type": "array", "items": { "type": "string" } }, "workspace": { "type": "object", "properties": { "slug": { "type": "string" }, "name": { "type": "string" } }, "required": [ "slug", "name" ], "additionalProperties": false }, "created": { "type": "boolean" } }, "required": [ "access_token", "token_type", "prefix", "scopes", "workspace", "created" ], "additionalProperties": false }- Errors
Error body JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "oneOf": [ { "type": "object", "properties": { "error": { "type": "string", "const": "invalid_code" }, "attempts_remaining": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 } }, "required": [ "error", "attempts_remaining" ], "additionalProperties": false }, { "type": "object", "properties": { "error": { "type": "string", "const": "workspace_ambiguous" }, "workspaces": { "type": "array", "items": { "type": "object", "properties": { "slug": { "type": "string" }, "name": { "type": "string" }, "role": { "type": "string", "enum": [ "owner", "editor", "viewer" ] } }, "required": [ "slug", "name", "role" ], "additionalProperties": false } } }, "required": [ "error", "workspaces" ], "additionalProperties": false }, { "type": "object", "properties": { "error": { "type": "string", "const": "expired" } }, "required": [ "error" ], "additionalProperties": false }, { "type": "object", "properties": { "error": { "type": "string", "const": "forbidden" } }, "required": [ "error" ], "additionalProperties": false }, { "type": "object", "properties": { "error": { "type": "string", "const": "invalid_request" } }, "required": [ "error" ], "additionalProperties": false }, { "type": "object", "properties": { "error": { "type": "string", "const": "rate_limited" }, "retryAfterSeconds": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "error", "retryAfterSeconds" ], "additionalProperties": false } ] }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /device /codeBegin a device authorization grant with workspace scopes, sync-device scopes, or workspace-independent capabilities.
- Scope
public- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "scopes": { "minItems": 1, "type": "array", "items": { "type": "string" } }, "capabilities": { "minItems": 1, "type": "array", "items": { "type": "string", "enum": [ "files:read", "files:write", "memory", "sessions", "agents", "services", "integrations", "apps", "share", "publish", "members" ] } }, "device_name": { "type": "string", "minLength": 1, "maxLength": 64 } } }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "device_code": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "user_code": { "type": "string", "pattern": "^[A-Z2-9]{4}-[A-Z2-9]{4}$" }, "verification_uri": { "type": "string", "format": "uri" }, "expires_in": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "interval": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "device_code", "user_code", "verification_uri", "expires_in", "interval" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /device /tokenPoll a device grant and receive its API key once approved.
- Scope
public- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "device_code": { "type": "string", "pattern": "^[a-f0-9]{64}$" } }, "required": [ "device_code" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "oneOf": [ { "type": "object", "properties": { "status": { "type": "string", "const": "pending" }, "interval": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "status", "interval" ], "additionalProperties": false }, { "type": "object", "properties": { "status": { "type": "string", "const": "slow_down" }, "interval": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "status", "interval" ], "additionalProperties": false }, { "type": "object", "properties": { "status": { "type": "string", "const": "denied" } }, "required": [ "status" ], "additionalProperties": false }, { "type": "object", "properties": { "status": { "type": "string", "const": "expired" } }, "required": [ "status" ], "additionalProperties": false }, { "type": "object", "properties": { "status": { "type": "string", "const": "consumed" } }, "required": [ "status" ], "additionalProperties": false }, { "type": "object", "properties": { "status": { "type": "string", "const": "approved" }, "access_token": { "type": "string", "pattern": "^duet_sk_.*" }, "token_type": { "type": "string", "const": "bearer" }, "prefix": { "type": "string", "pattern": "^duet_sk_.*" }, "scopes": { "type": "array", "items": { "type": "string" } }, "workspace": { "type": "object", "properties": { "slug": { "type": "string" }, "name": { "type": "string" } }, "required": [ "slug", "name" ], "additionalProperties": false } }, "required": [ "status", "access_token", "token_type", "prefix", "scopes" ], "additionalProperties": false } ] }- Delivery
- Standard response
- Retry
- Not declared idempotent