Authentication
Authenticate API requests using a Bearer JWT session token or an API key from your Context Repo dashboard.
Every API request requires an Authorization header. Context Repo supports two authentication methods: Bearer JWT for session-based access and API-Key for programmatic integrations.
Bearer JWT
Use a session JWT token when making requests on behalf of a signed-in user. This is the same token your browser uses when you're logged into the dashboard.
Header format:
Authorization: Bearer <session_jwt_token>The token must be a valid, non-expired session token issued by your signed-in dashboard session. Browser-based integrations built on top of Context Repo retrieve this token from their existing authenticated session — refer to your auth provider's documentation for the exact retrieval call.
Bearer tokens have full access to all resources owned by the authenticated user — they aren't restricted by permission scopes.
Bearer JWT authentication is primarily for browser-based, signed-in usage. For scripts, CLI tools, MCP clients, and server-to-server integrations, use an API key — it's simpler to set up and longer-lived.
API Key
API keys provide scoped, long-lived access for external tools, scripts, and integrations like the Chrome Extension and MCP clients.
Header format:
Authorization: API-Key <your_api_key>The header name API-Key is case-sensitive. API keys generated by the dashboard have the shape gm_<publicId>_<secret> — for example:
Authorization: API-Key gm_aB3cD4eF5gH6_xK9mN2pQ7rS5tU8vW1yZ3aB6cD9eF2gH5jK7Getting an API Key
- Open the Context Repo dashboard
- Go to Settings > API Keys
- Click New API Key
- Give it a name and select the permission scopes you need
- Click Create and copy the key immediately — it's only shown once
For a detailed walkthrough of key creation, viewing, and revoking, see the Settings page.
Permission Scopes
API keys are scoped per resource and per access kind. Four scopes are available:
| Scope | Label | What It Allows |
|---|---|---|
prompts.read | Prompts — Read | List, read, search, and inspect prompt versions |
prompts.write | Prompts — Write | Create, update, delete, and restore prompt versions. Implies prompts.read. |
documents.read | Documents & Collections — Read | List, read, deep-search, and export documents and collections |
documents.write | Documents & Collections — Write | Create, update, delete, restore versions, scrape URLs, and manage collection memberships. Implies documents.read. |
When you create a key in the dashboard, you pick None / Read / Write per resource — there is one selector for Prompts and another for Documents & Collections. Selecting "Write" automatically grants the matching read access, so you don't need to select both.
If your API key doesn't have the required scope for an endpoint, the API returns a 403 Forbidden error. Make sure you've assigned the right scopes for your use case.
Scope Requirements by Endpoint
Read methods (GET) require the matching .read scope; write methods (POST/PATCH/DELETE/restore) require the matching .write scope (which already includes .read).
| Endpoint Group | Read methods | Write methods |
|---|---|---|
Prompts (/v1/prompts/*) | prompts.read | prompts.write |
Documents (/v1/documents/*) | documents.read | documents.write |
Document scraping (POST /v1/documents/scrape) | — | documents.write |
Collections (/v1/collections/*) | documents.read | documents.write |
Search (GET /v1/search) | At least one of prompts.read or documents.read | — |
Progressive Disclosure (/v1/pd/*) | documents.read | — |
User profile (GET /v1/user/me) | Any authenticated request | — |
MCP Capabilities (GET /v1/mcp/capabilities) | None — public, unauthenticated | — |
Sandbox & Public Endpoints
Context Repo exposes one public, unauthenticated endpoint so agents, scanners, and integration tools can verify connectivity, content-type negotiation, and CORS behavior before provisioning credentials:
| Endpoint | Auth | Purpose |
|---|---|---|
GET /v1/mcp/capabilities | None | Returns the MCP protocol version, advertised capabilities, and supported tool surface. Static manifest, safe to cache. |
Sandbox curl
curl -i https://api.contextrepo.com/v1/mcp/capabilitiesExpect a 200 OK with Content-Type: application/json, a 24-hour shared Cache-Control: public, max-age=86400, s-maxage=86400 header, and an X-API-Version: v1 header. The capabilities payload is a static manifest shared across every client, safe to cache aggressively at CDN and browser layers.
Demo workflow without credentials
Agents and automated scanners that need to exercise the full request → response → error-recovery cycle without authentication can probe the public capabilities endpoint, then deliberately malform requests against other /v1/* routes to walk through the typed error envelopes:
# Capability probe — 200 OK with version + tool list
curl -i https://api.contextrepo.com/v1/mcp/capabilities
# Auth-required endpoint without credentials — 401 with ErrorEnvelope
curl -i https://api.contextrepo.com/v1/documents
# Malformed body on a write — 400 with field-level details
curl -i -X POST https://api.contextrepo.com/v1/documents \
-H "Authorization: API-Key gm_demo_invalid" \
-H "Content-Type: application/json" \
-d 'not-json'Every error response uses the same ErrorEnvelope shape (see Error Format) — a single error path covers 400 / 401 / 403 / 404 / 409 / 413 / 429 / 500. Agents can therefore wire one parser against the public capability probe and re-use it across the authenticated surface.
Sandbox response headers
Every /v1/* 2xx response also carries machine-readable rate-limit and version signals so an agent can self-pace its budget and detect future deprecations without parsing release notes:
| Header | Example | Purpose |
|---|---|---|
RateLimit-Limit | 100 | RFC 9598 — total requests allowed in the current window |
RateLimit-Remaining | 99 | RFC 9598 — requests left in the current window |
RateLimit-Reset | 2026-05-20T13:00:00.000Z | RFC 9598 — ISO-8601 timestamp when the window resets |
X-RateLimit-Limit / Remaining / Reset | (same values) | Legacy alias — preserved for older agents that grep for X--prefixed headers |
X-API-Version | v1 | Route-version constant; switches to v2, v3, … when new majors ship |
Deprecation | ?0 | RFC 9745 structured field — ?0 means not deprecated; flips to ?1 and gains a Sunset header when a version is retired |
The publicly cacheable /v1/mcp/capabilities endpoint omits the RateLimit-* headers to keep its 24-hour CDN cache from baking one client's stale budget data into the response served to every other client. The X-API-Version and Deprecation headers still flow through because they are route-constant, not caller-scoped.
Why no anonymous write tier
Mutation endpoints (POST, PATCH, DELETE) require authentication by design: writes touch user-scoped resources, are versioned per user, and bill against per-user rate-limit buckets. To exercise the full surface — including the async scrape + poll loop documented in Asynchronous Operations — create an API key via Settings → API Keys.
Idempotency
POST writes to /v1/documents and /v1/documents/scrape accept an optional Idempotency-Key HTTP header. Sending the same key with the same body within a 24-hour window returns the original response byte-for-byte instead of re-executing the handler — agents can retry on network timeout without spawning duplicate rows or paying for duplicate Firecrawl scrapes.
Idempotency-Key: 8c3f1a92-7e4d-4f1b-9a01-2b7c5d6e8f10| Endpoint | Status |
|---|---|
POST /v1/documents/scrape | Supported |
POST /v1/documents | Supported |
Sample replay
# First call — runs Firecrawl, returns documentId "doc_a1b2"
curl -i -X POST https://api.contextrepo.com/v1/documents/scrape \
-H "Authorization: API-Key gm_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8c3f1a92-7e4d-4f1b-9a01-2b7c5d6e8f10" \
-d '{"url":"https://example.com"}'
# HTTP/1.1 202 Accepted
# Location: /v1/documents/doc_a1b2
# Replay with the SAME key and body — Firecrawl is NOT invoked
curl -i -X POST https://api.contextrepo.com/v1/documents/scrape \
-H "Authorization: API-Key gm_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8c3f1a92-7e4d-4f1b-9a01-2b7c5d6e8f10" \
-d '{"url":"https://example.com"}'
# HTTP/1.1 202 Accepted
# Location: /v1/documents/doc_a1b2
# Idempotent-Replayed: trueContract
| Behavior | Status | Notes |
|---|---|---|
| Fresh key | original status (201 / 202) | Handler runs once; response is cached for 24h |
| Replay (same key + same body) | original status | Idempotent-Replayed: true advisory header set |
| Same key + different body | 422 Unprocessable Entity | Reusing one key for two distinct payloads is rejected |
| Same key + first request still processing | 409 Conflict | Retry after the first finishes |
| Invalid header (>255 chars / control chars / non-ASCII) | 400 Bad Request |
Keys are scoped per-user and per-endpoint. Two different users sending the same key value get independent fresh executions; the same user can reuse a key value across POST /v1/documents and POST /v1/documents/scrape without collision.
TTL and failure caching
- Ledger rows expire 24 hours after the first request and are purged by a background cron.
- Successful responses (
2xx) and deterministic client errors (4xx) are cached. - Transient server errors (
5xx) are never cached — the ledger row is deleted on failure so a subsequent retry with the same key is treated as fresh.
Choosing an Auth Method
| Use Case | Recommended Method |
|---|---|
| Browser-based integrations | Bearer JWT |
| Chrome Extension | API Key with documents.write (needed to save scraped pages) |
| MCP clients (Cursor, Claude Desktop, etc.) | API Key with the scopes matching the MCP tools you'll use — for full read + write access, grant prompts.write and documents.write |
| CI/CD pipelines and scripts | API Key with the minimum scopes needed |
| Custom applications | API Key for server-to-server, Bearer JWT for user-facing |
Error Responses
Authentication failures return one of two status codes:
| Status | Meaning |
|---|---|
401 Unauthorized | Missing or invalid Authorization header — the token or key couldn't be verified |
403 Forbidden | Valid authentication, but the API key doesn't have the required permission scope |
{
"error": {
"code": 401,
"message": "Invalid authentication"
}
}