---
title: "Cork API reference (REST v1): auth, rate limits, idempotency and async jobs · Cork"
description: "The Cork API reference: the REST API v1 at api.corkcli.com/api/v1, with OAuth 2.1 bearer tokens, per-caller rate limits and 429s, Idempotency-Key retries, 202 async jobs, errors and the OpenAPI 3.1 spec."
canonical: "https://corkcli.com/docs/api"
last_updated: "2026-11-14"
---

# Cork API reference (REST v1)

The Cork API is the REST API (v1) at `https://api.corkcli.com/api/v1` that the dashboard, SDKs and integrations call: workspaces, repos, members, live activity, the ledger, costs, messages between people and agents, notes, deferred work, and long-running jobs. Agents usually reach Cork through its MCP server (https://mcp.corkcli.com, see [Cork MCP server](https://corkcli.com/docs/mcp-tools.md)) instead. The full contract is the [OpenAPI spec](https://corkcli.com/openapi.json).

## Base URL and versioning

`https://api.corkcli.com/api/v1`. Every response carries `API-Version: 1`. The unversioned `/api/...` paths are deprecated aliases of the same handlers and keep working; their responses carry a `Deprecation` header and a `Link` to the v1 path. See the [versioning and deprecation policy](https://corkcli.com/docs/api/versioning.md).

```bash
curl -s https://api.corkcli.com/api/v1/health
curl -s https://api.corkcli.com/api/v1/plans
```

## Authentication

Send `Authorization: Bearer <token>`. A token is one of:

- an OAuth 2.1 access token (`cat_...`) from the authorization code flow with PKCE (S256) at `https://mcp.corkcli.com`. Clients register themselves with dynamic client registration at `https://mcp.corkcli.com/register` (RFC 7591) or a Client ID Metadata Document. The one scope is `collide`. Access tokens last 24 hours; refresh them with the `crt_...` refresh token;
- the machine credential `cork login` writes to `~/.cork/credentials.json`;
- a dashboard sign-in token.

Sign-up is self-serve, and so is the OAuth flow: there is no API key to request and no form. Step by step: [auth.md](https://corkcli.com/auth.md).

```bash
curl -s https://api.corkcli.com/api/v1/workspaces \
  -H "Authorization: Bearer $CORK_TOKEN"
```

## Plans and the free tier

`GET /api/v1/plans` needs no sign-in. It returns the plans from the server's own plan table, Free included, and how to get credentials: the OAuth metadata, the registration endpoint and the scope. Cork also runs free, entirely on one machine, with no account: `curl -fsSL https://corkcli.com/install.sh | sh`. Prices: [pricing.md](https://corkcli.com/pricing.md).

## Sandbox and test environment

There is no hosted sandbox and no test-mode key: `https://api.corkcli.com` is production. The sandbox is Cork's free local mode, the same server and MCP tools on `127.0.0.1` with no account, no key and no production data; it has no REST API, so try the REST API in a workspace of your own on Free. The endpoints that need no key are listed there too: [Sandbox and test environment](https://corkcli.com/docs/sandbox.md).

## Rate limits

REST routes are limited per caller (per credential, or per client address without one) by a token bucket of 600 requests that refills at 10 per second. Agent routes (memory, intents, messages, the inbox) are held to the workspace's plan instead: Free 8 per second with a burst of 120, the paid plan (agent hours) 60 per second with a burst of 600, Enterprise unmetered. Usage reports, a session's token bill, are never limited.

Every limited response says where you stand:

```http
RateLimit-Policy: "rest";q=600;w=60
RateLimit: "rest";r=598;t=1
RateLimit-Limit: 600
RateLimit-Remaining: 598
RateLimit-Reset: 1
```

Over the limit the answer is `429 Too Many Requests` with `Retry-After`:

```json
{"error": "rate limited: retry after 2 seconds", "code": "rate_limited", "retry_after_s": 2}
```

## Idempotency

Send `Idempotency-Key` (1 to 255 characters) on any POST, PUT, PATCH or DELETE and a retry is safe. Keys belong to the authenticated caller and are kept for 24 hours.

- The same key and request: the first response again, with `Idempotent-Replayed: true`.
- The same key with a different method, path or body: `422`, `code: idempotency_key_reused`.
- While the first request is still running: `409`, `code: idempotency_key_in_progress`, with `Retry-After`.
- 5xx answers are not stored, so a retry runs again. Without a credential the header is ignored.

```bash
curl -s https://api.corkcli.com/api/v1/memory/save \
  -H "Authorization: Bearer $CORK_TOKEN" \
  -H "Idempotency-Key: 6f1c2a8e-remember-retry" \
  -H "Content-Type: application/json" \
  -d '{"repo_id": "github.com/acme/api", "fact": "Payments retries are capped at 3", "anchor": "payments/retry.py"}'
```

## Async jobs

Operations that can take long run as jobs: exporting a large repo's graph (`graph-export`), the waste report over many weeks (`waste-report`), clearing a repo's map or notes (`repo-clear`), waiting for messages beyond the inbox's 25-second hold (`inbox-wait`), and waiting for a repo's push turn (`land-turn`).

### Start one

```bash
curl -si https://api.corkcli.com/api/v1/jobs \
  -H "Authorization: Bearer $CORK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"operation": "waste-report", "params": {"workspace_id": "ws_123", "weeks": 26}}'
```

```http
HTTP/2 202
location: /api/v1/jobs/job_8c1d...
retry-after: 2

{"job_id": "job_8c1d...", "status": "queued", "poll_after_s": 2, "status_url": "/api/v1/jobs/job_8c1d..."}
```

Or ask the endpoint itself with `Prefer: respond-async` (RFC 7240): the graph export, the waste report, clearing a repo and the inbox wait accept it, and answer the same 202 with `Preference-Applied: respond-async`. Without the header they answer synchronously as before.

### Poll it

```bash
curl -s https://api.corkcli.com/api/v1/jobs/job_8c1d... -H "Authorization: Bearer $CORK_TOKEN"
```

```json
{"job_id": "job_8c1d...", "operation": "waste-report", "status": "succeeded",
 "result_status": 200, "result_url": "/api/v1/jobs/job_8c1d.../result"}
```

`status` is `queued`, `running`, `succeeded`, `failed` or `cancelled`. While it runs, `Retry-After` says when to ask again. `GET /api/v1/jobs/{id}/result` returns the operation's own status and body; small results are also inline as `result`. `DELETE /api/v1/jobs/{id}` cancels a job that has not finished. `GET /api/v1/jobs` lists yours. A job belongs to the caller who started it and is kept for 24 hours.

## Errors

Errors are JSON with an `error` message. Newer endpoints add a machine-readable `code`: `rate_limited`, `idempotency_key_reused`, `idempotency_key_in_progress`, `not_found`, `unauthorized`, `invalid_request`. Statuses mean what HTTP says: 400 a bad request, 401 no valid credential, 403 signed in but not allowed (not a member, or no seat for that repo), 404 not found, 409 and 422 for idempotency, 429 rate limited.

One exception, kept for the hooks: the agent routes reached by their old unversioned paths (`/report`, `/gate`...) answer `200` with `ok: false` and a `reason`, so that an agent is never blocked by Cork. Under `/api/v1` they use real status codes.

## Machine-readable

- [openapi.json](https://corkcli.com/openapi.json): every operation, parameter, header and error shape (OpenAPI 3.1)
- [/.well-known/api-catalog](https://corkcli.com/.well-known/api-catalog): the API catalog (RFC 9727)
- [/.well-known/mcp.json](https://corkcli.com/.well-known/mcp.json): the MCP server's tools
- [/status.json](https://corkcli.com/status.json): whether the service is up (below)

## Service status

[corkcli.com/status](https://corkcli.com/status) shows each part of Cork now and since checks began, with incidents. The same answer, for machines, needs no credential:

```bash
curl -s https://corkcli.com/status.json
```

Its shape is close to Statuspage's, so tools that read those read this:

```json
{
  "schema_version": 1,
  "page": {"id": "collide", "name": "Cork", "url": "https://corkcli.com/status", "updated_at": "…"},
  "status": {"indicator": "none", "description": "All systems operational"},
  "components": [{
    "id": "api", "name": "API and MCP server", "description": "what is checked",
    "status": "operational", "updated_at": "…", "response_ms": 118,
    "checks": [{"id": "mcp", "name": "…", "status": "up", "ms": 98, "from": "website", "kind": "external"}],
    "uptime": {"window_days": 90, "tracking_since": "…", "percent": 99.98,
               "observed_minutes": 1440, "down_minutes": 1, "unrecorded_minutes": 1, "degraded_minutes": 0},
    "days": [{"date": "2026-10-22", "status": "operational", "uptime": 100}]
  }],
  "incidents": [{"id": "…", "name": "…", "status": "monitoring", "created_at": "…", "updated_at": "…",
                 "resolved_at": null, "shortlink": "…", "components": [{"id": "api", "name": "…"}],
                 "incident_updates": [{"id": "…", "status": "monitoring", "body": "…", "created_at": "…"}]}],
  "server": {"reachable": true, "commit": "…", "hooks_version": 83, "last_check_at": "…", "tracking_since": "…"},
  "checked_from": ["server", "website"]
}
```

- `status.indicator`: `none`, `minor` (a component degraded), `major` (a component partly or fully down) or `critical` (the website or the API and MCP server fully down, or half the components down).
- `components[].status`: `operational` (every check passes), `degraded_performance` (one is slow), `partial_outage` (some fail) or `major_outage` (all fail). Components: website, api, oauth, hooks, install, embeddings, dashboard, and email when it is on.
- `uptime.percent` counts only recorded minutes, from `tracking_since`; a minute with no check recorded is down for everything the server serves. `days[].status` is `no_data` before checks began, never operational.
- Two vantage points: the server records a self-check every minute (`GET https://api.corkcli.com/api/v1/status` is that record alone), and the website probes the public addresses itself, at most every 45 seconds. When the server cannot be reached, status.json still answers, with `server.reachable: false`.
- Also as [status.md](https://corkcli.com/status.md), and incidents as an [Atom feed](https://corkcli.com/status/feed.xml).

## Questions

### What is the Cork API?

The Cork API is Cork's REST API, version 1, at `https://api.corkcli.com/api/v1`. The dashboard, the Python SDK and integrations call it to read and change workspaces, repos, members, activity, costs, messages and notes, and to run long jobs. Every response carries `API-Version: 1`.

### How do I authenticate to the Cork API?

Send `Authorization: Bearer <token>` with an OAuth 2.1 access token from the authorization code flow with PKCE at `https://mcp.corkcli.com` (clients register themselves; the scope is `collide`), or the machine credential `cork login` writes. There is no API key to request.

### Is there an OpenAPI spec for the Cork API?

Yes: OpenAPI 3.1 at [https://corkcli.com/openapi.json](https://corkcli.com/openapi.json), also listed in the API catalog at [/.well-known/api-catalog](https://corkcli.com/.well-known/api-catalog).

### What are the Cork API's rate limits?

Per caller, a bucket of 600 requests that refills at 10 a second. Agent routes follow the workspace's plan instead: Free 8 a second, the paid plan (agent hours) 60 a second. Every limited response carries `RateLimit` headers, and over the limit the answer is `429` with `Retry-After`.

### How do long-running calls work in the Cork API?

`POST /api/v1/jobs`, or `Prefer: respond-async` on a long route, answers `202 Accepted` with `Location: /api/v1/jobs/{id}`. Poll that address until the job has succeeded or failed, then read `/api/v1/jobs/{id}/result`; `DELETE` cancels it.

## More

- [Cork MCP server](https://corkcli.com/docs/mcp-tools.md) · [Cork CLI](https://corkcli.com/docs/cli.md) · [Python SDK](https://corkcli.com/docs/python.md)
- [Versioning and deprecation policy](https://corkcli.com/docs/api/versioning.md) · [auth.md](https://corkcli.com/auth.md) · [llms.txt](https://corkcli.com/llms.txt)
