> ## 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.

# Get an identity token inside a computer

> Coming soon: a program on a Linux computer asks 169.254.169.254 for a one-hour Orgo token, with no key file.

> [!WARNING]
>
> **Coming soon.** Identity tokens are off by default and rolling out host by host. Until they reach your computer, the address below answers `404` with `"error": "not_enabled"`, or the connection fails. Keep using `ORGO_API_KEY` until then, as described in [Key scopes and computer keys](/api-reference/api-keys/computer-keys.md).

A program on a Linux Orgo computer can ask the computer for a short-lived Orgo token. It needs no API key on disk. The token lasts one hour, reaches only that computer, and works as a Bearer token on the Orgo API.

## Get a token

Run this on the computer:

```bash theme={null}
curl -s -H 'Metadata-Flavor: Orgo' http://169.254.169.254/orgo/v1/identity/token
```

The `Metadata-Flavor: Orgo` header is required. Only `GET` works, over plain `http` on port 80. Don't send the request through a proxy: it is refused. If your environment sets `http_proxy` or `HTTP_PROXY`, add `169.254.169.254` to `no_proxy` and `NO_PROXY`.

```json theme={null}
{
  "token": "orgo_vm_eyJ2IjoxLCJraWQiOi….3q2X…",
  "token_type": "Bearer",
  "expires_in": 3599,
  "expires_at": "2026-10-20T21:04:05Z",
  "desktop_id": "8e0c1d2f-3a4b-4c5d-9e6f-7a8b9c0d1e2f",
  "vm_id": "vm-abc123",
  "user_id": "2b7d9f1e-4c6a-4e8b-a0d2-5f7e9c1b3a6d",
  "project_id": "6f1c2a7e-3b9d-4c1a-9e2f-0a1b2c3d4e5f"
}
```

**token** `string`

The token, starting `orgo_vm_`. Send it as `Authorization: Bearer <token>`. Treat it as a secret: never print or log it.

**token\_type** `string`

Always `Bearer`.

**expires\_in** `integer`

Seconds until the token expires.

**expires\_at** `string`

When the token expires, in UTC.

**desktop\_id** `string`

This computer's UUID, the `id` the REST endpoints take, such as `GET /api/computers/{id}`.

**vm\_id** `string`

The computer's current `instance_id`. It changes when the computer wakes onto a new host, so address the API with `desktop_id`.

**user\_id** `string`

The account that owns the computer's workspace. The token acts as that account, held to this computer.

**project\_id** `string`

The computer's workspace UUID.

`GET http://169.254.169.254/orgo/v1/identity`, with the same header, answers only `desktop_id`, `vm_id` and `"state": "bound"`. It never returns a token, and a `200` there does not mean tokens are on. The token request is the test.

## Use the token

```bash theme={null}
TOKEN_JSON=$(curl -s -H 'Metadata-Flavor: Orgo' http://169.254.169.254/orgo/v1/identity/token)
TOKEN=$(echo "$TOKEN_JSON" | jq -r .token)
COMPUTER_ID=$(echo "$TOKEN_JSON" | jq -r .desktop_id)

curl -s https://www.orgo.ai/api/computers/$COMPUTER_ID \
  -H "Authorization: Bearer $TOKEN"
```

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

METADATA = "http://169.254.169.254/orgo/v1/identity/token"

def identity(fresh=False):
    response = requests.get(
        METADATA,
        params={"fresh": "1"} if fresh else None,
        headers={"Metadata-Flavor": "Orgo"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

def call_orgo(method, path, **kwargs):
    ident = identity()
    url = f"https://www.orgo.ai/api{path.format(id=ident['desktop_id'])}"
    response = requests.request(method, url, headers={"Authorization": f"Bearer {ident['token']}"}, **kwargs)
    if response.status_code == 401:
        # The token went stale (a copy, a move, a transfer): ask for a new one, once.
        ident = identity(fresh=True)
        response = requests.request(method, url, headers={"Authorization": f"Bearer {ident['token']}"}, **kwargs)
    return response

print(call_orgo("GET", "/computers/{id}").json())
```

```javascript JavaScript theme={null}
// Node 18 or later, saved as identity.mjs. The built-in fetch works with this address.
const METADATA = 'http://169.254.169.254/orgo/v1/identity/token';

async function identity(fresh = false) {
  const response = await fetch(fresh ? `${METADATA}?fresh=1` : METADATA, {
    headers: { 'Metadata-Flavor': 'Orgo' },
  });
  if (!response.ok) throw new Error(`identity: ${response.status} ${(await response.json()).error}`);
  return response.json();
}

async function callOrgo(method, path, init = {}) {
  let ident = await identity();
  const url = `https://www.orgo.ai/api${path.replace('{id}', ident.desktop_id)}`;
  let response = await fetch(url, { ...init, method, headers: { ...init.headers, Authorization: `Bearer ${ident.token}` } });
  if (response.status === 401) {
    // The token went stale (a copy, a move, a transfer): ask for a new one, once.
    ident = await identity(true);
    response = await fetch(url, { ...init, method, headers: { ...init.headers, Authorization: `Bearer ${ident.token}` } });
  }
  return response;
}

console.log(await (await callOrgo('GET', '/computers/{id}')).json());
```

## Refresh the token

Ask the address again whenever you need a token. You don't need to store one. The computer hands back the same token while at least 40 minutes of its hour are left, and gets a new one after that, so asking before each batch of calls is cheap. A token is never written to the computer's disk.

The address allows 20 requests a second per computer, with bursts of 40. Past that it answers `429` with `Retry-After: 1`.

## After a 401, ask with ?fresh=1

The Orgo API checks on every request that the computer still stands as the token says. After a copy of the computer, a move, a transfer, or someone else joining its workspace, a token it holds can go stale and answer `401`. Ask once with `?fresh=1`:

```bash theme={null}
curl -s -H 'Metadata-Flavor: Orgo' 'http://169.254.169.254/orgo/v1/identity/token?fresh=1'
```

`fresh=1` drops the token the computer kept and gets a new one. It is the only query parameter accepted. If the new token also answers `401`, or the address now answers `403` or `404`, the computer can no longer have a token. Stop retrying.

## Errors from 169.254.169.254

Every error body is `{ "error": "<code>", "message": "<one sentence>" }`. A `403 not_eligible` adds `reason`.

| Status | `error` | When | What to do |
| - | - | - | - |
| `400` | `metadata_header_required` | The `Metadata-Flavor: Orgo` header is missing or different. | Send it exactly. |
| `400` | `bad_request` | A request body, a `Host` other than `169.254.169.254`, a query other than `fresh=1`, or a proxy-style request line. | Send a plain `GET` with no body. |
| `403` | `browser_request` | The request carries `Origin`, `Referer`, `Forwarded`, `Via` or an `X-Forwarded-*` header, or came through a proxy. | Call from a program, not a web page, with no proxy. |
| `403` | `not_eligible` | This computer may not have an identity. `reason` is `computer_deleted`, `not_linux`, `owner_guest`, `owner_deleting` or `workspace_shared`. | Use `ORGO_API_KEY`. |
| `404` | `not_enabled` | Identity tokens are not on for this computer or its host. | Use `ORGO_API_KEY`. |
| `404` | `not_found` | Another path. | Use `/orgo/v1/identity/token`. |
| `405` | `method_not_allowed` | A method other than `GET`. | Use `GET`. |
| `429` | `rate_limited` | More than 20 requests a second. | Wait for `Retry-After`. |
| `503` | `retry` | Not ready yet: a computer still starting, a computer that just woke or moved, or Orgo briefly unreachable. | Wait for `Retry-After` (2 to 60 seconds), then ask again. |

A program in a web page on the computer can't get a token, and neither can a container on the computer, such as one run with Docker.

## Errors from the Orgo API

The API answers a refused token with `{ "error": "…", "code": "…" }`, at the status in this table, on every endpoint.

| Status | `code` | Message | What to do |
| - | - | - | - |
| `401` | `invalid_token` | `Invalid or expired computer identity token.` | Ask for a new token. |
| `401` | `identity_stale` | `This computer identity token is out of date. Ask the computer for a new one.` | Ask with `?fresh=1`. |
| `401` | `not_eligible` | `This computer can no longer have an identity token.` | Use `ORGO_API_KEY`. |
| `401` | `vm_identity_off` | `Computer identity tokens are turned off.` | Use `ORGO_API_KEY`. |
| `403` | `computer_key` | `A computer’s identity token can’t … Use your account or the web UI.` | The endpoint is not in the table in [What a token can call](#what-a-token-can-call). |
| `403` | `computer_scope_mismatch` | `A computer’s identity token works only for its own computer.` | Name this computer, the token's `desktop_id`. |
| `403` | `workspace_scope_mismatch` | `A computer’s identity token works only for its own computer’s workspace.` | Name this computer's workspace, the token's `project_id`. |
| `403` | `computer_key` | `A computer’s identity token can call … only on a computer Bops bots work on.` | A Bops call from a computer no Bops bot works on. Make it from a computer your bots work on. |
| `403` | `bops_cloud_not_ready` | `A computer’s identity token can’t read the account profile yet. Use the computer’s key.` | `GET /api/user/profile` is not open to tokens yet. Use the computer key. |
| `503` | `database` | `Service temporarily unavailable. The database is not accepting requests. Retry shortly.` | Retry. |

## What a token can call

A token acts as the computer's own key, held to its own computer. It can call the endpoints below, about its own computer only. It can never read the computer's passwords: `GET /api/computers/{id}/vnc-password` answers `403` with `code: "computer_key"`, and `GET /api/computers/{id}` returns `"vnc_password": null`. It never manages keys or reaches your billing.

| Method | Path | Can a token call it | What it does |
| - | - | - | - |
| POST | `/api/bops/computer/active` | Only on a computer your Bops bots work on | Tells Orgo the free Bops computer is in use so it is not suspended for sitting idle. |
| GET | `/api/bops/plan` | Only on a computer your Bops bots work on | Returns the Bops plan, AI credit and free Bops computer, granting the free starting credit on first read. |
| GET | `/api/bops/settings` | Only on a computer your Bops bots work on | Returns whether the account's Bops bots work in the cloud or on the Mac. |
| GET | `/api/computers/{id}` | Yes | Gets a computer with its status, hardware and connection details. |
| POST | `/api/computers/{id}/bash` | Yes | Runs a shell command on the computer and returns its output. |
| POST | `/api/computers/{id}/click` | Yes | Clicks at a point on the computer's screen. |
| POST | `/api/computers/{id}/drag` | Yes | Drags the mouse from one point to another on the computer. |
| POST | `/api/computers/{id}/exec` | Yes | Runs Python code on the computer and returns its output. |
| POST | `/api/computers/{id}/key` | Yes | Presses a key or key combination on the computer. |
| POST | `/api/computers/{id}/mouse-move` | Yes | Moves the pointer on the computer's screen without clicking. |
| POST | `/api/computers/{id}/resume` | Yes | Resumes a suspended computer where it left off. |
| GET | `/api/computers/{id}/screens` | Yes | Lists the screens on the computer. |
| POST | `/api/computers/{id}/screens` | Yes | Adds a screen to the computer. |
| GET | `/api/computers/{id}/screens/{screenId}` | Yes | Gets one screen on the computer. |
| GET | `/api/computers/{id}/screenshot` | Yes | Takes a screenshot of the computer's screen. |
| POST | `/api/computers/{id}/scroll` | Yes | Scrolls on the computer's screen. |
| POST | `/api/computers/{id}/type` | Yes | Types text on the computer. |
| POST | `/api/computers/{id}/wait` | Yes | Waits a number of seconds on the computer. |
| GET | `/api/desktops/{id}/proxy/{...path}` | Yes | Passes a read request through to the computer's Desktop API. |
| HEAD | `/api/desktops/{id}/proxy/{...path}` | Yes | Passes a read request through to the computer's Desktop API. |
| POST | `/api/desktops/{id}/proxy/{...path}` | Yes | Passes a request that acts on the computer through to its Desktop API. |
| PUT | `/api/desktops/{id}/proxy/{...path}` | Yes | Passes a request that acts on the computer through to its Desktop API. |
| PATCH | `/api/desktops/{id}/proxy/{...path}` | Yes | Passes a request that acts on the computer through to its Desktop API. |
| DELETE | `/api/desktops/{id}/proxy/{...path}` | Yes | Passes a request that acts on the computer through to its Desktop API. |
| POST | `/api/projects/{id}/access/log` | Only on a computer your Bops bots work on | Records Bops bots' uses of apps and keys. |
| GET | `/api/projects/{id}/orbit/issues` | Only on a computer your Bops bots work on | Lists the Bops workspace's Orbit issues. |
| POST | `/api/projects/{id}/orbit/issues` | Only on a computer your Bops bots work on | Creates an Orbit issue, or updates the one already open for the same thread or ticket. |
| PATCH | `/api/projects/{id}/orbit/issues/{issueId}` | Only on a computer your Bops bots work on | Updates an Orbit issue or adds notes to it. |
| PUT | `/api/projects/{id}/routines` | Only on a computer your Bops bots work on | Saves many Bops routines at once and can delete the ones left out. |
| POST | `/api/projects/{id}/routines/{routineId}/runs` | Only on a computer your Bops bots work on | Records the result of a Bops routine's run. |
| GET | `/api/secrets` | Only on a computer your Bops bots work on | Lists the names of the account's saved secrets, never their values. |
| POST | `/api/secrets/call` | Only on a computer your Bops bots work on | Calls a third party API with a saved secret without showing the secret. |
| GET | `/api/user/profile` | Not yet: `403` with `code: "bops_cloud_not_ready"` | Shows the account's id, email, name and plan status. |
| WebSocket | `/desktops/{id}/ws/audio` | Yes | Streams the computer's sound. |
| WebSocket | `/desktops/{id}/ws/rtc` | Yes | Streams the computer's screen over WebRTC, with its mouse and keyboard. |
| WebSocket | `/desktops/{id}/ws/stats` | Yes | Streams the live statistics of a screen connection. |
| WebSocket | `/desktops/{id}/ws/terminal` | Yes | Opens a terminal on the computer. |
| WebSocket | `/desktops/{id}/ws/websockify` | Yes | Streams the computer's screen over VNC, with its mouse and keyboard. |

Through the Desktop API proxy and on the WebSocket paths, Orgo swaps the token for the computer's own credential, so it never reaches the computer. A browser can't use a token as `?token=`. An open connection is checked again every 15 seconds. It can stay open past the token's hour, and closes within about 25 seconds once the computer no longer stands as the token says.

## Which computers get a token

A computer gets a token when all of these hold:

* It is a Linux computer. Windows, Mac and iPhone computers get none, and some older Linux computers are not served: the connection fails. Use `ORGO_API_KEY` there.
* Only its owner can use its workspace: no other member, no open invite or share link, and no workspace key, Invoicing client or MCP sign-in for it. A shared workspace gets no token for now (`403 not_eligible`, `reason: "workspace_shared"`).
* No one-computer key is bound to it.
* Its owner is a registered account that has not asked to be deleted.

Orgo hands a computer at most 60 new tokens an hour, counted on each Orgo server on its own. Because the computer reuses a token for its first 20 minutes, a program never comes near that.

> [!NOTE]
>
> Any program on the computer can ask for a token, whatever user it runs as. Treat every program there as able to act as the computer.

## Related pages

* [Key scopes and computer keys](/api-reference/api-keys/computer-keys.md): the computer key a token stands in for.
* [Which endpoints a one-computer key can call](/api-reference/api-keys/permissions.md#which-endpoints-a-one-computer-key-can-call): the same table, for keys.
* [Key expiry and rotation](/api-reference/api-keys/expiry-and-rotation.md): how a computer key rotates until tokens arrive.


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