Build

API reference

The /v1 REST surface is the product for integrators: your intranet calls answer, your Slack calls capture, your agents mount MCP. Deliberately small, versioned, with a typed TypeScript SDK. Your employees never create Engram accounts — identity rides on-behalf-of.

Authentication & tokens

Bearer token auth; every request resolves to one workspace. Two token kinds, minted in Settings → Tokens (shown once, stored hashed, revocable, last-use tracked):

  • Integration tokens — folder-scoped, role-clamped to viewer/contributor (never manager; a DB constraint enforces it), classification-capped. Set allowOnBehalfOf to let a client backend pass subject claims.
  • Personal tokens — bound to a member, resolving their live role on every call. The credential for governance over the API and MCP.

Conventions: errors are RFC 7807 problem+json, list endpoints paginate, and capture/events accept idempotency keys.

Endpoints

EndpointWhat it does
POST /v1/capturePush knowledge → 202 { sourceId }. JSON (text/markdown/html/chat export) or multipart (PDF/DOCX/image/audio, 25MB — extracted, OCR'd, or transcribed server-side)
GET /v1/sources/:idCapture status: queued / compiled / failed → resulting node ids
POST /v1/searchHybrid search → ranked results + snippets
POST /v1/answerRAG answer + citations + derived confidence
POST /v1/recommend“Starting task X” → relevant prompts/workflows/agents/decisions
GET /v1/memoriesList/browse with kind, status, path, tag filters
GET /v1/memories/:idFull memory: body, frontmatter, context, provenance, versions
POST /v1/eventsUsage feedback: used | accepted | dismissed | outcome
POST /v1/tasksRegister a task for routing (Human-AgentOS)
POST /v1/chatOne agentic chat turn → SSE stream
GET /v1/chatList the caller's conversations
GET /v1/chat/conversations/:idTranscript + open confirmation cards
POST /v1/chat/confirmExecute or dismiss a pending mutating chat action
POST /v1/connectors(admin) Register an upload connector: multipart zip + mapping + importAs
GET /v1/connectors(admin) List connectors + last-run summaries
POST /v1/connectors/:id/sync(admin) Trigger a sync run → 202
GET/POST/DELETE /v1/webhooks(admin personal token) Manage outbound webhook subscriptions

On-behalf-of identity — the key mechanism

Client applications authenticate as themselves and pass their user's existing identity:

POST /v1/answer
Authorization: Bearer egm_live_...

{
  "query": "What is the probation period policy?",
  "subject": {
    "externalUserId": "u_4821",            // their id, opaque to Engram
    "roles": ["employee", "dept:product"]  // their role model, verbatim
  }
}

The subject's roles translate through the workspace's role mappings into path and classification grants, then are intersected with the token's own scopes — a token can never delegate more than it holds. The effect: every answer is trimmed to that employee's clearance, inside the client's own UI, and onboarding a client is mint token + fill in role mappings + point a connector at their corpus. No per-client code.

The answer shape

{
  "answer": "Employees receive 15 annual leave days... [[mn_ax82]]",
  "citations": [{
    "nodeId": "mn_ax82", "title": "Leave Policy 2026", "kind": "doc",
    "status": "approved", "path": "/company/policies", "snippet": "..."
  }],
  "confidence": "grounded" | "partial" | "no_answer",
  "queryLogId": "ql_..."   // thumbs-up/down via POST /v1/events
}

Confidence is derived, not model-claimed: grounded means every sentence is cited; no_answer is the explicit refusal path. Citations are resolved against the caller's access before rendering — a citation the caller can't open is never emitted.

Agentic chat over SSE

POST /v1/chat runs one conversation turn and streams SSE events: conversation (id), text-delta, tool-call / tool-result, pending-action, done, error. The loop's tools are the same 33-tool registry as MCP. Mutating tools never execute inside the loop — they file a pending action; your UI renders a confirmation card and calls POST /v1/chat/confirm, which re-resolves the caller's live permissions at confirm time. Conversations persist server-side, private to their actor. The control plane's own chat panel and the desktop bubble are thin frontends over this exact endpoint.

Webhooks

Outbound

Subscribe client systems (admin personal token, /v1/webhooks) to memory.pending_review, memory.approved, and gap.detected. Every delivery is HMAC-SHA256 signed over the raw body — X-Engram-Signature: sha256=<hex> plus X-Engram-Event — verify it exactly like a GitHub or Stripe webhook. Delivery is best-effort with a 5s timeout and never blocks the triggering mutation; endpoints failing 10 times in a row are auto-deactivated.

Inbound (PM webhook)

Point Jira, GitHub, or Linear at /api/webhooks/pm to turn closed issues and merged PRs into tasks and candidate captures. With PM_WEBHOOK_SECRET set, payload signatures are verified: GitHub's x-hub-signature-256, Linear's linear-signature, or the generic x-engram-signature (use that one for Jira Automation, which can't sign natively).

TypeScript SDK

@engram/sdk is a dependency-free typed client; every method maps 1:1 onto a /v1 endpoint:

const engram = new Engram({ baseUrl, token: process.env.ENGRAM_TOKEN });

const res = await engram.answer("How do I submit a travel expense claim?", {
  subject: { externalUserId: user.id, roles: user.roles },
});

await engram.captureFile({ file: pdfBlob, targetFolder: "/company/policies" });

for await (const ev of engram.chat("what's pending my review?")) {
  // conversation · text-delta · tool-call · pending-action · done
}

The SDK currently ships workspace-internal ("@engram/sdk": "workspace:*"); it is publish-ready and used internally by Engram's own surfaces, which keeps the public API honest.