API Reference

Resource Model

The API operates on three core resource types:

ResourcePath prefixDescription
Project/v1/projectsContainer for items and files
Item/v1/projects/{project_key}/itemsInline text or file upload
Search/v1/projects/{project_key}/searchFull-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

MethodPathDescription
GET/healthService health check

Auth

MethodPathDescription
GET/v1/auth/meCurrent user + account context

Projects

MethodPathDescription
GET/v1/projectsList projects for current account
GET/v1/projects/{project_key}Get a single project
POST/v1/projectsCreate 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}/exportDownload 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's next_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.

MethodPathDescription
GET/v1/projects/{project_key}/itemsList items in a project
GET/v1/projects/{project_key}/items/{item_key}Get a single item
POST/v1/projects/{project_key}/itemsCreate 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 database
  • file: Binary content stored in object storage

Processing Status

Items have a processing_status field:

  • ready: Content is fully indexed and searchable
  • processing: Async extraction in progress (file uploads)
  • failed: Processing failed (check processing_error field)

List Items Query Parameters

  • content_type: Filter by type (inline or file)
  • content_sha256: Filter by content hash for deduplication lookup
  • created_via: Filter by provenance (ui, cli, mcp, unknown)
  • limit: Max items per page (1-200, default 50)
  • cursor: Opaque cursor from previous response's next_cursor
MethodPathDescription
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's next_cursor

Pagination

List endpoints use cursor-based pagination for stable, efficient paging through results.

  1. Make the initial request (optionally with limit).
  2. The response includes has_more (boolean) and next_cursor (string or null).
  3. If has_more is true, pass next_cursor as the cursor query parameter in the next request.
  4. Repeat until has_more is 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:

StatusCodeDescription
400validation_errorInvalid request (e.g., PUT on file items)
401unauthorizedInvalid or missing API key
403forbiddenInsufficient permissions
404not_foundResource not found
409conflictDuplicate key or content
412precondition_failedETag mismatch (If-Match failed)
422validation_errorRequest validation failed
428precondition_requiredMissing required If-Match header
429too_many_requestsRate 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.