Zekra
Menu · Zekra

REST API

Zekra's HTTP API for building your own agents and integrations. Covers authentication, every /api/brain endpoint with examples, errors, server-sent events and a typed TypeScript client.

Everything Zekra does is available over plain HTTP and JSON. The MCP server and the CLI are thin clients of this API.

  • Base URL: https://app.zekra.dev (or your self-hosted URL)
  • Content type: application/json for request and response bodies, unless noted
  • Timestamps: RFC 3339. Memory ids: UUID strings. Importance: 0 to 1.

SDK: there is no published SDK package yet. The zekra-cli npm package only installs the CLI binary and has no importable library. The API is small, so a typed wrapper around fetch is enough; see TypeScript client.

Authentication

Access tokens (agents and services)

Send your token in the X-Zekra-Token header:

sh

A token (cbt_…) resolves to an agent identity. Each request is checked against that identity's grants: canRead for reads and canWrite for writes on the brain named in the request. Admin tokens bypass grants and can use the access-control endpoints. A revoked or unknown token gets no access. The legacy header X-Cabrain-Token is still accepted.

X-Agent-Id is an optional label used to attribute activity. It is not a credential. When a token is present, the token's identity is used.

Console sessions (people)

The web console signs people in through /api/auth/*. The session is carried by a cookie, or by Authorization: Bearer <jwt> using the token returned from POST /api/auth/login. Use access tokens for programmatic access. Sessions are meant for the console.

How a request is authorized

  1. Authentication gate. When the server runs with ZEKRA_REQUIRE_AUTH=1, which is how a public deployment should run, every /api/brain/* endpoint except GET /api/brain/ping and the webhook ingest endpoint needs either a valid X-Zekra-Token or a signed-in session. Otherwise the response is 401.
  2. Brain authorization. Each handler then checks the caller's grant on the brain it touches, and returns 403 permission_denied if the grant is missing.

A request without a token is treated as the trusted console and gets admin rights, unless the server sets ZEKRA_REQUIRE_TOKEN=1. Always send a token from agents and services. See Security.

Errors

Errors use one shape:

json
HTTPcodeMeaning
400invalid_argumentBad JSON, or a required field is missing
401unauthenticatedWebhook secret missing or wrong. The authentication gate also returns 401.
403permission_deniedYour token has no grant on the brain, or the endpoint is admin-only
404not_foundNo such memory, gap or data source
503no_embedderThe server has no embedding provider configured, so retain and recall cannot run
503unavailableA backing service (database, embedder, LLM) failed
503auth_unavailableAuthentication is enforced but the auth plugin is not active (server misconfiguration)

Memory

POST /api/brain/retain

Store a memory. Access: write.

FieldTypeRequiredNotes
namespacestringyes
contentstringyes
sourceKindstringWhere it came from, such as claude_code, chat or manual
sourceRefstringSession, thread or run id
visibilitystringprivate (default), team, global
importanceHintnumber0 to 1. Blended into the computed importance
ownerAgentIdstring
metadataobjectFree-form. metadata.type is what recall's types filter matches.
validAtRFC 3339When the event happened. Set this when you import dated records; the default is now.
networkstringfact, experience, observation, belief. Derived when omitted.
memoryTypestringepisodic, semantic, procedural, working. Derived when omitted.

Field names are also accepted in snake_case (source_kind, valid_at and so on).

sh
json

decision is one of add, update, invalidate or noop. For update, supersededId is the memory that was replaced. Credentials in content are moved to the brain's secrets vault and replaced with [secret:<name>].

POST /api/brain/recall

Hybrid recall inside one brain. Access: read.

FieldTypeDefaultNotes
namespacestringrequired
querystringrequired
limitint8Results after reranking
expandEntitybooltrueOne-hop expansion through the entity graph
minImportancenumberImportance floor
typesstring[]Only memories whose metadata.type is in this list
excludeSourceKindsstring[]Drop these source kinds
since, untilRFC 3339Bound on the event time (validAt)
asOfRFC 3339What the brain believed at that moment, including memories that were later superseded
orderBystringrelevancerecent or oldest switches from relevance to time ordering
sh
json

viaEntity is set on results that came in through entity expansion. An empty result list is recorded as a knowledge gap.

POST /api/brain/search

The same kind of search across several brains. Access: read on each brain searched.

json

If you omit namespaces, all brains your token can read are searched. Each result also carries namespace.

GET /api/brain/memory?namespace=&id=

One memory with full provenance. Access: read.

json

POST /api/brain/memory/edit

{ namespace, id, content?, importance?, metadata? }. Changing the content re-embeds the memory; metadata replaces the existing metadata. Access: write. Returns { "id", "updated": true }.

POST /api/brain/forget

{ namespace, id, reason? }. Soft-deletes the memory by setting invalidAt. Access: write. Returns { "id", "invalidAt" }.

POST /api/brain/dedup

{ namespace, sourceKind? }. Soft-invalidates memories that share a sourceRef, keeping the newest. Access: write. Returns { "namespace", "sourceKind", "invalidated": <n> }.

Brains

EndpointBody or queryReturns
GET /api/brain/namespaces{ "brains": [ { namespace, memories, lastAt } ] }
GET /api/brain/brain?namespace={ namespace, memories, types, sources, openGaps, recalls, firstAt, lastAt }
POST /api/brain/brain/delete{ namespace, confirm } (confirm must equal namespace){ namespace, deleted }. Access: write. Deletes the brain and all its memories.
GET /api/brain/export?namespace=The brain as NDJSON (application/x-ndjson), one memory per line
POST /api/brain/import?namespace=NDJSON body in the export format{ "imported": <n> }. The namespace query parameter overrides the namespace in the file.
GET /api/brain/statsInstance totals: ready, brains, memories, entities, edges, agents, sessions24h, recalls24h, openGaps
GET /api/brain/activity?limit=50{ "items": [ { id, ts, op, namespace, agentId, outcome, latencyMs } ] }
GET /api/brain/ping{ "plugin": "brain", "status": "ok", "authRequired": false }. Always open.

There is no create-brain endpoint. A brain exists once it holds a memory: retain one to create it.

Knowledge gaps

EndpointBody or queryReturns
GET /api/brain/gaps?namespace=&status=&limit=status: open, indexed, dismissed or all{ "gaps": [ { id, namespace, query, hits, status, resolution, firstSeen, lastSeen } ] }
POST /api/brain/gaps/resolve{ id, status, resolution? }{ id, status }

Graph

All graph endpoints need read access on the brain.

EndpointBodyReturns
GET /api/brain/graph?namespace=&limit=200A sample for visualization: { ready, derived, nodes, edges, typeCounts, relationCounts, totalNodes, … }
GET /api/brain/graph/ontology?namespace={ "entityTypes": [ { Name, Description, Count } ], "edgeTypes": [ { Name, Description, Src, Dst, Count } ] }
POST /api/brain/graph/traverse{ namespace, entity, depth?, relations?, types?, direction?, asOf?, limit? }{ "nodes": [ { id, name, type, depth, path, via, summary, distance } ], "count" }
POST /api/brain/graph/neighbors{ namespace, entity, asOf? }{ "edges": [ { id, src, dst, relation, fact, validFrom, validTo } ], "count" }
POST /api/brain/graph/path{ namespace, from, to, maxDepth? }{ "path": [names…], "hops", "connected" }
POST or GET /api/brain/graph/spine{ namespace, entity, depth?, hubs?, roles?, perGroup?, window?, since?, until?, timeRoles? }, or the same as query parameters on GET (lists comma-separated){ root, depth, hubs, window, groups: [ { role, total, shown, capped, items } ], totals }
POST /api/brain/graph/communities{ namespace, iterations? }{ "communities": <n> }. Recomputes entity clusters. Access: write.

The ontology keys are capitalized (Name, Count) in the current release.

Chat

POST /api/brain/chat

Ask a brain a question. Access: read. Set write: true to let the agent retain what it learns; this only takes effect if you also have write access.

json
json

If nothing relevant is found, grounded is false and the answer says so.

Access control

Admin-only: a non-admin token gets 403.

EndpointBody or queryReturns
GET /api/brain/tokens?includeRevoked=1{ "tokens": [ { token, agentId, label, isAdmin, createdAt, lastUsedAt, revoked, grants } ] }
POST /api/brain/tokens{ agentId, label?, isAdmin? }The new token object, including token
POST /api/brain/tokens/revoke{ token }{ "revoked": true }
POST /api/brain/grant{ agentId, namespace, canRead? (true), canWrite? (false) }{ agentId, namespace, canRead, canWrite }
POST /api/brain/grant/revoke{ agentId, namespace }{ "revoked": true }

Available to anyone with access to the brain:

EndpointBodyAccessReturns
POST /api/brain/share{ namespace, granteeAgentId, canRead?, canWrite? }writeThe grant
POST /api/brain/session{ namespace, write?, label? }read (write if write: true)A new scoped token, see below

POST /api/brain/session creates a new agent identity, mints a non-admin token for it and grants it this brain (read-only unless write is true):

json

Secrets vault

EndpointBody or queryAccessReturns
GET /api/brain/secrets?namespace=read{ "secrets": [ { namespace, name, hint, kind, sourceRef, createdBy, createdAt, updatedAt } ] }. Never values.
POST /api/brain/secrets{ namespace, name, value, kind? }write{ namespace, name, stored: true }
POST /api/brain/secrets/reveal{ namespace, name }write{ namespace, name, value }
POST /api/brain/secrets/delete{ namespace, name }write{ namespace, name, deleted }

Data sources

EndpointBody or queryAccess
GET /api/brain/datasources?namespace=read. Returns { datasources, kinds }.
POST /api/brain/datasources{ namespace, kind, name, config }write
POST /api/brain/datasources/sync{ id }write. Returns { ingested, status, error? }.
POST /api/brain/datasources/delete{ id }write
POST /api/brain/ingest/{id}{ content, sourceRef?, metadata? } with header X-Webhook-SecretWebhook secret only

Details and connector configs: Data sources.

Realtime events (SSE)

GET /api/brain/events is a server-sent events stream of brain activity. Each message has an event name and a JSON payload with a ts timestamp. The server sends a : ping comment every 25 seconds.

EventPayload
retain{ namespace, decision }
recall{ namespace, count }
gap{ namespace, query } on a miss, or { resolved, status } when a gap is resolved
search{ count }
chat{ namespace, recalled }
brain{ deleted }
grant{ agentId, namespace }
session{ namespace, agentId, write }
secret{ namespace, name, op }, where op is put, reveal or delete
datasource{ namespace, op, id, … }, where op is create, sync, delete or ingest
text

The stream is meant for the console, which uses its session cookie. The browser EventSource API cannot set custom headers, so outside the browser use a client that can send X-Zekra-Token (for example curl -N). The stream is not filtered by brain.

Account API

People's accounts live under /api/auth/* (sign-in), /api/me/* (the signed-in user) and /api/admin/* (console administrators). These endpoints use the console session, not X-Zekra-Token.

AreaEndpoints
Sign-up and sign-inPOST /api/auth/register, POST /api/auth/login, POST /api/auth/logout, GET /api/auth/me, GET /api/auth/methods
Email verification and passwordsPOST /api/auth/verify-email, POST /api/auth/verify-email/resend, POST /api/auth/password/forgot, POST /api/auth/password/reset
Sign-in by emailed codePOST /api/auth/code/request {email}, POST /api/auth/code/verify {email, code}
Two-factorPOST /api/auth/2fa/challenge; GET /api/me/2fa; POST /api/me/2fa/enroll, /confirm, /disable, /recovery
Single sign-onGET /api/auth/google, GET /api/auth/github, GET /api/auth/apple (each only when configured)
Linked accountsGET /api/me/identities, DELETE /api/me/identities/{ref}
Profile and preferencesGET and PUT /api/me/account/profile, GET and PUT /api/me/account/notifications
Data exportGET and POST /api/me/account/export, GET /api/me/account/export/download
Account deletionGET and POST /api/me/delete, POST /api/me/delete/cancel
Administration/api/admin/stats, /api/admin/users, /api/admin/users/{id} and its roles, disable, enable, verify, resend-verification, 2fa-reset, sessions and sessions/revoke actions

When an account has two-factor on, a sign-in answers 401 with code: "2fa_required" and a challenge. Finish it with POST /api/auth/2fa/challenge and { challenge, code } or { challenge, recovery_code }.

TypeScript client

A minimal typed client using fetch (Node 18+, Deno, Bun or the browser):

ts