Browse documentationDevice flow

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
POST/v1/feedback

Submit 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
POST/v1/auth/email/start

Email 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
POST/v1/auth/email/verify

Exchange 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
POST/v1/device/code

Begin 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
POST/v1/device/token

Poll 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