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 for setup details.
Base URL
All API requests are made to a single public endpoint:
https://api.contextrepo.comEvery path is prefixed with /v1/. For example, to list your prompts:
https://api.contextrepo.com/v1/promptsResponse Format
All successful responses return a JSON envelope with a data field:
{
"data": {
"id": "abc123",
"title": "My Prompt",
"description": "A helpful code review prompt"
}
}List endpoints include a pagination object alongside data:
{
"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:
{
"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 under components.schemas.ErrorEnvelope. The patterns below cover the recoverable codes:
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());
}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
}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: 86400Preflight 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/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
- Honor
Retry-Afterliterally — never retry sooner than the server tells you. - 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 snippet under Error Format. - 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.
- 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.
- Bucket your token budget. If you're a server-side integration making bursts, pre-emptively pause when
X-RateLimit-Remainingdrops below 5 to avoid the round-trip cost of a429.
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.
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 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
- Send the request. A
202 Acceptedresponse includes the resource identifier (documentId) before the work is finished. - Poll the GET endpoint for that resource (
GET /v1/documents/{id}) every 2–5 s. - The resource transitions through
statusvalues as work completes (pending→processing→completed). Thecontentfield is populated oncestatus === "completed". - Stop polling once
statusis terminal (completedorfailed). Surface failures using the sameErrorEnvelopeshape returned by synchronous endpoints.
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.