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

# API conventions

> Base URL, authentication, client setup, asynchronous starts, idempotency, pagination, errors, rate limits, and request ids for the Managed Agents API.

Every Managed Agents operation follows the same rules for authentication, retries, paging, and errors. Read this page once, then use the **Endpoints** reference for each operation's exact schema.

## Base URL

Every operation lives under one base path:

```text theme={"theme":"css-variables"}
https://api.recursion.labelbox.com/managed-agents/v1
```

Requests and responses are JSON. Most fields use `snake_case`. A few responses, such as the model list, use `camelCase`, so follow each operation's schema in **Endpoints**.

## Authentication

Send an API key as a bearer token on every request:

```http theme={"theme":"css-variables"}
Authorization: Bearer rma_...
```

Create the key in the console under **API keys**. See [API keys](/managed-agents/api-keys) for scopes, expiry, and safe storage.

| Key scope    | Extra header                                                                                       | Where requests act                                      |
| ------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| Organization | None. You may send `x-organization-id`, but it must name the key's organization.                   | The key's organization.                                 |
| Tenant       | `x-organization-id: <organization id>` or `x-organization-id: default`. Required on every request. | The organization the header names, if you belong to it. |

A key acts as you, with the role you hold in that organization at the moment of the request. If an admin changes your role, your keys follow on the next request. Once the organization is resolved, the response carries `recursion-organization-id` and `recursion-tenant-id` headers that name the scope the request ran in.

| Response              | Cause                                                                                                                                          |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 unauthorized`    | The key is missing, malformed, revoked, or expired, it belongs to a person who left the tenant, or its organization was archived.              |
| `400 invalid_request` | A tenant-scoped key sent no `x-organization-id`.                                                                                               |
| `404 not_found`       | `x-organization-id` names an organization you can't reach, an archived one, or a different organization than an organization-scoped key's own. |
| `403 forbidden`       | Your role in the organization doesn't allow the operation. See [Organizations and roles](/managed-agents/organizations-and-roles).             |

## Install the SDK

The TypeScript SDK is `@labelbox/recursion-sdk`:

```bash theme={"theme":"css-variables"}
npm install @labelbox/recursion-sdk
```

From Python or any other language, call the REST API directly with your usual HTTP client. Every page shows each request as cURL, and **Endpoints** has each operation's exact schema. See [Python and other languages](#python-and-other-languages).

## Client setup

Export your key, then create one client for your process. Every TypeScript sample in these docs assumes the `rl` client below. Always pass `baseUrl` as shown.

```bash theme={"theme":"css-variables"}
export RECURSION_API_KEY='rma_...'
```

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    import { createRecursionClient } from '@labelbox/recursion-sdk';

    const rl = createRecursionClient({
      apiKey: process.env.RECURSION_API_KEY!,
      baseUrl: 'https://api.recursion.labelbox.com',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/models' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

A successful call returns the models you can use in agents:

```json theme={"theme":"css-variables"}
{
  "items": [
    {
      "modelId": "anthropic/claude-sonnet-5",
      "displayName": "claude-sonnet-5",
      "family": "anthropic",
      "supportsImageInput": true
    }
  ]
}
```

### Tenant-scoped keys

A tenant-scoped key must name the organization on every request. Use an organization id, or `default` for your tenant's default organization.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    import { createRecursionClient } from '@labelbox/recursion-sdk';

    const rl = createRecursionClient({
      apiKey: process.env.RECURSION_API_KEY!,
      baseUrl: 'https://api.recursion.labelbox.com',
      headers: { 'x-organization-id': 'default' },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/models' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'x-organization-id: default'
    ```
  </Tab>
</Tabs>

### Call shape

| Surface    | Shape                                                                                                                                         |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| TypeScript | `await rl.managedAgents.<operationId>({ ...pathAndQuery, body })`. Path and query values are top-level keys; the JSON body goes under `body`. |
| REST       | `https://api.recursion.labelbox.com/managed-agents/v1/<path>` with a JSON body and `Content-Type: application/json`.                          |

Send every body as JSON. A body in a media type the API doesn't accept returns `415 unsupported_media_type`. Skill bundle uploads are the one exception; they use `multipart/form-data`. See [Skills](/managed-agents/skills).

A few operations, such as `getSkillVersionContent` and `getSessionImage`, have no TypeScript method. Call them over REST, which works from any language.

### Python and other languages

There is no Python SDK for this API. Send the same REST requests the cURL samples show, with the bearer header on each one. For example, with `httpx`:

```python theme={"theme":"css-variables"}
import os

import httpx

api = httpx.Client(
    base_url="https://api.recursion.labelbox.com/managed-agents/v1",
    headers={"Authorization": f"Bearer {os.environ['RECURSION_API_KEY']}"},
)
models = api.get("/models").json()
```

Translate any cURL sample the same way: the path follows `/managed-agents/v1`, `-d` becomes a JSON body, and each `-H` becomes a header, such as `Idempotency-Key` on `startSession`.

## Asynchronous session starts

`startSession` returns `202 Accepted` as soon as the session is recorded. It doesn't wait for the sandbox or the agent.

```json theme={"theme":"css-variables"}
{
  "session_id": "b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38",
  "status_path": "/v1/sessions/b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38"
}
```

Follow the session with `getSession` or by [streaming its events](/managed-agents/events). The session record is authoritative. The event stream is written separately and can lag it, so an empty event list doesn't mean the session is still starting.

What success means depends on the work:

| Work             | Done when                                                                                                                                                                    |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Graded session   | The newest outcome has `status: "terminal"` and `terminal_result: "satisfied"`. The session's `execution_state` becomes `completed` with `stop_reason: "outcome_satisfied"`. |
| Ungraded session | `execution_state` is `idle` or `completed` and `stop_reason` says why. `end_turn` means the agent finished its turn, not that its work is correct.                           |
| Failed session   | `status` is `failed`. Read `failure.code`, `failure.category`, and `failure.retryable`. See [Errors](/managed-agents/errors#session-failure-codes).                          |

## Idempotent creates

`startSession` requires an `Idempotency-Key` header, and `createAgent` accepts one. Use a stable key for each logical create, so a retry after a timeout or dropped connection can't create a second session or agent.

| Retry                                              | Result                                                                              |
| -------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Same key, same method, path, query, and body bytes | The original successful response is replayed with `Idempotency-Replayed: true`.     |
| Same key, any different request                    | `409 idempotency_conflict`.                                                         |
| Same key while the first request is still running  | `409 idempotency_in_progress` with `Retry-After`.                                   |
| The key couldn't be checked                        | `503 idempotency_unavailable`. The request did not run, so retry with the same key. |

Keys hold 1 to 256 visible ASCII characters, sent as exactly one header value. A malformed or repeated header returns `400 invalid_request`. Keys share one namespace per organization. A completed key is remembered for about 24 hours, and an abandoned in-progress key can be reused after about 1 hour.

In TypeScript, pass `'Idempotency-Key'` at the top level of the operation input. Over REST, send the `Idempotency-Key` header. Retry with the same bytes: reordering JSON keys counts as a different request.

`createVault` takes its retry key in the body instead, as `idempotency_key`, up to 128 characters.

## Pagination

Paged list operations return a continuation token. Each operation documents its own default and maximum page size.

| Field             | Use                                                                           |
| ----------------- | ----------------------------------------------------------------------------- |
| `limit`           | Maximum items to return in one page.                                          |
| `page_token`      | The previous response's `next_page_token`. Keep the same filters and `limit`. |
| `next_page_token` | Present when more items exist. Stop when it's absent or empty.                |
| `after_event_id`  | Continue `listSessionEvents`. Pass that response's `next_page_token` here.    |

A page can hold fewer items than `limit` and still have a next token, because event pages also have a size budget. A session-list token is valid for one hour and only with the filters and `limit` it was issued for. A token that's no longer valid returns `400 invalid_request` with `details.field` set to `page_token`, so restart the list without it.

## Errors

Every error response has the same flat shape:

```json theme={"theme":"css-variables"}
{
  "code": "invalid_request",
  "message": "agent_id is required",
  "details": {
    "field": "agent_id",
    "requestId": "0b6f2c1d-9a4e-4b7f-8c3d-5e6a7b8c9d0e"
  }
}
```

| Field               | Use                                                                                                                      |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `code`              | Stable, machine-readable. Branch on this.                                                                                |
| `message`           | Human-readable and can change. Don't match on it.                                                                        |
| `details.field`     | The request field, query parameter, or header at fault, when there is one.                                               |
| `details.requestId` | The request id. Include it when you report a problem.                                                                    |
| `details.retryable` | `true` when the same request can succeed later. Some `429` and `503` responses omit it; retry those with backoff anyway. |

The TypeScript client throws on any non-`2xx` response. REST callers check the HTTP status and `code`. See [Errors](/managed-agents/errors) for every code and what to do about it.

## Rate limits

Requests are limited per person, across all your keys and console use. Event streams are also limited in how many can be open at once. Numeric limits aren't published and can change.

| Response                              | Meaning                                       | What to do                                                 |
| ------------------------------------- | --------------------------------------------- | ---------------------------------------------------------- |
| `429 rate_limit_exceeded`             | You sent too many requests in a short window. | Wait for `Retry-After`, then retry.                        |
| `503 service_unavailable` on a stream | The service is at stream capacity.            | Close streams you no longer need, then retry with backoff. |

When `Retry-After` is absent, back off exponentially with jitter, starting around one second. See [Limits](/managed-agents/limits) for every other limit.

## Request ids

Every response carries an `x-request-id` header, and error bodies repeat it as `details.requestId`. You can send your own `x-request-id` of up to 128 letters, digits, `_`, `.`, `:`, or `-`, and it's echoed back so you can match it to your own logs.

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" href="/managed-agents/quickstart">
    Create an environment and an agent, run a graded session, and read the verdict.
  </Card>

  <Card title="Errors" href="/managed-agents/errors">
    Look up every error code with its cause, fix, and retry guidance.
  </Card>

  <Card title="Limits" href="/managed-agents/limits">
    Find every size, count, and time limit in one place.
  </Card>

  <Card title="Troubleshooting" href="/managed-agents/troubleshooting">
    Match a symptom to its cause and fix.
  </Card>
</CardGroup>
