> ## Documentation index
> Fetch the complete documentation index at: https://contextrepo.com/docs/llms.txt
> Use it to find every page before exploring further.

# API Reference

> Programmatic access to stored context, retrieval, and reasoning through Context Repo's REST API.

Context Repo exposes a RESTful API for managing prompts, documents,
collections, retrieval, and cited reasoning programmatically. Every endpoint
requires authentication — see [Authentication](https://contextrepo.com/docs/api/authentication) for
setup details.

## Base URL

All API requests are made to a single public endpoint:

```
https://api.contextrepo.com
```

Every path is prefixed with `/v1/`. For example, to list your prompts:

```
https://api.contextrepo.com/v1/prompts
```

## Response Format

All successful responses return a JSON envelope with a `data` field:

```json
{
  "data": {
    "id": "abc123",
    "title": "My Prompt",
    "description": "A helpful code review prompt"
  }
}
```

List endpoints include a `pagination` object alongside `data`:

```json
{
  "data": [
    { "id": "abc123", "title": "My Prompt" }
  ],
  "pagination": {
    "cursor": "eyIxIjoiMjAifQ==",
    "hasMore": true
  }
}
```

Pass the `cursor` value as a query parameter on the next request to fetch the next page.

## Error Format

All errors return a JSON envelope with a single `error` object. The shape is consistent across every endpoint and every status code:

```json
{
  "error": {
    "code": 401,
    "message": "Invalid authentication",
    "details": null
  }
}
```

| Field           | Type                | Description                                                                                                 |
| --------------- | ------------------- | ----------------------------------------------------------------------------------------------------------- |
| `error.code`    | `integer`           | Mirrors the HTTP status code (`400`, `401`, `403`, `404`, `409`, `412`, `413`, `422`, `428`, `429`, `500`). |
| `error.reason`  | `string` (optional) | Stable machine-readable reason on document protocol errors, such as `REVISION_MISMATCH`.                    |
| `error.message` | `string`            | Human-readable description safe to surface to end users.                                                    |
| `error.details` | `any` (nullable)    | Optional structured context — e.g., the field that failed validation. Absent or `null` on most errors.      |

### Status Code Reference

| HTTP  | Recoverable  | Meaning               | Typical Cause                                                                                                           |
| ----- | ------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `400` | After fix    | Bad Request           | Missing required field, malformed JSON, invalid query parameter, bad ID format                                          |
| `401` | After fix    | Unauthorized          | Missing `Authorization` header, expired JWT, revoked API key                                                            |
| `403` | After fix    | Forbidden             | Valid credentials but the API key lacks the required scope                                                              |
| `404` | No           | Not Found             | Resource doesn't exist or the caller has no access                                                                      |
| `409` | After reload | Conflict              | Lease conflict, stale fence, or operation still in progress                                                             |
| `412` | After reload | Precondition Failed   | The document revision changed after you read it                                                                         |
| `413` | After fix    | Payload Too Large     | Request body exceeds the per-endpoint size cap                                                                          |
| `422` | After fix    | Unprocessable Entity  | An idempotency key was reused for different inputs, or the document cannot enter coordination from its current snapshot |
| `428` | After fix    | Precondition Required | A managed write omitted a required ETag, key, or lease header                                                           |
| `429` | Yes — retry  | Rate Limited          | Quota exhausted — wait `Retry-After` seconds, then retry                                                                |
| `500` | Yes — retry  | Internal Server Error | Transient failure — retry with exponential backoff                                                                      |

### Recovery Patterns

The full machine-readable schema lives in [`/openapi.json`](https://contextrepo.com/openapi.json) under `components.schemas.ErrorEnvelope`. The patterns below cover the recoverable codes:

```ts title="Handle 401 (refresh credentials, do not retry)"
if (res.status === 401) {
  // Fetch a fresh Bearer JWT or rotate the API key; never retry the same key
  throw new AuthError(await res.json());
}
```

```ts title="Handle 429 (honor Retry-After)"
if (res.status === 429) {
  const retryAfterSec = Number(res.headers.get("Retry-After") ?? 60);
  await sleep(retryAfterSec * 1000);
  return retry(req); // single retry — see Rate Limiting for the full backoff
}
```

```ts title="Handle 5xx (exponential backoff with jitter)"
async function withBackoff(req: () => Promise<Response>, max = 4) {
  for (let attempt = 0; attempt < max; attempt++) {
    const res = await req();
    if (res.status < 500) return res;
    const base = 2 ** attempt * 500;          // 500ms, 1s, 2s, 4s
    const jitter = Math.random() * 250;        // de-correlate concurrent retries
    await sleep(base + jitter);
  }
  throw new Error("Upstream unavailable after retries");
}
```

## CORS Policy

The API allows requests from all origins. Every response includes these headers:

```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, If-Match, Idempotency-Key, X-ContextRepo-Lease-Id, X-ContextRepo-Lease-Fence
Access-Control-Expose-Headers: ETag, X-ContextRepo-Revision, X-ContextRepo-Content-Hash, X-ContextRepo-Lease-Expires-At, Idempotent-Replayed, Retry-After
Access-Control-Max-Age: 86400
```

Preflight `OPTIONS` requests are handled automatically for all `/v1/` paths and return a `204 No Content` response with CORS headers.

## Rate Limiting

Every request is rate-limited per authenticated identity (per-user JWT or per-API-key). Buckets are sliding 60-second windows; exceeding a bucket returns `429 Rate Limited` with a `Retry-After` hint.

### Per-Tier Limits

| Tier   | Endpoints                                                                                                                                                                              | Limit       | Window |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------ |
| Read   | `GET /v1/documents`, `GET /v1/collections`, `GET /v1/prompts/{id}`, `GET /v1/documents/{id}`, `GET /v1/collections/{id}`, `GET /v1/user/me`, `GET /v1/pd/read/*`, `POST /v1/pd/expand` | **120 req** | 60 s   |
| API    | `POST` / `PATCH` / `PUT` / `DELETE` on all CRUD resources, `GET /v1/prompts`, `GET /v1/search`, `POST /v1/pd/search`, `POST /v1/reason`                                                | **100 req** | 60 s   |
| Scrape | `POST /v1/documents/scrape`                                                                                                                                                            | **10 req**  | 60 s   |

### Response Headers

Rate-limit metadata can appear on non-cacheable responses. On `429`, clients should read `Retry-After` first, then fall back to deriving wait time from `X-RateLimit-Reset` if absent:

| Header                  | Type               | Description                                            |
| ----------------------- | ------------------ | ------------------------------------------------------ |
| `X-RateLimit-Limit`     | integer            | Total requests allowed in the current window           |
| `X-RateLimit-Remaining` | integer            | Requests remaining before throttling kicks in          |
| `X-RateLimit-Reset`     | ISO 8601 timestamp | When the window resets and the budget refills          |
| `Retry-After`           | integer seconds    | Wait this long before retrying (present on `429` only) |

### Example 429 Response

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-05-18T19:01:00.000Z
Retry-After: 38

{
  "error": {
    "code": 429,
    "message": "Rate limit exceeded"
  }
}
```

### Recommended Retry Strategy

1. **Honor `Retry-After` literally** — never retry sooner than the server tells you.
2. **Exponential backoff with jitter** on transient failures (`5xx`, network errors). Start at 500 ms, double per attempt, cap at 8 s, add ±250 ms jitter to de-correlate clients. See the [Recovery Patterns](https://contextrepo.com/docs/api#recovery-patterns) snippet under Error Format.
3. **Cap retry attempts at 4–5.** Beyond that, surface the failure to the caller; the limiter is signalling a sustained backpressure condition, not a transient blip.
4. **Cap concurrency on the Scrape tier.** With 10 req/60 s, more than one or two parallel scrapers per workspace will hit the limit immediately.
5. **Bucket your token budget.** If you're a server-side integration making bursts, pre-emptively pause when `X-RateLimit-Remaining` drops below 5 to avoid the round-trip cost of a `429`.

## API Versioning

The REST surface is versioned with a URL prefix (`/v1/`). Inside a major version, changes are additive only; new majors run side-by-side with the prior one, with a six-month deprecation runway signaled by `Deprecation` and `Sunset` HTTP headers. The current major is `v1` — no version is deprecated today.

The full contract lives at [Versioning & Deprecation Policy](https://contextrepo.com/docs/api/versioning).

## Document Revisions and Coordination

Completed documents expose a canonical revision in their body and response
headers. For PATCH and restore, send body `expectedRevision` or forward the
exact `ETag` from `GET /v1/documents/{id}` as `If-Match` for compare-and-swap protection. Changed writes
advance `revision`; exact unchanged writes return the same compact acknowledgement
with `noop: true`.

PATCH and restore accept either `If-Match` or body `expectedRevision`.
When both are supplied, their revisions must agree and the header's content
hash is also checked. Managed PATCH and restore additionally require
`Idempotency-Key`, `X-ContextRepo-Lease-Id`, and
`X-ContextRepo-Lease-Fence`. Refresh and coordination-policy operations
retain their existing preconditions.

Retry the identical operation with its original revision, key, payload, and
lease fields. Do not replace the revision or key to recover an uncertain
result. Matching receipts are retained for 24 hours from creation.
Reconcile uncertain operations by reading the document and history before
taking further action.

Treat `409` and `412` as a signal to reload state rather than overwriting it.
See the [document endpoints](https://contextrepo.com/docs/api/endpoints#documents) for the full flow.

## Asynchronous Operations

Most endpoints respond synchronously with the final resource state. A small set of operations run in the background and respond with `202 Accepted` plus a handle the client polls:

| Endpoint                    | Async Reason                                    | Handle Field      |
| --------------------------- | ----------------------------------------------- | ----------------- |
| `POST /v1/documents/scrape` | URL extraction can take 10–60 s for large pages | `data.documentId` |

### The Pattern

1. Send the request. A `202 Accepted` response includes the resource identifier (`documentId`) **before** the work is finished.
2. Poll the GET endpoint for that resource (`GET /v1/documents/{id}`) every 2–5 s.
3. The resource transitions through `status` values as work completes (`pending` → `processing` → `completed`). The `content` field is populated once `status === "completed"`.
4. Stop polling once `status` is terminal (`completed` or `failed`). Surface failures using the same `ErrorEnvelope` shape returned by synchronous endpoints.

```ts title="Poll pattern (TypeScript)"
const create = await fetch("https://api.contextrepo.com/v1/documents/scrape", {
  method: "POST",
  headers: { Authorization: `API-Key ${key}`, "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://example.com/article" }),
});
if (create.status !== 202) throw new Error("scrape failed to enqueue");
const { data: { documentId } } = await create.json();

for (let i = 0; i < 30; i++) {           // ~150 s ceiling at 5 s intervals
  await sleep(5_000);
  const poll = await fetch(`https://api.contextrepo.com/v1/documents/${documentId}`, {
    headers: { Authorization: `API-Key ${key}` },
  });
  const { data } = await poll.json();
  if (data.status === "completed") return data;
  if (data.status === "failed") throw new Error(`scrape failed: ${data.status}`);
}
throw new Error("scrape timed out");
```

The Scrape tier is rate-limited at 10 req/60 s — far stricter than the standard Write tier — because each call spawns background work. Cap parallel scrapes at 1–2 per workspace.

## What's Next

- [Authentication](https://contextrepo.com/docs/api/authentication): Set up Bearer JWT or API key authentication for your requests.
- [Endpoints](https://contextrepo.com/docs/api/endpoints): Full reference for every REST endpoint with examples.
