---
title: "REST API"
description: "Call the Overlane API with a bearer token, pin a date version in the Overlane-Version header, handle errors that say whether to retry, and read the OpenAPI 3.1 document the API serves."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.overlane.app/llms.txt
> Use this file to discover all available pages before exploring further.

# REST API

The API lives at `https://api.overlane.app`. Every path that touches team data starts with `/v1/tenants/{tenantId}`, so the team is part of every request and one team can never read another's data.

## Authenticate

Send `Authorization: Bearer <token>`. Any of these works, and each is checked against the database on every request, so a revocation takes effect on the next call:

| Credential | Prefix | Made by |
| --- | --- | --- |
| Team API key | `ovl_k_` | [`overlane api-key create`](/cli/api-key-create/) |
| Agent token | `ovl_a_` | [`overlane auth login --repo`](/cli/auth-login/), see [Agent sign-in](/agent-tokens/) |
| CLI session | `ovl_s_` | [`overlane auth login`](/cli/auth-login/) |
| OAuth access token | none | The MCP and OAuth sign-in flow |

```sh
curl -H "Authorization: Bearer $OVERLANE_KEY" https://api.overlane.app/v1/me
```

`GET /v1/me` answers who the token belongs to: the team, the user, the scopes and whether it is a person or an agent.

## Pin a version

The API version is a date. Send it in the `Overlane-Version` header; leave the header off and you get the current version. Every answer repeats the version it was served at.

```sh
curl -H "Authorization: Bearer $OVERLANE_KEY" -H "Overlane-Version: 2026-10-10" https://api.overlane.app/v1/me
overlane api versions
```

A date that does not exist is refused with `400` and the versions that do. [API versions](/developers/versions/) explains how a breaking change ships and how long an old version keeps working.

```js run
assert.equal(resolveApiVersion(undefined).version, CURRENT_API_VERSION);
assert.equal(resolveApiVersion(CURRENT_API_VERSION).ok, true);
const unknown = resolveApiVersion("2020-01-01");
assert.equal(unknown.ok, false);
assert.equal(unknown.code, "unsupported_version");
assert.equal(resolveApiVersion("latest").code, "invalid_version");
```

## Errors

Every error body has the same shape, and the connector, key and webhook routes add `retryable`:

```json
{ "error": { "code": "rate_limited", "message": "more than 60 requests in one minute for this team's plan; retry in 12 seconds", "hint": "wait for the window to end; a paid plan has a higher limit", "retryable": true } }
```

- `code` is stable and safe to branch on. `message` is for people.
- `hint` names the next step, often a command.
- `retryable` says whether the same request may succeed later without a change: true for `408`, `429` and server errors.
- A refusal from the [autonomy gate](/autonomy/) adds `approval` with the class, what it needs and the subject to approve.

The CLI maps these to exit codes: 3 for sign-in, 4 not found, 6 blocked by policy or an approval, 7 retry later.

```js run
const error = DeveloperError.parse({ error: { code: "rate_limited", message: "slow down", hint: "retry in 5 seconds", retryable: true } });
assert.equal(error.error.retryable, true);
assert.equal(retryableStatus(429), true);
assert.equal(retryableStatus(403), false);
```

## Rate limits

A call made with a team API key counts against the team's per-minute limit and the key's own cap. Over the limit you get `429`, `Retry-After` and the `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` headers. See [API keys and rate limits](/developers/api-keys-and-limits/).

## Writes are safe to repeat

Task and issue writes take a `requestId`; sending the same one again returns the first result. Connector actions and webhook events carry their own idempotency keys: a provider event ID, and the webhook event ID.

## The OpenAPI document

The API serves its own description at `/v1/openapi.json` (a GET with any valid credential). It is generated from the same route tables the role middleware reads and from the contracts, so it cannot describe a route that is not there. A test checks both ways: every route is an operation and every operation is a route that answers. Each operation carries:

- `x-overlane-route`, the route ID;
- `x-overlane-action`, the role right it needs;
- `x-overlane-autonomy-class` when the autonomy gate asks about it;
- `x-overlane-human-only` for payments and legal terms.

```sh
overlane api openapi
overlane api openapi --full --json
```

The document is committed at one snapshot per version, and the [TypeScript SDK](/developers/sdk/) and the [API reference](/api/) page are generated from it, so both list every operation at the current version. A few public endpoints (CLI sign-in, the CI runner broker, the release signing key, the status page data and inbound webhooks) are not operations of the document; they are on [Endpoints outside the SDK](/developers/outside-the-sdk/).

Source: https://docs.overlane.app/developers/rest-api/index.mdx
