# CircleCI API V3 conventions

CircleCI's V3 API. Every route is served under `/api/v3` on `https://circleci.com`.

No standard is implemented. V3 takes the ideas that work from established API designs and combines them into one coherent whole, but it is not JSON:API, not `problem+json`, and not HAL. Familiar names like `data`, `attributes`, `source.pointer` and `filter[...]` carry no behaviour from those specs. Take everything from this document, and do not use a standard-conformant client library.

## Conventions

### Auth

`Authorization: Bearer <token>`. A token in a query parameter is rejected.

### Ids

Ids are UUIDs, never slugs or names. Some collections resolve a slug via `filter[slug]`.

### One resource

`{"data": {"id", "attributes", "references"}}`. Each entry in `references` is an `id`, or an array of them, sometimes carrying a nested `attributes` object of denormalised fields from the referenced resource. Read the operation's schema for which fields those are, and fetch the resource itself for anything not listed - there is no way to request more inline.

### A collection

`{"data": [...], "page": {"next", "prev"}}`. `page.next` and `page.prev` are opaque cursor values, not URLs - send one back as `page[cursor]`, unmodified, and stop when `page.next` is `null`. `page[limit]` bounds are per-operation, and out of range is a 400, not a clamp.

### Scoping

Scoping uses `filter[...]`. Most list operations require a scope filter such as `filter[org_id]` or `filter[project_id]` and return 400 without it, so read the operation's parameters.

### Methods

`GET` reads, `POST` creates and performs named actions and partial updates, `PUT` replaces in full, `DELETE` removes. There is no `PATCH`.

### Retrying

`GET`, `PUT` and `DELETE` are idempotent. V3 aims for idempotent `POST` wherever an operation allows it, but never assume a given `POST` is replayable: retrying one that timed out can create a second resource or fire an action twice. Retry a `POST` only if its own description says it is idempotent - otherwise re-read the collection to find out whether the first attempt landed.

### Destructive operations

`DELETE`, and `POST` actions such as `cancel`, `delete`, `revoke` and `purge`, take effect immediately and normally cannot be undone. Treat them as irreversible: no operation here reverses another, and there is no dry-run mode. Where recovery is possible at all it is a support matter, not an API call.

### Errors

Errors return a single `error` object, never an `errors` array: `{"error": {"type", "id", "title", "detail", "source"}}`. Branch on the status code; every member is optional. `error.type`, where present, separates causes within one status and is a bare token, not a URI. Never branch on `title` or `detail`. Quote `error.id` in support requests. Invalid input is always 400, never 422.

### Rate limits

On 429, wait the `Retry-After` seconds, then back off exponentially. Responses carry `RateLimit-Policy` and `RateLimit`, the policy and the current state against it (IETF draft-ietf-httpapi-ratelimit-headers). Limits are per route and are not published: read the headers rather than assuming a figure.

### Caching

Responses carry an `ETag` and a `Cache-Control` directive. Where a response has an `ETag`, send it back as `If-None-Match` to make the request conditional and get a 304 when nothing has changed.

### Deprecation

A deprecated operation is marked `deprecated: true` and its responses carry `Deprecation`, whose value is `true` (RFC 9745), `Sunset`, the HTTP-date when the endpoint will be removed (RFC 8594), and `Link` with `rel="deprecation"`, pointing at the migration documentation (RFC 8288). Migrate before the `Sunset` date. A removed route returns 410, permanently.

### Additions

Response fields are added without a version change. Ignore members you do not recognise rather than failing to parse.

