Skip to content
Contextely

Machine surface

REST API

A thin mirror of the MCP surface for callers that speak HTTP but not MCP. Both go through the same retrieval pipeline, so the entitlement and freshness guarantees are identical. There is no second, laxer path.

Authentication

Authorization: Bearer ctx_sk_...
# or
X-API-Key: ctx_sk_...

The key resolves to a member. Everything you retrieve is scored against that member's entitlement scopes. CORS is open, because the credential is the key rather than the origin.

Endpoints

POST /api/v1/search

MCP tool: context_searchmetered: query

The main call. Searches the workspace's condensed working memory and returns what this asker is entitled to see, refreshing anything past its freshness TTL from its system of record first.

body: {"query":"string","limit":"integer","skip_refresh":"boolean"}

GET /api/v1/memory/{id}/related

MCP tool: context_related

Walk from one memory object to its neighbours, ranked by shared entities, then topics, then keywords. Use it when one search result is the right thread and you want the rest of it. A neighbour outside the asker's scopes is never named or counted.

POST /api/v1/memory/{id}/expand

MCP tool: context_expandmetered: refresh

Read the full current record from the system of record a memory object was condensed from, for when the summary is not enough. No model is involved, so it returns exactly what the source says. Costs one source round-trip, metered against the monthly refresh allowance.

GET /api/v1/memory/{id}

MCP tool: memory_get

Fetch one memory object by id. Returns not_found rather than forbidden when the asker is not entitled to it, so probing for ids cannot reveal what exists.

GET /api/v1/sources

MCP tool: sources_list

List the connected systems of record with their freshness policies, sync status, and whether this asker is entitled to anything from each.

GET /api/v1/usage

MCP tool: workspace_usage

Plan, metered usage and remaining allowance for the month. Call it before a batch run to decide whether to proceed or back off.

GET /api/v1/actions

MCP tool: actions_list

The enabled actions the caller may invoke. Proposed and disabled actions never appear, and neither do actions whose required write scopes the caller does not hold.

POST /api/v1/actions/{name}/preview

MCP tool: action_preview

Validates arguments and returns a rendered description plus the request hash, without contacting the source. It reports upstream_dry_run:false rather than simulating an effect, because a fabricated preview is indistinguishable from a real one.

body: {"name":"string","args":"object"}

POST /api/v1/actions/{name}/invoke

MCP tool: action_invokemetered: action

Performs one enabled action. Entitlement is checked at entry and again immediately before the effect; the allowance is charged only when it reaches upstream; and every attempt, refusals included, is recorded in the action log.

body: {"name":"string","args":"object","idempotency_key":"string"}

POST /api/v1/ingest

Read records from a connected source and condense them into memory objects. Owner or admin only, because ingesting spends the workspace's condensation budget.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/actions/discover

Enumerate the actions a connected source offers and record them as PROPOSED. Owner or admin only, and it enables nothing: each proposed action has to be enabled by hand, with the write scopes it requires. Deliberately not a tool, for the same reason /ingest is not.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/action-runs

Every invocation that ran and every invocation that was refused, newest first, with the refusal reason. Not gated behind the paid plan: what an agent changed in your systems is a safety artefact, not a compliance nicety.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/action-runs/{id}/approve

Perform an action whose policy holds it for approval. The APPROVER's entitlement is checked, because releasing it is authorising it. Owner or admin only, and deliberately not a tool: an agent holding a key must never see its own approval bypass in its tool list.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/action-runs/{id}/reject

Mark a held action rejected. Nothing upstream is contacted, then or ever.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org

Plan, subscription status, tenant count and the caps applied to a new tenant. The place to confirm a key is wired up correctly.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants

Idempotent on `external_ref`, which is your own identifier for the client. Call it on every one of your customer's logins rather than tracking which ones you have already created; `created` tells you which happened.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/tenants

Every tenant this organization manages, newest first, with this period's usage.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/tenants/{tenant_id}

The tenant, what it holds, and what it has spent this period.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants/{tenant_id}

Rename it, or change its caps. Raising a cap takes effect on the client's very next request, which is what makes a cap safe to set tightly.

body: {"source_id":"string","limit":"integer"}

DELETE /api/v1/org/tenants/{tenant_id}

Deletes the tenant's people, keys, sources and condensed memory objects. Usage already spent stays on this period's invoice, because it has already cost a source round trip and a model call.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants/{tenant_id}/members

The asker a retrieval is scored against. `scopes` is what your own permission model says this person may see, and it is enforced inside the ranking function rather than as a filter over the answer. No email is ever sent: these people do not have Contextely accounts and are never invited to make one.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/tenants/{tenant_id}/members

Their roles and entitlement scopes. Never their memory.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants/{tenant_id}/members/{member_id}

Takes effect on their next retrieval. Narrowing a scope narrows every key that speaks as them, because a key is never more privileged than the person it belongs to.

body: {"source_id":"string","limit":"integer"}

DELETE /api/v1/org/tenants/{tenant_id}/members/{member_id}

Their API keys go with them, so an agent holding one stops working immediately.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants/{tenant_id}/keys

The returned `key` is shown once and never stored, so keep it. Every retrieval made with it is scored against that person's scopes, which is what makes it safe to hand to an agent.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/tenants/{tenant_id}/keys

Prefixes and last use. The secret is not recoverable; mint a new one and revoke the old.

body: {"source_id":"string","limit":"integer"}

DELETE /api/v1/org/tenants/{tenant_id}/keys/{key_id}

Immediate. The next request carrying it is refused.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants/{tenant_id}/sources

Either an MCP server or a read-only SQL query. `required_scopes` is inherited by every memory object condensed from it, which is how a whole source is made visible to only part of a client's staff.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/tenants/{tenant_id}/sources

Kind, status, last sync and last error.

body: {"source_id":"string","limit":"integer"}

POST /api/v1/org/tenants/{tenant_id}/ingest

Exists so you never have to mint a wildcard-scoped owner key per client purely to trigger a sync, which would be exactly the privilege manufacture the entitlement model is built to prevent.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/tenants/{tenant_id}/audit

Every query, and how many candidates were refused on entitlement grounds. This is the artefact your own customer's security reviewer asks for, and it is the reason to resell a permission-aware layer rather than a search index.

body: {"source_id":"string","limit":"integer"}

GET /api/v1/org/usage

What every client has spent this period against the pool, and what is over the included volume. Usage past the included amount bills as overage and keeps serving; only a client's own cap stops a request.

body: {"source_id":"string","limit":"integer"}

Example

curl -s https://www.contextely.com/api/v1/search \
  -H "Authorization: Bearer ctx_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"query":"acme renewal","limit":5}'

Machine-readable spec

An OpenAPI 3.1 document is generated from the same tool catalogue this page renders, so it cannot describe an endpoint that does not exist. Point a client generator, an agent or Postman at it directly.

curl -s https://www.contextely.com/api/v1/openapi.json

Status codes

200Success.
400bad_request: malformed arguments. The message says which.
401unauthorized: missing, invalid or revoked API key.
402quota_exceeded: the month's metered allowance is spent. Back off or upgrade.
404not_found: no such object, or none this asker is entitled to.
500internal: reported to us with a reference id you can quote.
502upstream_failure: a system of record or the condenser could not be reached.
503unconfigured: a capability this deployment has not been given credentials for.

Every error body is {"error": {"code", "message"}}. Branch on code, never on the message text.