---
title: "REST API"
description: "Conventions, errors, idempotency, rate limits, and where to find the generated reference."
canonical_url: "https://www.agent-identity.dev/docs/api"
markdown_url: "https://www.agent-identity.dev/docs/api.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 900
  task: "Call the REST API directly and handle its errors, idempotency, and rate limits correctly."
  outcome: "Requests carry a bearer token, mutations that must not double-apply carry an idempotency key, and error bodies are read by code rather than message."
  appliesTo:
    package:
      - "@aid/api"
  prerequisites:
    - "An API key of the right scope for the operation."
  files:
    - "apps/api/src/app.ts"
  sideEffects:
    - "Rate limits are enforced per organization, and separately per IP for unauthenticated routes."
  verification:
    - description: "Confirm the service is ready, not merely running."
      expect: "GET /v1/ready returns 200; it fails when the API cannot reach its database."
  rollback:
    - "There is no global undo. Suspend an agent to stop its activity, or revoke the key used."
  failureModes:
    - symptom: "429 rate_limit.exceeded."
      resolution: "Honour Retry-After and back off. Unauthenticated routes such as org bootstrap and login have a much lower limit than authenticated ones."
    - symptom: "409 idempotency.conflict."
      resolution: "The same idempotency-key was reused with a different body. Use a fresh key, or resend the original body verbatim."
---

# REST API
URL: /docs/api
LLM index: /llms.txt
Description: Conventions, errors, idempotency, rate limits, and where to find the generated reference.
Related: /docs/getting-started/authentication, /docs/sdk, /docs/concepts/events

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Call the REST API directly and handle its errors, idempotency, and rate limits correctly.
Outcome: Requests carry a bearer token, mutations that must not double-apply carry an idempotency key, and error bodies are read by code rather than message.

### Applies To

- Package: `@aid/api`

### Prerequisites

- An API key of the right scope for the operation.

### Files

- `apps/api/src/app.ts`

### Side Effects

- Rate limits are enforced per organization, and separately per IP for unauthenticated routes.

### Verification

- Confirm the service is ready, not merely running.
  - Expected: GET /v1/ready returns 200; it fails when the API cannot reach its database.

### Rollback

- There is no global undo. Suspend an agent to stop its activity, or revoke the key used.

### Failure Modes

- 429 rate_limit.exceeded. — Recovery: Honour Retry-After and back off. Unauthenticated routes such as org bootstrap and login have a much lower limit than authenticated ones.
- 409 idempotency.conflict. — Recovery: The same idempotency-key was reused with a different body. Use a fresh key, or resend the original body verbatim.
<!-- farming-labs:agent-contract:end -->

# REST API

Most people should use the [TypeScript SDK](/docs/sdk), which wraps everything here. This
page is for calling the API directly, or for understanding what the SDK does underneath.

The base URL is `https://api.agent-identity.dev`. The full, always-current reference
lives at **[/api-reference](/api-reference)**, generated from the service's own OpenAPI
document, which needs no authentication:

```bash title="terminal"
curl -sS https://api.agent-identity.dev/v1/openapi.json
```

Read that rather than any endpoint list written by hand — including this page, which
covers the conventions the generated reference does not explain.

## A first request

```bash title="terminal"
curl -sS https://api.agent-identity.dev/v1/me \
  -H "authorization: Bearer $AID_KEY"
```

```json
{ "actor_type": "user", "actor_id": "…", "org_id": "…", "org_slug": "acme", "org_name": "Acme" }
```

The SDK equivalent is `await client.whoami()`, which returns the same fields in
`camelCase`.

## Conventions

Everything is under `/v1`. Request and response bodies are JSON in `snake_case`; the
[TypeScript SDK](/docs/sdk) presents `camelCase` and converts at the boundary.

Authentication is `Authorization: Bearer <api-key>`. See
[Authentication](/docs/getting-started/authentication) for the org-key versus agent-key
distinction, which is behind most permission errors.

Unauthenticated routes, deliberately: `POST /v1/organizations`, `POST /v1/auth/signup`,
`POST /v1/auth/login`, `GET /v1/health`, `GET /v1/ready`, `GET /v1/openapi.json`,
`GET /v1/identities/:id/a2a/agent-card.json`, and `POST /v1/webhooks/mail/inbound` — the
last of which is authenticated by the provider's signature over the raw body instead.

## Status codes

| Code | Meaning here |
| --- | --- |
| `200` | Read succeeded. A timed-out event wait is also `200`. |
| `201` | Resource created. |
| `202` | Accepted for async processing — mail sends in particular. |
| `400` | Validation failed. |
| `401` | No usable actor: missing, malformed, revoked, or foreign key. |
| `403` | Recognised actor, not allowed to do this. |
| `404` | Not found, *or* not yours — ownership failures are hidden as absence. |
| `409` | Idempotency conflict: same key, different body. |
| `429` | Rate limited. |
| `503` | `/v1/ready` only: the database is unreachable. |

## Errors

```json
{
  "error": {
    "code": "auth.unauthorized",
    "message": "Missing or invalid API key",
    "request_id": "req-…",
    "retryable": false
  }
}
```

`retryable` is set by the server. Honour it rather than deciding from the status code.
`request_id` correlates with the API's own logs and is what to quote in a bug report.

## Idempotency

Mutating endpoints accept an `idempotency-key` header. Same key and same body replays the
original result; same key with a different body is `409`. Derive the key from whatever
caused the request — an order id, an inbound message id — so that retrying the whole
operation produces the same key.

## Rate limits

Authenticated requests are limited per organization (600 per minute). Unauthenticated
ones are limited per IP (30 per minute), shared across signup, login,
org bootstrap, the public agent card, and the inbound mail webhook before its signature is
checked — a deliberately shared bucket, so that one of those routes cannot be used to
exhaust another's budget. Both return `429` with a `Retry-After` header; the SDK honours it for you.

## Health

`GET /v1/health` answers as soon as the process is up. `GET /v1/ready` also round-trips a
query against Postgres and returns `503 {"status":"unavailable"}` when it cannot. Point
load balancers at `/v1/ready`.

Discovery order for this product's API, from most to least authoritative:

1. `GET {API_URL}/v1/openapi.json` — the generated spec, including every request and
   response schema. Always prefer it to prose.
2. This docs site's `/api-reference`, which renders that same document.
3. Narrative pages here, which explain conventions and sequencing the spec cannot.

Notes that are not visible in the OpenAPI document:

- `404` is returned for resources that exist but belong to another agent or
  organization — vault secrets, A2A tasks, tunnels. Do not treat `404` as proof of
  non-existence, and do not retry it as if it were transient.
- `POST /v1/identities/:id/mail/send` returns `202`, not `200` or `201`. Success means
  queued, not delivered; the outcome arrives as a `mail.delivered` / `mail.bounced` event.
- `GET /v1/events/wait` returns `200` on timeout with `{"event": null, "timed_out": true}`.
  Branch on the body, not the status.
- There is no `PATCH` or `DELETE` for `/v1/identities/:id`. Use suspend and reinstate.
- Pagination is `limit` plus a `before` timestamp, not offsets or cursors, on
  `/v1/events`, `/v1/webhooks/deliveries`, and `/v1/vault/leases`.
- The inbound mail webhook returns `200` with counters even when nothing was ingested.
  Check `ingested`, `unroutable`, and `blocked` rather than the status code.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
