---
title: "Devices – Superbot API"
canonical_url: "https://superbot.gg/developers/devices"
summary: "The devices paired to your account."
---

> Markdown of https://superbot.gg/developers/devices. Site index: /llms.txt.

# Devices

The devices paired to your account.

Server: `https://superbot.gg`; every path below starts at it. Errors: [https://superbot.gg/developers#errors](https://superbot.gg/developers#errors). Spec: [https://superbot.gg/docs/openapi.json](https://superbot.gg/docs/openapi.json).

## Operations

- [Run a job on an agent computer](https://superbot.gg/developers/devices/createAgentJob.md): `POST /v1/devices/{device_id}/jobs`
- [Read an agent computer job](https://superbot.gg/developers/devices/getAgentJob.md): `GET /v1/devices/{device_id}/jobs/{job_id}`
- [Stream an agent computer terminal](https://superbot.gg/developers/devices/streamAgentTerminal.md): `GET /v1/devices/{device_id}/terminals/{terminal_id}/stream`
- [Preview an agent computer code](https://superbot.gg/developers/devices/previewAgentPair.md): `GET /v1/pair/{user_code}`
- [Approve an agent computer code](https://superbot.gg/developers/devices/approveAgentPair.md): `POST /v1/pair/{user_code}/approve`
- [Decline an agent computer code](https://superbot.gg/developers/devices/denyAgentPair.md): `POST /v1/pair/{user_code}/deny`
- [List devices](https://superbot.gg/developers/devices/listDevices.md): `GET /v1/devices`
- [Get a device](https://superbot.gg/developers/devices/getDevice.md): `GET /v1/devices/{device_id}`
- [Rename a device](https://superbot.gg/developers/devices/updateDevice.md): `PATCH /v1/devices/{device_id}`
- [Unlink a device](https://superbot.gg/developers/devices/deleteDevice.md): `DELETE /v1/devices/{device_id}`

## Run a job on an agent computer

`POST /v1/devices/{device_id}/jobs`

Queue one device-shell job on a linked agent computer, by its id. First-party only: a session or a device token of your own Superbot app; agent computer tokens, MCP connectors, caller keys and the account API key are refused (403). An asleep agent computer answers `queued_asleep`; the job waits for its next poll up to 600 s.

- Operation id: `createAgentJob`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:write` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:write`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `device_id` | string | required | An agent computer (`agt_…`) on your account. |

### Request body

`application/json`, required.

- `kind` (AgentJobKind, required): The device-shell job kinds a first-party device may run on an agent computer. One of `bash`, `bash_write`, `bash_output`, `bash_kill`, `bash_resize`, `read`, `write`, `glob`, `grep`.
- `args` (object, required): The relay tool's own args, camelCase, per kind: bash {command, cwd?, timeoutMs?, runInBackground?, interactive?} (interactive: a real tty, answered with bash_write; open a terminal pane with {command: <login shell>, runInBackground: true, interactive: true}, whose result carries {type: 'terminal', terminalId}); bash_write {terminalId, input, newline?}; bash_output {terminalId, since?, wait?}; bash_kill {terminalId}; bash_resize {terminalId, cols, rows} (a terminal pane's resize of an interactive terminal's tty: integers, cols 20..500, rows 5..200; a non-interactive terminal answers not_a_tty); read {path, line?, limit?}; write {path, content, overwrite?}; glob {pattern, cwd?}; grep {pattern, path?, cwd?}. Unknown keys are refused.

### Response 201

Queued.

`application/json`: AgentJobEnqueueResponse.

- `job_id` (string, required)
- `state` (AgentJobState, required): `queued_asleep`: the agent computer has no open poll; the job waits for its next poll inside the 600 s TTL. `expired`: the TTL ran out before a result. One of `queued`, `queued_asleep`, `claimed`, `done`, `failed`, `expired`.
- `expires_at` (string, required): ISO 8601, 600 s after the enqueue.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X POST "https://superbot.gg/v1/devices/$DEVICE_ID/jobs" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY" \
  -H "content-type: application/json" \
  -d '{"kind":"bash","args":{}}'
```

```js
const deviceId = 'DEVICE_ID';
const res = await fetch(`https://superbot.gg/v1/devices/${deviceId}/jobs`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({"kind":"bash","args":{}}),
});
console.log(res.status, await res.json());
```

```python
import os
import requests

device_id = "DEVICE_ID"
res = requests.request(
    "POST",
    f"https://superbot.gg/v1/devices/{device_id}/jobs",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
        "content-type": "application/json",
    },
    json={"kind":"bash","args":{}},
)
print(res.status_code, res.json())
```

## Read an agent computer job

`GET /v1/devices/{device_id}/jobs/{job_id}`

The job and, once it is `done` or `failed`, its result. `wait` (seconds, at most 25) parks the read until the job settles. A job lost to a Superbot restart answers 410 `job_lost_in_restart` (`retryable: true`, `kind`): it may have run on the agent computer, so check before repeating anything that changes state, then send it again. An id nobody minted, another account's, or older than the 600 s job lifetime is 404 `job_not_found`.

- Operation id: `getAgentJob`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:read` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:read`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `device_id` | string | required | An agent computer (`agt_…`) on your account. |
| `job_id` | string | required | The job id the enqueue answered. |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `wait` | string | optional | Seconds to wait for the job to settle, 0-25. |

### Response 200

The job.

`application/json`: AgentJobView.

- `job_id` (string, required)
- `device_id` (string, required)
- `kind` (AgentJobKind, required): The device-shell job kinds a first-party device may run on an agent computer. One of `bash`, `bash_write`, `bash_output`, `bash_kill`, `bash_resize`, `read`, `write`, `glob`, `grep`.
- `state` (AgentJobState, required): `queued_asleep`: the agent computer has no open poll; the job waits for its next poll inside the 600 s TTL. `expired`: the TTL ran out before a result. One of `queued`, `queued_asleep`, `claimed`, `done`, `failed`, `expired`.
- `created_at` (string, required)
- `expires_at` (string, required)
- `result` (AgentJobResult): Present once the state is `done` or `failed`.
  - `ok` (boolean, required)
  - `content` (any)
  - `error` (object)
    - `code` (string, required)
    - `message` (string, required)

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 410 | `job_lost_in_restart` | Superbot restarted after the job was queued and its outcome was not kept (`job_lost_in_restart`, `retryable: true`, `kind`). Send the job again once a repeat is safe. |

### Examples

```sh
curl -sS -X GET "https://superbot.gg/v1/devices/$DEVICE_ID/jobs/$JOB_ID" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const deviceId = 'DEVICE_ID';
const jobId = 'JOB_ID';
const res = await fetch(`https://superbot.gg/v1/devices/${deviceId}/jobs/${jobId}`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

device_id = "DEVICE_ID"
job_id = "JOB_ID"
res = requests.request(
    "GET",
    f"https://superbot.gg/v1/devices/{device_id}/jobs/{job_id}",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```

## Stream an agent computer terminal

`GET /v1/devices/{device_id}/terminals/{terminal_id}/stream`

Server-sent events, every frame `event: terminal` with an AgentTerminalFrame as JSON: `output` (seq strictly increasing), then `exit` or `gone`, after which the stream closes. Resume with `since=<seq>` (or Last-Event-ID). Open a terminal with a `bash` job `{command: <login shell>, runInBackground: true, interactive: true}` (a real tty: prompts, sudo and TUIs work) and type into it with `bash_write`.

- Operation id: `streamAgentTerminal`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:read` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:read`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `device_id` | string | required | An agent computer (`agt_…`) on your account. |
| `terminal_id` | string | required | The terminal id a background `bash` job answered. |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `since` | string | optional | Resume after this output seq (a byte cursor). Absent: from the start of the buffer. |

### Response 200

The terminal stream.

`text/event-stream`: string.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |

### Examples

```sh
curl -sS -X GET "https://superbot.gg/v1/devices/$DEVICE_ID/terminals/$TERMINAL_ID/stream" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const deviceId = 'DEVICE_ID';
const terminalId = 'TERMINAL_ID';
const res = await fetch(`https://superbot.gg/v1/devices/${deviceId}/terminals/${terminalId}/stream`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

device_id = "DEVICE_ID"
terminal_id = "TERMINAL_ID"
res = requests.request(
    "GET",
    f"https://superbot.gg/v1/devices/{device_id}/terminals/{terminal_id}/stream",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```

## Preview an agent computer code

`GET /v1/pair/{user_code}`

The details of a pending code an agent computer showed. First-party only: your own Superbot app (a session or its device token).

- Operation id: `previewAgentPair`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: Any live caller key (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: none

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `user_code` | string | required | The code the agent computer shows (e.g. `ABCD-EFGH`). |

### Response 200

The pending code.

`application/json`: AgentPairPreview.

- `kind` (string, required): One of `agent_computer`.
- `label` (string, required)
- `agent_vendor` (string | null, required)
- `ip` (string | null, required)
- `geo` (string | null, required)
- `tier` (string, required): One of `link`.
- `approve_on` (string, required): One of `this_device`, `phone`.
- `asked_at` (string): When the agent computer asked for this code (ISO-8601 UTC). Absent on an older edge.
- `expires_at` (string): When the code lapses (ISO-8601 UTC). Absent on an older edge.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |

### Examples

```sh
curl -sS -X GET "https://superbot.gg/v1/pair/$USER_CODE" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const userCode = 'USER_CODE';
const res = await fetch(`https://superbot.gg/v1/pair/${userCode}`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

user_code = "USER_CODE"
res = requests.request(
    "GET",
    f"https://superbot.gg/v1/pair/{user_code}",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```

## Approve an agent computer code

`POST /v1/pair/{user_code}/approve`

Link the agent computer behind a pending code at the link tier (terminal and relay, no synced data). First-party only; a web session with a recent phone is told to finish on the phone (409 approve_on_phone).

- Operation id: `approveAgentPair`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: Any live caller key (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: none

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `user_code` | string | required | The code the agent computer shows (e.g. `ABCD-EFGH`). |

### Request body

`application/json`.

- `label` (string): A new label, 1-32 characters.

### Response 200

Linked.

`application/json`: AgentPairApproveResponse.

- `device_id` (string, required)
- `kind` (string, required): One of `agent_computer`.
- `tier` (string, required): One of `link`.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 409 | [`conflict`](https://superbot.gg/developers#errors-conflict) | The resource is not in a state that allows this change. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X POST "https://superbot.gg/v1/pair/$USER_CODE/approve" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY" \
  -H "content-type: application/json" \
  -d '{"label":"your-label"}'
```

```js
const userCode = 'USER_CODE';
const res = await fetch(`https://superbot.gg/v1/pair/${userCode}/approve`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({"label":"your-label"}),
});
console.log(res.status, await res.json());
```

```python
import os
import requests

user_code = "USER_CODE"
res = requests.request(
    "POST",
    f"https://superbot.gg/v1/pair/{user_code}/approve",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
        "content-type": "application/json",
    },
    json={"label":"your-label"},
)
print(res.status_code, res.json())
```

## Decline an agent computer code

`POST /v1/pair/{user_code}/deny`

Decline a pending code ("Not me"). The agent computer's next poll of its device code answers `access_denied` (RFC 8628 §3.5) and the code is dead. `reason` `not_me` says the user never asked for it: it is logged with the claimed vendor and the request network, and three distinct accounts reporting one network inside 24 hours count against that network's code limit. First-party only. 404 `not_found` for a code that is unknown, expired, approved or already declined.

- Operation id: `denyAgentPair`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: Any live caller key (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: none

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `user_code` | string | required | The code the agent computer shows (e.g. `ABCD-EFGH`). |

### Request body

`application/json`.

- `reason` (string): Why. Default `cancel`. One of `cancel`, `not_me`.

### Response 200

Declined.

`application/json`: AgentPairDenyResponse.

- `ok` (boolean, required): One of `true`.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X POST "https://superbot.gg/v1/pair/$USER_CODE/deny" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY" \
  -H "content-type: application/json" \
  -d '{"reason":"cancel"}'
```

```js
const userCode = 'USER_CODE';
const res = await fetch(`https://superbot.gg/v1/pair/${userCode}/deny`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({"reason":"cancel"}),
});
console.log(res.status, await res.json());
```

```python
import os
import requests

user_code = "USER_CODE"
res = requests.request(
    "POST",
    f"https://superbot.gg/v1/pair/{user_code}/deny",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
        "content-type": "application/json",
    },
    json={"reason":"cancel"},
)
print(res.status_code, res.json())
```

## List devices

`GET /v1/devices`

The devices paired to this account, oldest first, paged by device id. `online` filters on presence (seen within the last two minutes). Revoked devices are not listed.

- Operation id: `listDevices`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:read` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:read`

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | optional | Items per page, 1-100. |
| `after_id` | string | optional | Return the page after this resource id (the previous page’s `last_id`). |
| `before_id` | string | optional | Return the page before this resource id (the next page’s `first_id`). |
| `online` | string | optional | Only devices that are (`true`) or are not (`false`) online now. One of `true`, `false`. |

### Response 200

A page of devices.

`application/json`: DeviceList.

- `object` (string, required): One of `list`.
- `data` (array of Device, required)
  - `id` (string, required)
  - `object` (string, required): One of `device`.
  - `kind` (string, required)
  - `label` (string, required)
  - `fingerprint` (string, required)
  - `created_at` (string, required)
  - `last_seen_at` (string | null, required)
  - `online` (boolean, required)
  - `capabilities` (object)
    - `tools_count` (integer, required)
    - `hub_port` (integer)
    - `helper_version` (string)
    - `held_vendors` (array of string, required)
    - `held_models` (array of string, required)
  - `agent` (AgentDeviceFacet): Present only on a Device of kind `agent_computer`.
    - `agent_vendor` (string, required): The agent VM vendor, as it declared itself at link time. `generic` is any other agent. One of `grok-bot`, `muse`, `dots`, `manus`, `cursor-cloud`, `devin`, `replit`, `genspark`, `kimi`, `generic`.
    - `tier` (string, required): `link`: terminal and relay only. `data`: link plus the synced data the linking app granted. One of `link`, `data`.
    - `state` (string, required): `online`: polling now. `asleep`: suspended by its vendor (jobs wait `queued_asleep`). `offline`: not seen recently. One of `online`, `asleep`, `offline`.
    - `expires_at` (string, required): The next lifecycle deadline (ISO 8601), the one `expiry_kind` names. Warned 3 days ahead.
    - `expiry_kind` (string, required): `data_idle`: 14 days idle drops the data tier to link. `reconfirm`: the 90-day re-confirm is due (missed: link tier). `link_idle`: 30 days idle unlinks it. One of `data_idle`, `reconfirm`, `link_idle`.
    - `reconfirm_due_at` (string | null, required): When the data tier must be re-confirmed (POST /me/devices/{device_id}/agent-reconfirm); null at the link tier.
  - `key` (string): Whether this device holds the account's current end-to-end encryption key, from its own last report. `linked`: it holds the current key. `not_linked`: it holds none (it can still add logins, which reach the other devices). `out_of_date`: it holds an older or a different key. `unknown`: it has not reported, or the account has no key yet. Absent on a device that holds no key (an agent computer, an API key, an MCP connector, the cloud computer, a web browser: browsers never hold the key). A desktop app reports through its Helper (`custody_via`). One of `linked`, `not_linked`, `out_of_date`, `unknown`.
  - `custody_via` (string): The id of the device whose key this one uses (a desktop app names its Helper); `key` is that device's state. Absent when the device holds its own.
  - `created_via` (string): Present only on a Device of kind `agent_computer`. `app_link`: linked through the app-minted link, so the vendor was recorded by the app and may be shown as verified. `device_code`: the agent claimed its own vendor at a device-code approval, so show it as unverified. A missing value is `device_code`. One of `app_link`, `device_code`.
- `has_more` (boolean, required)
- `first_id` (string | null, required): Id of the first item on this page; pass as `before_id` for the previous page.
- `last_id` (string | null, required): Id of the last item on this page; pass as `after_id` for the next page.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X GET "https://superbot.gg/v1/devices" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const res = await fetch(`https://superbot.gg/v1/devices`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

res = requests.request(
    "GET",
    "https://superbot.gg/v1/devices",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```

## Get a device

`GET /v1/devices/{device_id}`

One device, with its live capabilities when it is connected.

- Operation id: `getDevice`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:read` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:read`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `device_id` | string | required | The device id (a Device `id`). |

### Response 200

The device.

`application/json`: Device.

- `id` (string, required)
- `object` (string, required): One of `device`.
- `kind` (string, required)
- `label` (string, required)
- `fingerprint` (string, required)
- `created_at` (string, required)
- `last_seen_at` (string | null, required)
- `online` (boolean, required)
- `capabilities` (object)
  - `tools_count` (integer, required)
  - `hub_port` (integer)
  - `helper_version` (string)
  - `held_vendors` (array of string, required)
  - `held_models` (array of string, required)
- `agent` (AgentDeviceFacet): Present only on a Device of kind `agent_computer`.
  - `agent_vendor` (string, required): The agent VM vendor, as it declared itself at link time. `generic` is any other agent. One of `grok-bot`, `muse`, `dots`, `manus`, `cursor-cloud`, `devin`, `replit`, `genspark`, `kimi`, `generic`.
  - `tier` (string, required): `link`: terminal and relay only. `data`: link plus the synced data the linking app granted. One of `link`, `data`.
  - `state` (string, required): `online`: polling now. `asleep`: suspended by its vendor (jobs wait `queued_asleep`). `offline`: not seen recently. One of `online`, `asleep`, `offline`.
  - `expires_at` (string, required): The next lifecycle deadline (ISO 8601), the one `expiry_kind` names. Warned 3 days ahead.
  - `expiry_kind` (string, required): `data_idle`: 14 days idle drops the data tier to link. `reconfirm`: the 90-day re-confirm is due (missed: link tier). `link_idle`: 30 days idle unlinks it. One of `data_idle`, `reconfirm`, `link_idle`.
  - `reconfirm_due_at` (string | null, required): When the data tier must be re-confirmed (POST /me/devices/{device_id}/agent-reconfirm); null at the link tier.
- `key` (string): Whether this device holds the account's current end-to-end encryption key, from its own last report. `linked`: it holds the current key. `not_linked`: it holds none (it can still add logins, which reach the other devices). `out_of_date`: it holds an older or a different key. `unknown`: it has not reported, or the account has no key yet. Absent on a device that holds no key (an agent computer, an API key, an MCP connector, the cloud computer, a web browser: browsers never hold the key). A desktop app reports through its Helper (`custody_via`). One of `linked`, `not_linked`, `out_of_date`, `unknown`.
- `custody_via` (string): The id of the device whose key this one uses (a desktop app names its Helper); `key` is that device's state. Absent when the device holds its own.
- `created_via` (string): Present only on a Device of kind `agent_computer`. `app_link`: linked through the app-minted link, so the vendor was recorded by the app and may be shown as verified. `device_code`: the agent claimed its own vendor at a device-code approval, so show it as unverified. A missing value is `device_code`. One of `app_link`, `device_code`.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X GET "https://superbot.gg/v1/devices/$DEVICE_ID" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const deviceId = 'DEVICE_ID';
const res = await fetch(`https://superbot.gg/v1/devices/${deviceId}`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

device_id = "DEVICE_ID"
res = requests.request(
    "GET",
    f"https://superbot.gg/v1/devices/{device_id}",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```

## Rename a device

`PATCH /v1/devices/{device_id}`

Set the device label. The label survives later activity from the device.

- Operation id: `updateDevice`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:write` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:write`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `device_id` | string | required | The device id (a Device `id`). |

### Request body

`application/json`, required.

- `label` (string, required): The new label, 1-64 printable characters (trimmed).

### Response 200

The renamed device.

`application/json`: Device.

- `id` (string, required)
- `object` (string, required): One of `device`.
- `kind` (string, required)
- `label` (string, required)
- `fingerprint` (string, required)
- `created_at` (string, required)
- `last_seen_at` (string | null, required)
- `online` (boolean, required)
- `capabilities` (object)
  - `tools_count` (integer, required)
  - `hub_port` (integer)
  - `helper_version` (string)
  - `held_vendors` (array of string, required)
  - `held_models` (array of string, required)
- `agent` (AgentDeviceFacet): Present only on a Device of kind `agent_computer`.
  - `agent_vendor` (string, required): The agent VM vendor, as it declared itself at link time. `generic` is any other agent. One of `grok-bot`, `muse`, `dots`, `manus`, `cursor-cloud`, `devin`, `replit`, `genspark`, `kimi`, `generic`.
  - `tier` (string, required): `link`: terminal and relay only. `data`: link plus the synced data the linking app granted. One of `link`, `data`.
  - `state` (string, required): `online`: polling now. `asleep`: suspended by its vendor (jobs wait `queued_asleep`). `offline`: not seen recently. One of `online`, `asleep`, `offline`.
  - `expires_at` (string, required): The next lifecycle deadline (ISO 8601), the one `expiry_kind` names. Warned 3 days ahead.
  - `expiry_kind` (string, required): `data_idle`: 14 days idle drops the data tier to link. `reconfirm`: the 90-day re-confirm is due (missed: link tier). `link_idle`: 30 days idle unlinks it. One of `data_idle`, `reconfirm`, `link_idle`.
  - `reconfirm_due_at` (string | null, required): When the data tier must be re-confirmed (POST /me/devices/{device_id}/agent-reconfirm); null at the link tier.
- `key` (string): Whether this device holds the account's current end-to-end encryption key, from its own last report. `linked`: it holds the current key. `not_linked`: it holds none (it can still add logins, which reach the other devices). `out_of_date`: it holds an older or a different key. `unknown`: it has not reported, or the account has no key yet. Absent on a device that holds no key (an agent computer, an API key, an MCP connector, the cloud computer, a web browser: browsers never hold the key). A desktop app reports through its Helper (`custody_via`). One of `linked`, `not_linked`, `out_of_date`, `unknown`.
- `custody_via` (string): The id of the device whose key this one uses (a desktop app names its Helper); `key` is that device's state. Absent when the device holds its own.
- `created_via` (string): Present only on a Device of kind `agent_computer`. `app_link`: linked through the app-minted link, so the vendor was recorded by the app and may be shown as verified. `device_code`: the agent claimed its own vendor at a device-code approval, so show it as unverified. A missing value is `device_code`. One of `app_link`, `device_code`.

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X PATCH "https://superbot.gg/v1/devices/$DEVICE_ID" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY" \
  -H "content-type: application/json" \
  -d '{"label":"Studio Mac"}'
```

```js
const deviceId = 'DEVICE_ID';
const res = await fetch(`https://superbot.gg/v1/devices/${deviceId}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({"label":"Studio Mac"}),
});
console.log(res.status, await res.json());
```

```python
import os
import requests

device_id = "DEVICE_ID"
res = requests.request(
    "PATCH",
    f"https://superbot.gg/v1/devices/{device_id}",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
        "content-type": "application/json",
    },
    json={"label":"Studio Mac"},
)
print(res.status_code, res.json())
```

## Unlink a device

`DELETE /v1/devices/{device_id}`

Sign the device out: its tokens and the caller keys it minted are revoked. The caller's own device answers 409 `own_device` (sign out from that device instead).

- Operation id: `deleteDevice`
- Group: [Devices](https://superbot.gg/developers/devices.md)
- Auth: A caller key holding `devices:write` (`Authorization: Bearer sbc_…` or `x-api-key: sbc_…`), or a signed-in session.
- Scopes: `devices:write`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `device_id` | string | required | The device id (a Device `id`). |

### Response 200

The device was unlinked.

`application/json`: DeletedDevice.

- `id` (string, required)
- `object` (string, required): One of `device`.
- `deleted` (boolean, required): One of `true`.
- `agent_unlink` (AgentUnlink): What unlinking an agent computer did, and the credentials to rotate at the source.
  - `device_id` (string, required)
  - `reason` (string, required): One of `user`, `revoked`, `link_idle`.
  - `cancelled` (object, required)
    - `jobs` (integer, required)
    - `terminals` (integer, required)
    - `loci` (integer, required)
    - `prompts` (integer, required)
    - `pair_requests` (integer, required)
    - `live_sessions` (integer, required)
    - `automations` (integer, required)
  - `key` (object, required)
    - `holder_removed` (boolean, required)
    - `factor_wraps_removed` (integer, required)
    - `rotation` (string, required): One of `enqueued`, `no_holder_device`, `not_needed`.
  - `rotate_at_source` (array of object, required)
    - `kind` (string, required)
    - `adapter` (string, required)
    - `label` (string | null, required)
    - `class` (string, required)

### Errors

Every refusal carries `error`, `message`, `doc_url` and `request_id`; the gateway routes answer in the dialect you dialed.

| Status | Codes | When |
| --- | --- | --- |
| 400 | [`invalid_request`](https://superbot.gg/developers#errors-invalid_request) | The request is malformed or a parameter is invalid; `param` names the field. |
| 401 | [`unauthorized`](https://superbot.gg/developers#errors-unauthorized) | The bearer is missing, malformed or unknown. |
| 403 | [`insufficient_scope`](https://superbot.gg/developers#errors-insufficient_scope) | The key lacks a scope (insufficient_scope), is revoked, or may not call this route. |
| 404 | [`not_found`](https://superbot.gg/developers#errors-not_found) | No such resource on this account. |
| 409 | [`conflict`](https://superbot.gg/developers#errors-conflict) | The resource is not in a state that allows this change. |
| 429 | [`rate_limited`](https://superbot.gg/developers#errors-rate_limited) | Over the per-key request bucket; retry after x-ratelimit-reset. |

### Examples

```sh
curl -sS -X DELETE "https://superbot.gg/v1/devices/$DEVICE_ID" \
  -H "Authorization: Bearer $SUPERBOT_API_KEY"
```

```js
const deviceId = 'DEVICE_ID';
const res = await fetch(`https://superbot.gg/v1/devices/${deviceId}`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.SUPERBOT_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

```python
import os
import requests

device_id = "DEVICE_ID"
res = requests.request(
    "DELETE",
    f"https://superbot.gg/v1/devices/{device_id}",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERBOT_API_KEY']}",
    },
)
print(res.status_code, res.json())
```
