API Reference
Resource Model
The API operates on three core resource types:
| Resource | Path prefix | Description |
|---|---|---|
| Project | /v1/projects | Container for items and files |
| Item | /v1/projects/{project_key}/items | Inline text or file upload |
| Search | /v1/projects/{project_key}/search | Full-text search across items |
Items are the canonical resource type. Each item has a content_type indicating whether it stores inline text (inline) or an uploaded file (file). Both types share the same endpoint surface.
Authentication
All API requests require a Bearer token in the Authorization header. API keys are prefixed ctxme_ and validated against stored prefixes/hashes.
curl -H "Authorization: Bearer $API_KEY" https://api.ctx.me/v1/projects
Endpoints
Health
| Method | Path | Description |
|---|---|---|
| GET | /health | Service health check |
Auth
| Method | Path | Description |
|---|---|---|
| GET | /v1/auth/me | Current user + account context |
Projects
| Method | Path | Description |
|---|---|---|
| GET | /v1/projects | List projects for current account |
| GET | /v1/projects/{project_key} | Get a single project |
| POST | /v1/projects | Create a new project |
| PUT | /v1/projects/{project_key} | Update a project (ETag required) |
| DELETE | /v1/projects/{project_key} | Delete a project (ETag required) |
| GET | /v1/projects/{project_key}/export | Download the project as a ZIP archive |
List Projects
Returns a paginated list of projects for the current account.
{
"projects": [ ... ],
"next_cursor": "eyJ...",
"has_more": true
}
Query parameters:
limit: Max projects per page (1-200, default 50)cursor: Opaque cursor from previous response'snext_cursor
Create Project
Returns 201 Created with Location and ETag headers.
HTTP/1.1 201 Created
Location: /v1/projects/my-project
ETag: "v1:2026-02-07T..."
Get Project
Supports conditional GET via If-None-Match header. Returns 304 Not Modified when the ETag matches.
Update Project
Full replacement of project metadata. Requires If-Match header.
Delete Project
Soft-deletes a project. Requires If-Match header. Returns 409 Conflict if the project still contains items.
Export Project
Requires read scope; available on every plan. Streams a ZIP archive
(Content-Disposition: attachment; filename="<project_key>.zip") containing every
readable active item — original bytes for files, UTF-8 text for inline items,
laid out under their folders — plus a manifest.json at the archive root listing
each exported item (item_key, title, tags, folder, timestamps, size,
content_sha256, archive_path) and an errors array naming any item whose
content could not be read.
Returns 413 when the archive would exceed the configured size budget and 429
when the per-account export rate limit is exceeded. Derived data (search chunks,
embeddings, extracted text) and item version history are not included.
Items
The unified items endpoint handles both inline text content and file uploads through a single API surface.
| Method | Path | Description |
|---|---|---|
| GET | /v1/projects/{project_key}/items | List items in a project |
| GET | /v1/projects/{project_key}/items/{item_key} | Get a single item |
| POST | /v1/projects/{project_key}/items | Create item (JSON or multipart) |
| PUT | /v1/projects/{project_key}/items/{item_key} | Replace an inline item (full update) |
| PATCH | /v1/projects/{project_key}/items/{item_key} | Patch item metadata (partial update) |
| DELETE | /v1/projects/{project_key}/items/{item_key} | Delete an item |
Creating Items
Items can be created in two ways:
Inline Content (JSON):
curl -X POST -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "my-item", "title": "My Item", "data": "content here"}' \
https://api.ctx.me/v1/projects/myproject/items
File Upload (multipart/form-data):
curl -X POST -H "Authorization: Bearer $API_KEY" \
-F "file=@document.docx" \
-F "key=my-document" \
https://api.ctx.me/v1/projects/myproject/items
For file uploads, the key is optional — if omitted, it's derived from the filename.
Supported upload formats are .txt, .md/.markdown, .pdf, .docx, .csv,
.tsv, .html, .htm, and .json. File uploads return 202 Accepted while asynchronous
extraction runs.
PUT — Replace Inline Item
Full replacement of an inline item's content and metadata. Requires If-Match header. File items cannot be replaced via PUT — use PATCH to update metadata, or delete and re-upload.
PATCH — Partial Metadata Update
Partially updates item metadata. Requires If-Match header. Accepted fields (at least one required):
title(optional): New title (1-255 characters)mime_type(optional): New MIME type (not changeable for file items)tags(optional): New tags array
Content Types
Items have a content_type field indicating the storage type:
inline: Text content stored directly in the databasefile: Binary content stored in object storage
Processing Status
Items have a processing_status field:
ready: Content is fully indexed and searchableprocessing: Async extraction in progress (file uploads)failed: Processing failed (checkprocessing_errorfield)
List Items Query Parameters
content_type: Filter by type (inlineorfile)content_sha256: Filter by content hash for deduplication lookupcreated_via: Filter by provenance (ui,cli,mcp,unknown)limit: Max items per page (1-200, default 50)cursor: Opaque cursor from previous response'snext_cursor
Search
| Method | Path | Description |
|---|---|---|
| GET | /v1/search?q=<query> | Search context items |
Query parameters:
q(required): Search query (2-256 chars)project_key: Scope search to a single project. Omit to search all projects.tags: Comma-separated tags (AND semantics)include_pending: Include items with non-ready processing status (default false)created_via: Filter by provenance (ui,cli,mcp,unknown)limit: Results per page (1-50, default 20)cursor: Opaque cursor from previous response'snext_cursor
Pagination
List endpoints use cursor-based pagination for stable, efficient paging through results.
- Make the initial request (optionally with
limit). - The response includes
has_more(boolean) andnext_cursor(string or null). - If
has_moreis true, passnext_cursoras thecursorquery parameter in the next request. - Repeat until
has_moreis false.
# First page
curl -H "Authorization: Bearer $API_KEY" \
"https://api.ctx.me/v1/projects/myproject/items?limit=10"
# Next page (using next_cursor from previous response)
curl -H "Authorization: Bearer $API_KEY" \
"https://api.ctx.me/v1/projects/myproject/items?limit=10&cursor=eyJ..."
Conditional Requests and ETags
Optimistic Concurrency (If-Match)
Write operations that modify existing resources require the If-Match header to prevent lost updates. The value must match the resource's current etag.
# 1. Fetch the resource and capture its ETag
curl -i -H "Authorization: Bearer $API_KEY" \
https://api.ctx.me/v1/projects/myproject/items/my-item
# 2. Update with If-Match
curl -X PUT -H "Authorization: Bearer $API_KEY" \
-H 'If-Match: "v1:2026-02-07T12:00:00Z"' \
-H "Content-Type: application/json" \
-d '{"title": "Updated", "data": "new content", "mime_type": "text/plain", "tags": []}' \
https://api.ctx.me/v1/projects/myproject/items/my-item
Conditional GET (If-None-Match)
Read operations support the If-None-Match header to avoid re-downloading unchanged resources. Returns 304 Not Modified when the ETag matches.
Errors
Error responses follow a consistent envelope format:
{
"error": {
"code": "string",
"message": "Human-readable detail",
"details": {}
}
}
Common error codes:
| Status | Code | Description |
|---|---|---|
400 | validation_error | Invalid request (e.g., PUT on file items) |
401 | unauthorized | Invalid or missing API key |
403 | forbidden | Insufficient permissions |
404 | not_found | Resource not found |
409 | conflict | Duplicate key or content |
412 | precondition_failed | ETag mismatch (If-Match failed) |
422 | validation_error | Request validation failed |
428 | precondition_required | Missing required If-Match header |
429 | too_many_requests | Rate limited |
Versioning
All endpoints are prefixed with /v1/. The current API version is v1. Breaking changes within /v1/ are avoided where possible. Deprecated endpoints remain functional and will be announced before removal.