REST API reference
The public capture, recall, and upload contract: authentication, request fields, response models, and delivery semantics.
API hosts and authentication
This reference covers the public routes used by the published 0.1.0 SDKs. Use HTTPS and the x-api-key header on every request. JSON examples are illustrative shapes, not live responses. Account authorization is derived from the key; there is no public user_id override.
| Method and route | Host | Purpose |
|---|---|---|
| GET /verify | https://api.contexara.com | Validate an active API key. |
| POST /ingest | https://api.contexara.com | Submit a complete conversation-turn event. |
| POST /v1/memory/search | https://retrieval.contexara.com | Recall relevant memory. |
| POST /upload | https://api.contexara.com | Upload a file and consume an SSE response. |
Create an API key in the dashboard at /keys. Enable capture permission for writes and recall permission for searches. Keep the key on your server or in your secret manager; never expose it in browser JavaScript, a public repository, or logs. The key determines the authenticated account. A workspace ID selects an authorized destination; it does not grant access to another account.
x-api-key: ctx_live_replace_with_your_keyUse Content-Type: application/json for ingest and search. Use multipart/form-data with a generated boundary for upload. Loop chat endpoints require internal authentication and are not part of this public API. There are no SDK-backed public list, delete, or stats operations in this release.
GET /verify
Send no body. A 200 response contains {"ok": true}; an invalid or inactive key returns 401. Successful verification is not a permission or quota preflight.
GET /verify HTTP/1.1
Host: api.contexara.com
x-api-key: ctx_live_replace_with_your_key{"ok":true}POST /ingest
Submit one complete conversation turn. The SDK builds this envelope automatically; a custom HTTP client must build and persist it before delivery.
{
"event_id": "evt_replace_with_a_new_unique_id",
"event_type": "conversation_turn",
"event_version": "1.0",
"capture_version": "my-app-0.1.0",
"schema_version": "1.0",
"turn_id": "turn_000001",
"tenant_id": "unset",
"user_id": "unset",
"session_id": "conversation_replace_with_your_stable_id",
"turn_number": 1,
"project_id": null,
"workspace_id": null,
"provider": "sdk",
"created_at": "2026-09-29T12:00:00Z",
"status": "complete",
"incomplete_reason": null,
"conversation": {
"user": {
"timestamp": null,
"content": "The deployment region is Mumbai."
},
"assistant": {
"timestamp": null,
"content": null
}
},
"metadata": {
"capture_client_version": "0.1.0",
"os": null,
"hook_event_names": []
}
}| Field | Type and contract |
|---|---|
| event_id | String, maximum 512 characters. Unique per logical event; unchanged on retries. |
| event_type / event_version / schema_version | conversation_turn / 1.0 / 1.0. |
| capture_version | Your capture implementation version, e.g. my-app-0.1.0. |
| session_id | Stable conversation string, maximum 512 characters. |
| turn_number / turn_id | Integer ≥1; derived turn_NNNNNN identifier, padded to at least six digits. |
| tenant_id / user_id | Send unset; the server stamps authenticated identity. |
| provider | sdk for this integration. |
| created_at | RFC 3339 timestamp with timezone; naive timestamps are invalid. |
| status / incomplete_reason | Use complete and null for SDK-style complete turns. |
| project_id / workspace_id | Nullable authorized destination UUIDs. |
| conversation.user / assistant | Objects with timestamp and content. Content string or null; maximum 100,000 characters per message. SDK capture requires non-blank user text. |
| metadata | capture_client_version, os, and hook_event_names as shown. |
The shown event ID, session ID, and timestamp must be replaced before first delivery. turn_000001 matches turn_number=1. The cURL guide includes a one-time identity-generation example.
Capture acknowledgement and delivery
| HTTP response | Meaning |
|---|---|
| 202 {ok:true, turn_id, status:"accepted"} | Queued for processing. Not a search-readiness guarantee. |
| 200 {ok:true, turn_id, status:"processed"} | Processed in a synchronous deployment. Evidence availability can still lag. |
Persist the original event until delivery is acknowledged. Retry an uncertain capture with the identical event_id and entire payload. Never recreate an event for a retry. A new logical write requires a new event ID. Do not change content under an existing event ID: that misuse is not reliably rejected by the server. This is not an exactly-once guarantee.
A 409 capture conflict can be returned from synchronous processing. In queued deployments, later worker failures cannot change an already returned 202. The capture response has no processing-status handle. Do not treat the absence of an HTTP conflict as proof of eventual processing success.
POST /v1/memory/search
{
"query": "What deployment region did we choose?",
"limit": 5,
"scope": "general",
"deep": false,
"force_fast": false
}| Field | Type / default | Rules |
|---|---|---|
| query | string, required | Non-blank. |
| limit | integer, 10 | Range 1–50. Controls initial retrieval, not the final Deep Recall count. |
| scope | string, general | Stable values: general and project. |
| project_id | UUID or null | Required with project scope; omit with general. |
| workspace_id | UUID or null | Select an authorized workspace; omitted means personal. |
| deep | boolean, false | Opt into Deep Recall if entitled. |
| force_fast | boolean, false | Do not combine with deep=true. |
Use general for unassigned memories in the selected workspace, or project with an active project ID. Omit the workspace ID for your personal workspace; pass an authorized workspace ID for a team workspace. A project must belong to that workspace. Do not pass a project ID with general scope. all_active is outside the supported stable contract. Capture and recall destinations are separate: explicitly use the same intended project and workspace in both calls.
Blank query and out-of-range limit return 400. Invalid scope, missing project selection, and invalid/archived project selection return 422. Preferences, permissions, and workspace access can return 403; hard quota locks return 402.
Search response fields
{
"results": [
{
"memory_id": "example-memory",
"text": "The deployment region is Mumbai.",
"entities": [
{
"text": "Mumbai",
"type": "GPE"
}
],
"event_time": null,
"record_time": null,
"confidence_bucket": null,
"rrf_score": 0.83,
"speaker": null,
"source_type": "sdk",
"modality": null,
"source_id": null,
"content_hash": null,
"page_number": null,
"slide_number": null,
"mime_type": null
}
],
"timings_ms": {}
}| Field | Interpretation |
|---|---|
| results | Ordered array of memories; an empty array is a valid result. |
| memory_id / text | Memory identity and content. |
| rrf_score | Nullable ranking signal; not a probability. Preserve returned ordering. |
| entities | Extractor-dependent objects; name, text, and type may each be absent. |
| event_time / record_time | Nullable timestamps. |
| confidence_bucket | Currently null; do not require a confidence value. |
| source_type / source_id / modality | Nullable provenance. |
| content_hash / page_number / slide_number / mime_type | Nullable attachment provenance. |
| timings_ms | Open numeric map; tolerate new timing keys. |
| note | Optional server-provided text. |
Do not require the legacy type, confidence, or salience fields. Tolerate additive response fields while validating required structure and known field types. Python models retain snake_case; Node models expose camelCase.
Deep Recall response semantics
When synthesis runs, the response includes synthesis (string or null) and grounded_in_source_windows (boolean), alongside results and timings_ms. These fields can be absent when a deep request falls back to Fast Recall. Presence and non-null synthesis are separate checks.
{"synthesis":null,"grounded_in_source_windows":false,"results":[],"timings_ms":{}}grounded_in_source_windows states whether a summoned source window materially informed synthesis; it is not a citation array. Internal counters such as summoned_count and sub_query_count are not public response fields. The final result count need not respect limit. Deep Recall can incur model cost before an uncertain timeout, so retry conservatively.
POST /upload multipart fields
| Field | Required / default | Meaning |
|---|---|---|
| file | Required | Binary file with filename and allowed MIME type. |
| caption | Optional | Text accompanying the upload. |
| explain | HTTP default true; SDK default false | Non-image false skips explanation; images still explain. |
| session_id | Optional | Conversation association. |
| project_id | Optional | Active authorized project UUID. |
| explicit_workspace_id | Optional | Workspace UUID. Do not send workspace_id as the multipart name. |
| group_id | Optional | Batch grouping for images. |
Let your HTTP library generate the multipart boundary. Validate the actual file format and byte size before sending. Unsupported MIME types return 415; oversized uploads return 413. Limits below use decimal bytes, not MiB.
| File type | Accepted MIME type | Maximum bytes |
|---|---|---|
| PNG / JPEG / WebP | image/png, image/jpeg, image/webp | 20,000,000 |
| application/pdf | 50,000,000 | |
| DOCX | application/vnd.openxmlformats-officedocument.wordprocessingml.document | 20,000,000 |
| PPTX | application/vnd.openxmlformats-officedocument.presentationml.presentation | 50,000,000 |
| TXT / Markdown | text/plain, text/markdown | 5,000,000 |
Implement Server-Sent Events parsing
data: {"content_hash":"example-hash","duplicate":false,"attachment_id":"example-attachment"}
data: [DONE]Decode UTF-8 incrementally: a network read can split a multibyte character or a frame. Accumulate lines until an empty line terminates an event; support LF and CRLF, and join multiple data: lines with newline characters. Ignore SSE comments. Parse each complete data payload as JSON, except the literal [DONE] sentinel.
| Payload | Handling |
|---|---|
| content_hash, duplicate, attachment_id | Persist the received metadata for later diagnostics. |
| chunk | Append explanation text; zero chunks is valid. |
| error | Mark the operation failed, even if [DONE] follows. |
| [DONE] | Normal stream terminator, successful only if no error preceded it. |
| EOF without [DONE] | Incomplete stream; surface an error and retain partial metadata. |
HTTP success is insufficient for an upload. Storage can precede later errors, including errors before the first event. An SSE failure does not imply that no file was stored. Do not regenerate an upload request automatically after a timeout.
Errors and retry behavior
| HTTP status | Meaning | Recovery |
|---|---|---|
| 400 | Blank query or invalid limit. | Correct the request. |
| 401 | Missing, invalid, or inactive API key. | Replace or activate the key. |
| 402 | Personal or team hard quota lock. | Review usage and allowance. |
| 403 | Permission, preference, or workspace access denied. | Check key scope, preferences, and membership. |
| 409 | Capture turn conflict in synchronous processing. | Investigate identity/payload; do not invent a retry ID. |
| 413 | Request or upload too large. | Reduce the payload or use an allowed file size. |
| 415 | Unsupported upload MIME type. | Use an allowed MIME type and file format. |
| 422 | Invalid fields, scope, project, or multipart data. | Inspect the error response and correct the payload. |
| 429 | Rate limit or upload allowance exhausted. | Honor Retry-After when present; otherwise inspect allowance. |
| 5xx | Server or upstream failure. | Bound retries according to operation and delivery certainty. |
Error bodies are endpoint-specific and can contain a string or structured detail; do not assume one universal error-envelope shape. Log status and safe diagnostic details while redacting keys and sensitive message bodies. Retry-After can be seconds or an HTTP date. Set both connection and response budgets and cap attempts.
| Operation | Retry policy for a custom client |
|---|---|
| Verify / Fast Recall | Bounded retry on transient network or server failures. |
| Deep Recall | Conservative retry because generation may already have run. |
| Capture | Only replay the exact persisted event after an uncertain result. |
| Upload | No automatic retries; inspect partial storage evidence. |
For 401, 402, 403, and validation failures, fix the underlying cause. For a 429 without Retry-After, do not assume a short backoff restores an exhausted allowance. Cancellation is local: it cannot undo work already accepted by the service.
Implementation checklist
Keep credentials server-side. Choose capture and recall destinations explicitly. Persist event identity and allocate turn numbers durably. Validate response shapes without rejecting additive fields. Preserve result order and source metadata. Handle empty recall normally. Parse SSE across arbitrary read boundaries. Distinguish acknowledged capture from searchable memory and stored attachments from successful explanation streams.
For existing application code, the Python and Node guides provide supported clients with these mechanics built in. Use the cURL guide to inspect individual requests during integration.