# Devices

> The Devices operations of the Kabu API: person-owned devices and machine device-code authorization.

Person-owned devices and machine device-code authorization.

Generated from the contract, version `2.0.0-draft.2` ([OpenAPI](/openapi.yaml)). Base URL `https://usekabu.com/api`. Errors are problem details, each [code](/docs/errors) with its own page.

## `GET /v2/devices` · Devices list

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `self`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `limit` | query | integer, from 1 to 100 | no |  |
| `after` | query | string | no | Opaque cursor from next_cursor. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [DeviceList](#schema-DeviceList) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/devices" \
  -H "Authorization: Bearer $KABU_USER_TOKEN"
```

## `PUT /v2/devices` · Devices register

Atomic compare-and-rebind on the hashed installation with expected_binding_version; first registration requires 0. Possession proof is additionally required for a cross-user rebind. Every success increments binding_version. Lost success replays with the same Idempotency-Key; stale_device_binding is non-retryable, do not fetch/overwrite a newer binding.

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `self`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |

**Request body**: [DeviceRegister](#schema-DeviceRegister).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Device](#schema-Device) |
| `409` | Installation proof conflict or non-retryable stale binding revision | [DeviceBindingConflict](#schema-DeviceBindingConflict) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X PUT "https://usekabu.com/api/v2/devices" \
  -H "Authorization: Bearer $KABU_USER_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"installation_id":"…","expected_binding_version":0,"token":"…","platform":"ios","bundle_id":"…","environment":"sandbox","supported_ask_types":[]}'
```

## `DELETE /v2/devices/{device_id}` · Devices remove

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `self`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `device_id` | path | string | yes | A device owned by this user. |

| Status | Means | Body |
|---|---|---|
| `204` | Done |  |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X DELETE "https://usekabu.com/api/v2/devices/$DEVICE_ID" \
  -H "Authorization: Bearer $KABU_USER_TOKEN"
```

## `PUT /v2/devices/{device_id}/live-activities/{session_id}` · Devices liveactivities register

Register/rotate a per-activity token for a visible session; user must own the device and be in the session audience.

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `self`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `device_id` | path | string | yes | A device owned by this user. |
| `session_id` | path | string | yes | A `ses_…` id. |

**Request body**: [LiveActivityRegister](#schema-LiveActivityRegister).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Device](#schema-Device) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X PUT "https://usekabu.com/api/v2/devices/$DEVICE_ID/live-activities/$SESSION_ID" \
  -H "Authorization: Bearer $KABU_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"token":"…"}'
```

## `DELETE /v2/devices/{device_id}/live-activities/{session_id}` · Devices liveactivities remove

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `self`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `device_id` | path | string | yes | A device owned by this user. |
| `session_id` | path | string | yes | A `ses_…` id. |

| Status | Means | Body |
|---|---|---|
| `204` | Done |  |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X DELETE "https://usekabu.com/api/v2/devices/$DEVICE_ID/live-activities/$SESSION_ID" \
  -H "Authorization: Bearer $KABU_USER_TOKEN"
```

## `POST /v2/device_authorizations` · Start machine device-code login

- **Auth:** no credentials.

**Request body**: [DeviceAuthorizationCreate](#schema-DeviceAuthorizationCreate).

| Status | Means | Body |
|---|---|---|
| `201` | OK | [DeviceAuthorization](#schema-DeviceAuthorization) |
| `429` | A rate, concurrency or quota limit was reached | [Problem](/docs/api/discovery#schema-Problem) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/device_authorizations" \
  -H "Content-Type: application/json" \
  -d '{"name":"…"}'
```

## `POST /v2/device_authorizations/poll` · Poll machine login result

RFC 8628 token endpoint: form-encoded device_code and grant_type. Before approval: 400 authorization_pending; too-fast polling: 400 slow_down (+5 s interval permanently); denial: 400 access_denied; expiry: 400 expired_token. Approval yields 200 access_token/token_type/scope. Terminal denial/expiry never yields a token.

- **Auth:** no credentials.

**Request body**: `application/x-www-form-urlencoded`: [DeviceAuthorizationPoll](#schema-DeviceAuthorizationPoll).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [DeviceAuthorizationToken](#schema-DeviceAuthorizationToken) |
| `400` | RFC 8628 polling error (pending, slow down, denial or expiry) | [DeviceAuthorizationError](#schema-DeviceAuthorizationError) |
| `429` | A rate, concurrency or quota limit was reached | [Problem](/docs/api/discovery#schema-Problem) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/device_authorizations/poll"
```

## `POST /v2/device_authorizations/approve` · Deviceauthorizations approve

Owner/admin only; atomically move pending to approved and mint an agent key for the machine in the selected org. Denied/expired codes cannot be approved (409 device_authorization_settled); never expose its secret to the human.

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `admin`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: [DeviceAuthorizationApprove](#schema-DeviceAuthorizationApprove).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [DeviceAuthorizationConsent](#schema-DeviceAuthorizationConsent) |
| `429` | A rate, concurrency or quota limit was reached | [Problem](/docs/api/discovery#schema-Problem) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/device_authorizations/approve" \
  -H "Authorization: Bearer $KABU_USER_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"user_code":"…"}'
```

## `GET /v2/device_authorizations/lookup` · Deviceauthorizations lookup

Owner/admin lookup before consent. Returns only computer name, requested access, expiry and state; never the machine polling capability. Unknown code is 404.

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `admin`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `user_code` | query | string | yes |  |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [DeviceAuthorizationConsent](#schema-DeviceAuthorizationConsent) |
| `429` | A rate, concurrency or quota limit was reached | [Problem](/docs/api/discovery#schema-Problem) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/device_authorizations/lookup?user_code=%E2%80%A6" \
  -H "Authorization: Bearer $KABU_USER_TOKEN"
```

## `POST /v2/device_authorizations/deny` · Deviceauthorizations deny

Owner/admin only. Atomically transition pending to denied. Approved/expired codes cannot be denied (409 device_authorization_settled). A denied code polls as access_denied, never a token.

- **Kabu apps only:** a signed-in human's token. Integrations use API keys.
- **Signed-in humans:** `admin`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: [DeviceAuthorizationDeny](#schema-DeviceAuthorizationDeny).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [DeviceAuthorizationConsent](#schema-DeviceAuthorizationConsent) |
| `429` | A rate, concurrency or quota limit was reached | [Problem](/docs/api/discovery#schema-Problem) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/device_authorizations/deny" \
  -H "Authorization: Bearer $KABU_USER_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"user_code":"…"}'
```

## Schemas

### DeviceList

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"list"` | yes |  |
| `data` | array of [Device](#schema-Device) | yes |  |
| `has_more` | boolean | yes |  |
| `next_cursor` | string or null | yes |  |

### Device

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"device"` | yes |  |
| `id` | string | yes |  |
| `platform` | string | yes |  |
| `bundle_id` | string | yes |  |
| `environment` | string | yes |  |
| `supported_ask_types` | array of string | yes |  |
| `live_activities` | boolean | yes |  |
| `created_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
| `binding_version` | integer, ≥ 1 | yes | Server-issued installation binding revision, strictly increases on every successful conditional registration, rotation or rebind. |

### DeviceRegister

User comes only from the token. Installation id is stored hashed. Moving a token between users needs the same installation proof (else 409). Bundle/environment allowlisted. Retire v1 only by authenticated legacy device id or same user + token + Kabu bundle, never token alone.

| Property | Type | Required | Description |
|---|---|---|---|
| `installation_id` | string, 32 to 256 characters | yes |  |
| `expected_binding_version` | integer, ≥ 0 | yes | 0 only for a new installation; otherwise the last server-issued binding_version. A stale version is 409 stale_device_binding, retryable=false. Never refresh/retry a stale request automatically. |
| `token` | string, 1 to 4096 characters | yes |  |
| `platform` | string, one of `"ios"` | yes |  |
| `bundle_id` | string | yes |  |
| `environment` | string, one of `"sandbox"`, `"production"` | yes |  |
| `supported_ask_types` | array of string, at most 32 items | yes | The ask types and push capabilities this build reads (open list; unknown values are ignored). Push capabilities: hitl_choice_v1 (KABU_ASK_CHOICE with option labels), hitl_choice_numbered_v1 (a 2- or 3-option choice as KABU_ASK_CHOICE_2/_3 with an "Option N: label" legend in the body; x-push-categories), hitl_sound_v1 (aps.sound), hitl_person_v1 (agent_key, agent_avatar, agent_color), hitl_brand_v1 (project, agent, icon, brand_logo_url, brand_accent), presence_la_v1 (a session's own Live Activity start and update carry content-state `presence` and aps `stale-date`, the lease's expires_at; x-push-payload-schemas). |
| `live_activity_start_token` | string or null | no |  |
| `legacy_device_id` | string | no |  |

Unknown properties are refused.

### DeviceBindingConflict

A binding conflict; stale_device_binding is terminal and cannot be retried automatically.

Type: [Problem](/docs/api/discovery#schema-Problem).

### LiveActivityRegister

| Property | Type | Required | Description |
|---|---|---|---|
| `token` | string, 1 to 4096 characters | yes |  |

Unknown properties are refused.

### DeviceAuthorizationCreate

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | string, 1 to 80 characters | yes |  |

Unknown properties are refused.

### DeviceAuthorization

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"device_authorization"` | yes |  |
| `device_code` | string | yes |  |
| `user_code` | string | yes |  |
| `verification_uri` | string | yes |  |
| `verification_uri_complete` | string | yes |  |
| `expires_in` | integer | yes |  |
| `interval` | integer | yes |  |

### DeviceAuthorizationPoll

| Property | Type | Required | Description |
|---|---|---|---|
| `device_code` | string, 1 to 256 characters | yes |  |
| `grant_type` | `"urn:ietf:params:oauth:grant-type:device_code"` | yes |  |

Unknown properties are refused.

### DeviceAuthorizationToken

RFC 8628 token response. Bearer agent key, machine-only; no human response returns this object. Sealed durable replay survives restart.

| Property | Type | Required | Description |
|---|---|---|---|
| `access_token` | string | yes |  |
| `token_type` | string | yes |  |
| `scope` | string | yes |  |
| `org_name` | string | no | Workspace display name for Connected to <org> / <project>. Optional for compatibility with older sealed token replays. |
| `project` | string | no | Connected project slug for installer confirmation. Optional for compatibility with older sealed token replays. |

### DeviceAuthorizationError

HTTP 400 application/json OAuth token error, not a Problem. Poll no faster than interval; slow_down permanently adds 5 seconds to the interval. access_denied and expired_token are terminal.

| Property | Type | Required | Description |
|---|---|---|---|
| `error` | string | yes | RFC 8628: authorization_pending, slow_down, access_denied, expired_token; other OAuth errors may be added. |
| `error_description` | string | no |  |

### DeviceAuthorizationApprove

| Property | Type | Required | Description |
|---|---|---|---|
| `user_code` | string, 1 to 64 characters | yes |  |
| `project` | [ProjectRef](/docs/api/messages#schema-ProjectRef) | no | A project slug (auto-created on first use) or `prj_…` id. Omit for `default`. A slug that normalises (lowercase, letters and digits only) to an existing project's key names that project, so `My App`, `my-app` and `my_app` are one project. Automatic creation stops at 1,000 live projects per org and mode with `429 quota_exceeded`; existing projects stay usable. Nothing remembers a deleted project (projects.delete): the next message that names its slug creates a fresh one. |

Unknown properties are refused.

### DeviceAuthorizationConsent

Secret-free human consent descriptor. Never contains device_code, an access token or a key secret. Requested access is the server's agent preset; the human selects the org/project on approval.

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"device_authorization_consent"` | yes |  |
| `user_code` | string | yes |  |
| `computer_name` | string | yes |  |
| `requested_access` | array of string | yes |  |
| `requested_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
| `expires_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
| `status` | string | yes | pending, approved, denied or expired (open). |

### DeviceAuthorizationDeny

| Property | Type | Required | Description |
|---|---|---|---|
| `user_code` | string, 1 to 64 characters | yes |  |

Unknown properties are refused.
