> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orgo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and manage API keys

> Create, list, change and revoke keys over the API, with the request, the response and every error code.

Five endpoints under `/api/keys` create, list, read, change and revoke your keys. [Settings, API keys](https://www.orgo.ai/settings/developers) does the same in the dashboard.

| Call | What it does | Permission |
| - | - | - |
| `POST /api/keys` | Creates a key and returns its value once. | API keys write |
| `GET /api/keys` | Lists your keys, never their values. | API keys read |
| `GET /api/keys/{id}` | Reads one key, never its value. | API keys read |
| `PATCH /api/keys/{id}` | Changes a key's name, permissions, expiry or workspace. Its value never changes. | API keys write |
| `DELETE /api/keys/{id}` | Revokes a key. `DELETE /api/keys?id={id}` is the same, kept for older clients. | API keys write |

Signed in, you can call all five. A key needs the API keys group at the level shown, as described in [API key permissions](/api-reference/api-keys/permissions.md). A full access key has it.

A workspace key can call all five, for the keys of its own workspace, as described in [What a workspace key can manage](#what-a-workspace-key-can-manage).

## The no-escalation rule

A key never makes or changes a key into more than it has itself. With API keys write, a key can make a key only when every group of the new key is at or under its own level, its scope is inside its own, and, when the calling key expires, the new key expires no later. It can change or revoke only keys already within its own access, and it can change itself only to narrow it. Anything past that answers `403` with `code: "key_escalation"`, and the message names what was too much. Signed in, you can do anything to your own keys. A computer's own key manages no keys at all.

```json theme={null}
{
  "error": "This key can’t give a key more than it has: Control write is more than this key’s Control read.",
  "code": "key_escalation"
}
```

## What a workspace key can manage

A workspace key with API keys access manages the keys of its own workspace, under the no-escalation rule. It can make keys for its workspace, list and read them, change them and revoke them. It never reaches past its workspace:

* A key for all your workspaces, including a `POST` with no body, answers `403` with `code: "key_escalation"`. The message names what was too much, such as `it would reach more than this key’s one workspace.`, or `it would have full access.` when a restricted workspace key leaves out `permissions`.
* Another workspace, or a computer in one, answers `403` with `code: "workspace_scope_mismatch"` and `This API key cannot access this workspace (workspace_scope_mismatch).`
* A one-computer key, even for a computer in its workspace, is not a workspace key's to make, change or revoke. Those calls answer `403` with `code: "account_key_required"`.
* A key outside its workspace answers `404` with `{ "error": "Key not found" }`, as if it did not exist.

A workspace key never manages people, whatever its permissions, as described in [What a workspace-scoped key can do](/api-reference/authentication.md#what-a-workspace-scoped-key-can-do). So a restricted key for one workspace can't be given Members write. Making one with Members write, or changing a key to one, answers `400` with `code: "invalid_permissions"`, whoever asks. Set Members to `read` or `none`.

The key of an [Invoicing](/guides/invoicing.md) client lists its workspace's keys but makes, changes and revokes none. Changing or revoking a key with it answers `403` with `code: "client_key"`. So does making one, once the request passes every other check. A request that fails one first, such as a key for all your workspaces (`key_escalation`) or a bad body (`400`), gets that check's answer. It makes none because your own key list leaves a client's workspace out and would never show a key it made.

## Create a key

```http theme={null}
POST https://www.orgo.ai/api/keys
```

Send a JSON body. Every field is optional. A body with none of them makes an unnamed full access key for all your workspaces that never expires.

**name** `string`

What the key is for, such as `CI` or `Contractor`. Left out, the key has no name. With `computer_id` and no name, it is named `Key for <computer name>`. Three names are kept for Orgo and refused with `400` and `code: "reserved_name"`: `Bops on iPhone`, any name starting `Computer key for`, and any name starting `Bops runner on`.

**permissions** `string | object`

What the key may call. Left out, or `"full"`: full access. `"read_only"`: read in every group. An object of group to level makes a custom key, such as `{ "computers": "write", "control": "write" }`. A group left out is `none`. Groups and levels are listed in [API key permissions](/api-reference/api-keys/permissions.md). An unknown group or level is refused with `400` and `code: "invalid_permissions"`. So is Members write on a key with `workspace_id`, as described in [What a workspace key can manage](#what-a-workspace-key-can-manage).

**expires\_in\_days** `integer`

Days until the key expires, a whole number from `1` to `365`. Send this or `expires_at`, not both.

**expires\_at** `string`

When the key expires, as an ISO 8601 date and time such as `2027-03-03T00:00:00Z`. It must be in the future and at most a year away. With neither `expires_in_days` nor `expires_at`, the key never expires. Expiry is described in [Key expiry and rotation](/api-reference/api-keys/expiry-and-rotation.md).

**workspace\_id** `string`

A workspace UUID. The key then reaches only that workspace. You must own the workspace or be a member with write access. Left out, the key reaches all your workspaces. Making a workspace key revokes the computer keys of that workspace's computers, as described in [When Orgo revokes a computer key](/api-reference/api-keys/computer-keys.md#when-orgo-revokes-a-computer-key). The workspace of an [Invoicing](/guides/invoicing.md) client gets its key only from Settings, Invoicing: naming it answers `409` with `code: "managed_key"`.

**computer\_id** `string`

The UUID of one Linux computer. The key then reaches only that computer. Its `instance_id` also works, including one it had before a wake. Send this or `workspace_id`, not both. Only you signed in, or a key for your whole account, can make one. A workspace key gets `403` with `code: "account_key_required"`. One-computer keys are rolling out, as described in [One-computer keys for contractors and embeds](/api-reference/api-keys/computer-keys.md#one-computer-keys-for-contractors-and-embeds).

> [!WARNING]
>
> Always send a JSON body, even `{}`. A `POST` with no body at all deletes your unnamed account-wide key and makes a new one. Older clients rely on that.

Each account can send at most 30 requests that create or change a key in each clock hour. A request counts even when it is then refused, for example for a bad body or by the no-escalation rule. More answers `429` with `code: "rate_limited"` and `retry_after` in seconds, also sent as `Retry-After`.

### Example

Set `$WORKSPACE_ID` to the workspace the key is for.

```bash cURL theme={null}
curl -X POST https://www.orgo.ai/api/keys \
  -H "Authorization: Bearer $ORGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI",
    "workspace_id": "'"$WORKSPACE_ID"'",
    "permissions": { "computers": "write", "control": "write" },
    "expires_in_days": 90
  }'
```

```python Python theme={null}
import os
import requests

response = requests.post(
    "https://www.orgo.ai/api/keys",
    headers={"Authorization": f"Bearer {os.environ['ORGO_API_KEY']}"},
    json={
        "name": "CI",
        "workspace_id": os.environ["WORKSPACE_ID"],
        "permissions": {"computers": "write", "control": "write"},
        "expires_in_days": 90,
    },
)
key = response.json()
print(key["key"])  # shown once
```

```javascript JavaScript theme={null}
const response = await fetch('https://www.orgo.ai/api/keys', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ORGO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'CI',
    workspace_id: process.env.WORKSPACE_ID,
    permissions: { computers: 'write', control: 'write' },
    expires_in_days: 90,
  }),
});
const key = await response.json();
console.log(key.key); // shown once
```

### Response

The key's value is in `key`, and again in `apiKey` for older clients. It is shown once. Orgo keeps only a digest and an encrypted copy.

```json theme={null}
{
  "key": "sk_live_3f9a…",
  "apiKey": "sk_live_3f9a…",
  "id": "0b6c5e2a-8f1d-4c3b-9a7e-2d4f6a8b0c1e",
  "name": "CI",
  "scope": "workspace",
  "workspace_id": "6f1c2a7e-3b9d-4c1a-9e2f-0a1b2c3d4e5f",
  "key_prefix": "sk_live_3f9a…",
  "created_at": "2026-10-20T15:04:05.123Z",
  "last_used": null,
  "permissions": {
    "computers": "write",
    "control": "write",
    "files": "none",
    "templates": "none",
    "workspaces": "none",
    "members": "none",
    "secrets": "none",
    "ai": "none",
    "bops": "none",
    "billing": "none",
    "keys": "none"
  },
  "access": "custom",
  "expires_at": "2027-01-18T15:04:05.123Z",
  "expired": false
}
```

A key made with `computer_id` also carries `"scope": "computer"` and `computer`: `{ "id", "name", "os", "workspace_id" }`.

## The key object

`GET`, `PATCH` and the create answer describe a key with these fields. None of them is the key's value.

**id** `string`

The key's id. Use it in `/api/keys/{id}`.

**name** `string | null`

The key's name.

**scope** `string`

Where it reaches: `account` (all your workspaces), `workspace` or `computer`.

**workspace\_id** `string | null`

The one workspace it reaches. For a one-computer key, that computer's workspace. `null` for all workspaces and for a computer's own key.

**key\_prefix** `string | null`

The first 12 characters of the value, then `…`. Use it to tell keys apart.

**created\_at** `string`

When it was made.

**last\_used** `string | null`

When it last signed in. An expired key's stays where it was.

**permissions** `object | null`

Its level in every group. `null` for full access.

**access** `string`

`full`, `read_only` or `custom`: the preset its permissions match.

**expires\_at** `string | null`

When it expires. `null` for never.

**expired** `boolean`

`true` once `expires_at` has passed. The key signs in nowhere. It stays listed until you delete it, except a one-computer key, which Orgo deletes soon after it expires.

**computer\_key** `boolean`

Only on a computer's own key, always `true`. That key answers `access: "full"` but calls only its fixed list, described in [What a computer key can call](/api-reference/api-keys/computer-keys.md#what-a-computer-key-can-call).

**computer** `object | null`

On a computer's own key and a one-computer key: the computer it is for, `{ "id", "name" }`. `null` on a computer's own key whose computer is gone.

**rotated\_at** `string | null`

Only on a computer's own key: when its secret last changed.

## List keys

```http theme={null}
GET https://www.orgo.ai/api/keys
```

Returns your keys under `keys`, newest first. A workspace key sees only the keys of its workspace. Keys of your [Invoicing](/guides/invoicing.md) clients are left out.

**workspace\_id** `string`

Only that workspace's keys, including keys for one of its computers. A workspace you don't own or belong to, or an id no workspace has, answers `403` with `{ "error": "You do not have access to this workspace." }`.

**computer\_id** `string`

Only the keys bound to that computer: its own key and its one-computer keys. Its UUID, or a current or past `instance_id`. No such computer answers `404` with `code: "computer_not_found"`. A computer in a workspace you don't own or belong to answers `403` with `{ "error": "You do not have access to this workspace." }`.

```bash theme={null}
curl https://www.orgo.ai/api/keys \
  -H "Authorization: Bearer $ORGO_API_KEY"
```

```json theme={null}
{
  "keys": [
    {
      "id": "0b6c5e2a-8f1d-4c3b-9a7e-2d4f6a8b0c1e",
      "name": "CI",
      "scope": "workspace",
      "workspace_id": "6f1c2a7e-3b9d-4c1a-9e2f-0a1b2c3d4e5f",
      "key_prefix": "sk_live_3f9a…",
      "created_at": "2026-10-20T15:04:05.123Z",
      "last_used": "2026-10-20T16:30:00.000Z",
      "permissions": { "computers": "write", "control": "write", "files": "none", "templates": "none", "workspaces": "none", "members": "none", "secrets": "none", "ai": "none", "bops": "none", "billing": "none", "keys": "none" },
      "access": "custom",
      "expires_at": "2027-01-18T15:04:05.123Z",
      "expired": false
    },
    {
      "id": "9d2e4f6a-1b3c-4d5e-8f7a-6b5c4d3e2f1a",
      "name": "Computer key for research-1",
      "scope": "computer",
      "workspace_id": null,
      "key_prefix": "sk_live_8c1d…",
      "created_at": "2026-09-02T10:00:00.000Z",
      "last_used": "2026-10-20T16:29:58.000Z",
      "permissions": null,
      "access": "full",
      "expires_at": null,
      "expired": false,
      "computer_key": true,
      "computer": { "id": "8e0c1d2f-3a4b-4c5d-9e6f-7a8b9c0d1e2f", "name": "research-1" },
      "rotated_at": "2026-10-01T09:12:44.000Z"
    }
  ],
  "hasKey": true,
  "hasDeveloperKey": true,
  "apiKey": null,
  "keyInfo": null,
  "one_computer_keys": false
}
```

`apiKey` and `keyInfo` describe the old unnamed key, for older clients. Over an API key, `apiKey` is always `null`. `one_computer_keys` says whether one-computer keys are available yet.

## Get a key

```http theme={null}
GET https://www.orgo.ai/api/keys/{id}
```

Returns the key object. A key that is not yours, or is outside the calling key's scope, answers `404` with `{ "error": "Key not found" }`.

```bash theme={null}
curl https://www.orgo.ai/api/keys/$KEY_ID \
  -H "Authorization: Bearer $ORGO_API_KEY"
```

## Change a key

```http theme={null}
PATCH https://www.orgo.ai/api/keys/{id}
```

Send only what changes. The key's value stays the same, so nothing that uses it needs updating. Returns the key object as it is now.

**name** `string | null`

A new name, or `null` for none. The kept names listed under `name` in [Create a key](#create-a-key) are refused.

**permissions** `string | object`

`"full"`, `"read_only"`, or an object of group to level. The object replaces every group: a group left out becomes `none`.

**expires\_in\_days** `integer`

A new expiry, `1` to `365` days from now.

**expires\_at** `string | null`

A new expiry date, at most a year away, or `null` to remove the expiry. A new expiry gets its own reminder emails.

**workspace\_id** `string | null`

Move the key to one workspace, or `null` for all workspaces. Moving a key into a workspace revokes the computer keys of that workspace's computers, as a new workspace key does. A one-computer key keeps its computer: sending `workspace_id` for it answers `400` with `code: "computer_scope_fixed"`. Make a new key instead.

```bash theme={null}
curl -X PATCH https://www.orgo.ai/api/keys/$KEY_ID \
  -H "Authorization: Bearer $ORGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "permissions": "read_only", "expires_in_days": 30 }'
```

No key's computer can change. A `PATCH` with `computer_id`, for any key, answers `400` with `code: "computer_scope_fixed"`.

Some keys can't be changed:

* A computer's own key belongs to Orgo. Changing it answers `409` with `{ "error": "Orgo manages this computer’s key. It can only be revoked.", "code": "managed_key" }`.
* The key of an [Invoicing](/guides/invoicing.md) client changes workspace only in Settings, Invoicing (`409`, `code: "managed_key"`). No other key moves into a client's workspace either (`409`, `code: "managed_key"`).
* A one-computer key is changed only by you signed in, or by a key for your whole account. A workspace key gets `403` with `code: "account_key_required"`.
* The Bops for iPhone key keeps its name (`409`, `code: "managed_key"`).
* A restricted key cannot change a key the Bops app signed in with (`403`, `code: "key_escalation"`).

Change requests share the limit of 30 each clock hour described in [Create a key](#create-a-key).

## Revoke a key

```http theme={null}
DELETE https://www.orgo.ai/api/keys/{id}
```

The key stops working at once. Answers `{ "success": true }`.

```bash theme={null}
curl -X DELETE https://www.orgo.ai/api/keys/$KEY_ID \
  -H "Authorization: Bearer $ORGO_API_KEY"
```

* Only you signed in, or a full access key for all your workspaces that never expires, can revoke a computer's own key. The computer forgets the key, and Orgo then takes it off the computer. A restricted or expiring key for all your workspaces gets `403` with `code: "key_escalation"`. A workspace key gets `404`, as for any key outside its workspace, since a computer key belongs to no workspace.
* Revoking a one-computer key that could control its computer (full access, or Control write) changes that computer's screen password. This is described in [Revoking changes the screen password](/api-reference/api-keys/computer-keys.md#revoking-changes-the-screen-password).
* A restricted key cannot revoke a key the Bops app signed in with.
* A one-computer key is revoked only by you signed in, or by a key for your whole account. A workspace key gets `403` with `code: "account_key_required"`.

`DELETE /api/keys?id={id}` does the same. Without `id` it answers `400` with `{ "error": "Missing id query param" }`.

## Errors

### Codes from the key endpoints

| Status | `code` | Message | When |
| - | - | - | - |
| `400` | `invalid_permissions` | `Unknown permission group “deploy”. Groups: computers, control, …` | An unknown group, a level other than `none`, `read` or `write`, or a value that is not an object, `"full"` or `"read_only"`. A restricted key for one workspace with Members write says `A key for one workspace can read its members but can’t change them. Set Members to read or none, or make the key for every workspace.` |
| `400` | `invalid_expiry` | `Give expires_in_days or expires_at, not both.` | Both sent, `expires_in_days` outside `1` to `365`, `expires_at` not a date, in the past, or more than a year away (`A key can last a year at most.`). |
| `400` | `reserved_name` | `That name is kept for Bops for iPhone. Choose another.` | A name Orgo keeps for its own keys. |
| `400` | `one_scope` | `A key is for the account, one workspace or one computer. Send workspace_id or computer_id, not both.` | Both scopes on a create. |
| `400` | `computer_scope_unavailable` | `Keys for one computer aren’t available yet.` | `computer_id` while one-computer keys are not turned on. Orgo turns them on for every account at once. |
| `400` | `computer_scope_unsupported` | `Keys for one computer work only on Linux computers for now.` | `computer_id` names a computer that is not Linux. |
| `400` | `computer_scope_fixed` | `A key’s computer can’t change. Make a new key for the other computer.` | A `PATCH` with `computer_id`, for any key. A `PATCH` with `workspace_id` on a one-computer key gets the same code with `This key is for one computer, and that can’t change. Make a new key for another scope.` |
| `400` | none | `Invalid JSON body` | A `POST` body that is not a JSON object. |
| `400` | none | `Send a JSON body with what to change.` | A `PATCH` with no body, or a body that is not a JSON object. |
| `400` | none | `Nothing to change. Send name, permissions, an expiry or workspace_id.` | A `PATCH` with none of the fields. |
| `403` | `key_permission` | `This key can’t create an API key for the account (it needs API keys write access).` | The calling key lacks the API keys level the call needs. The message names the call, here `POST /api/keys`. |
| `403` | `key_escalation` | `This key can’t give a key more than it has: …` | The no-escalation rule, described in [The no-escalation rule](#the-no-escalation-rule). |
| `403` | `computer_key` | `A computer’s own key can’t create an API key for the account. Use your account or the web UI.` | A computer's own key called a key endpoint. The message names the call, here `POST /api/keys`. |
| `403` | `computer_scope` | `A key for one computer can’t create an API key for the account. Use a key for your account or the web UI.` | A one-computer key called a key endpoint. The message names the call, here `POST /api/keys`. |
| `403` | `account_key_required` | `Only a key for your whole account, or you signed in, can make a key for one computer.` | A workspace key sent `computer_id`. Changing or revoking a one-computer key with a workspace key says `Only you, signed in, or a key for your whole account can change a key for one computer.` (or `revoke`). |
| `403` | `client_key` | `A client account’s key can’t make, change or revoke keys. Ask whoever runs the account for another key.` | The key of an [Invoicing](/guides/invoicing.md) client tried to change or revoke a key, or to make one in a request that passed every other check. |
| `403` | `agent_key_limit` | `Unclaimed agent accounts can have 3 API keys. Delete one first (DELETE /api/keys?id=). …` | The account was made by an agent and nobody has claimed it yet. |
| `403` | `GUEST_RESTRICTED` | `Sign up to create your own workspace.` | The caller joined through a share link. |
| `403` | none | `You do not have access to this workspace.` | You neither own nor belong to the workspace `workspace_id` names, or the workspace of the computer `computer_id` names. Also an id no workspace has. |
| `403` | `workspace_read_only` | `This workspace is view-only. Ask the owner for write access (workspace_read_only).` | A `POST` or `PATCH` names a workspace, or a computer in one, where you have view-only access. |
| `403` | `workspace_scope_mismatch` | `This API key cannot access this workspace (workspace_scope_mismatch).` | A workspace key named another workspace, or a computer in one, in `workspace_id` or `computer_id`. |
| `404` | none | `Key not found` | No key of yours has that id, or it is outside the calling key's scope. |
| `404` | `computer_not_found` | `Computer not found (computer_not_found).` | No computer matches `computer_id`. Signed in, the message is `Computer not found.` |
| `409` | `managed_key` | `Orgo manages this computer’s key. It can only be revoked.` | A change to a key Orgo manages, as listed in [Change a key](#change-a-key). A key made for, or moved into, a client's workspace says `That workspace belongs to a client. Its key is made and replaced in Settings, Invoicing.` |
| `409` | `computer_key_in_use` | `This computer has its own Orgo key: it runs your Bops bots, and a key for someone else there would stop them. Pick another computer.` | `computer_id` names a computer that holds its own key. Its own key is kept. |
| `429` | `rate_limited` | `Too many new keys this hour. Try again later.` | More than 30 requests to create or change a key in this clock hour, including ones later refused. Retry after `retry_after` seconds. |
| `500` | none | `Server error generating key` (or listing, reading, changing, deleting) | Orgo failed to finish the call. Retry. |

Every body is `{ "error": "…" }`, with `code` when the table shows one. Match on `code`, not on the message.

### Codes any endpoint can return to a key

These come from signing in with the key, before any endpoint runs. Every endpoint a key can call answers them with the status and `code` shown. The Computer Use API and threads put the same status, message and `code` in their OpenAI style envelope.

| Status | `code` | Message | When |
| - | - | - | - |
| `401` | `key_expired` | `This API key expired on Mar 3, 2026. Make a new one in Settings, API keys.` | The key is past `expires_at`. Make a new key. |
| `401` | `key_computer_gone` | `This key’s computer is gone. Make a new key.` | A key bound to one computer whose computer no longer exists. |
| `403` | `key_permission` | `This key can’t … (it needs Control write access).` | A restricted key called an endpoint outside its groups. |
| `403` | `computer_scope` | `A key for one computer can’t … Use a key for your account or the web UI.` | A one-computer key called an endpoint it may not call. |
| `403` | `computer_scope_mismatch` | `This key is for one computer and can’t act on another.` | A one-computer key named a different computer. |
| `403` | `computer_deleted` | `This key’s computer was deleted.` | A key bound to a deleted computer. |
| `403` | `computer_moved` | `This key’s computer moved to another workspace. Make a new key for it there.` | A one-computer key whose computer changed workspace without the key. A move in Orgo takes the key along or deletes it, as described in [How long a one-computer key lasts](/api-reference/api-keys/computer-keys.md#how-long-a-one-computer-key-lasts). |
| `403` | `computer_key` | `A computer’s own key can’t … Use your account or the web UI.` | A computer's own key called an endpoint outside its list. |
| `403` | `computer_key_held` | `This computer holds its own Orgo key, which has full access and never expires, …` | A restricted or expiring key called a Control or AI endpoint about a computer that holds its own key. Described in [Computers that hold their own key](/api-reference/api-keys/permissions.md#computers-that-hold-their-own-key). |
| `503` | `key_rotation_failed` | `Key rotation failed, try again later.` | A computer's own key was due to rotate and Orgo could not finish. Nothing ran. The answer carries `Retry-After: 5`. Described in [Key expiry and rotation](/api-reference/api-keys/expiry-and-rotation.md#when-rotation-fails). |

The other authentication errors, such as `Invalid API key`, are listed in [Authentication](/api-reference/authentication.md#error-responses).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.