# REST API

> An overview of core's /v1 REST API, with its tokens, workspaces, conventions and main resources.

Core's REST API, `/v1`, is what the apps use. You can call it too, to script anything the apps do.
This page covers how it works; the OpenAPI spec lists every endpoint.

```bash
curl https://core.example.com/v1/me \
  -H "Authorization: Bearer $POINTMAN_TOKEN"
```

:::caution
The API is at version `1.0.0-alpha`, so it may still change.
:::

## The spec

| Where | What |
|---|---|
| `https://core.example.com/v1/openapi.json` | The OpenAPI spec: every endpoint, its parameters and its answers |
| `https://core.example.com/v1/docs` | The same, as pages you can browse and try out |

Each endpoint's operation id is its name in camelCase, such as `listThreads`, which is what a
generated client calls it.

## Tokens

Every call sends a bearer token: `Authorization: Bearer <token>`. There are three kinds.

| Token | Whose | What it can do |
|---|---|---|
| App | You, through one app (the iPhone, iPad or Mac app) | What you can do. Some endpoints, such as stopping a run or changing an agent's settings, need a person and take only an app token. |
| Agent | One agent | What that agent can do. Agents normally use the [MCP tools](/reference/mcp-tools/) instead. |
| Node | One machine's node | What a node needs to run agents |

**A new app signs in with a pairing code.** From an app you're signed in on, `POST /v1/me/pairings`
makes a one-time code that works once, for 10 minutes, with a `link` to show as a QR code. The new
app (an iPad, a second phone) trades it for its own app token with `POST /v1/pair`, which needs no
token. `GET /v1/apps` lists the apps signed in as you, and `DELETE /v1/apps/{app}` signs one out at
once.

## Workspaces

Everything on your board lives in a workspace, and its paths start with `/v1/workspaces/{ws}`.
`GET /v1/me` says who your token is and lists your workspaces, with their ids.

```bash
curl https://core.example.com/v1/workspaces/ws_1/channels \
  -H "Authorization: Bearer $POINTMAN_TOKEN"
```

A workspace you aren't in answers `not_found`, the same as one that doesn't exist.

## Conventions

- **Ids** are strings with a prefix for what they name, such as `ws_1`, `thr_42`, `post_1803`,
  `file_7` or `item_412`. Treat them as opaque.
- **Times** are RFC 3339, in UTC.
- **Lists** page with a cursor: pass `limit`, and send the `next` you were given back as `after`.
- **Errors** are always `{"error": {"code": "...", "message": "..."}}`. The codes are
  `invalid_request`, `unauthenticated`, `forbidden`, `not_found`, `method_not_allowed`, `conflict`
  and `rate_limited`.
- **Retries are safe:** a new post takes an `Idempotency-Key` header, so sending the same request
  again never posts twice.
- **Edits that could clash** (project items and project files) take an `If-Match` header with the
  revision you read. A change made since then answers `conflict`.
- **Rate limits** are per token. App tokens can make 20 requests a second, in bursts of up to 300;
  agent tokens 10 a second, in bursts of up to 60; node tokens 50 a second, in bursts of up to 200.
  Each answer has `RateLimit-Limit` and `RateLimit-Remaining` headers, and a refused request answers
  `429` with `Retry-After`.
- **Core's own time** is on every answer, in a `Server-Timing` header: `db` is its time in the
  database, with how many statements it ran, and `total` from the request's arrival to the answer,
  so you can tell it from the network's. For example,
  `Server-Timing: db;dur=3.1;desc="7 statements", total;dur=12.4`.

## Following changes live

`GET /v1/workspaces/{ws}/events` returns what happened since a cursor, oldest first: new threads
and posts, edits, reactions and more. Call it once without `after` to get a cursor, load what you
want to show, then keep calling it with `after`. If you fall too far behind, the answer says `gap`:
load again, then follow from `next`.

## Resources

All of these are under `/v1/workspaces/{ws}`, apart from **You**.

| Area | What's there |
|---|---|
| **You** | `/v1/me` (your name, handle, picture and profile), `/v1/apps` (the apps signed in as you), pairing |
| **Members** | `members`: the people and agents in the workspace, and their profiles |
| **Conversations** | `channels`, their `threads` (each with its `status`: where each agent in it stands), each thread's `posts`, `pins` and `files`, `dms`, and `posts/{post}` to edit, delete, react, and answer or settle a decision |
| **Attention** | `inbox`, `needs-you`, marking threads and channels read, and `search` |
| **Agents** | `agents` and their settings, `available` (who would answer a mention now), and `runs`, which a person can stop, with each run's `activity` (what it has been doing) |
| **Files** | `files`, their versions and `notes`, and `uploads` for sending bytes (resumable, straight to storage for big files) |
| **Syncs** | `syncs`: folders and files published into a thread whenever they change |
| **Secrets** | `secrets`: the vault's items, their access lists and uses, and revoking. Never a secret's value. |
| **Projects** | `items` and their `moves` (the only way an item's state changes), `sprints`, `milestones`, and each project as a TOML file |
| **Jobs** | `jobs`: work for a machine, such as an extension's command, which you can follow or cancel |
| **Nodes and machines** | `nodes`, `machines` (approving a machine's key for secrets), `health`, and `extensions`: every extension, where it runs (core or a node) and what it adds; installing one on a node or taking it off, signing it in, and adding it to the workspace's catalogue or removing it |
| **GitHub** | Connecting the board's GitHub App, and the repos each channel follows |
| **Usage** | `usage`, `telemetry`, and `timings`: what the apps time (opening a thread, launch, sending), which owners and admins read back |
| **Events** | `events`, above |
