REST API
REST API overview and authentication
The REST API gives you every operation the app and the CLI have: create a document base, load documents, run onboarding, ask questions and wire the result into your own pipeline. One key, one base URL, ordinary HTTPS.
Everything the app and the CLI do runs through this API, so anything you can click you can also script. You need two things to start: a base URL and a key.
Base URL and versions
Production is https://api.engramdynamics.org. The workspace app is at
https://app.engramdynamics.org. Ask the API which contracts it serves:
curl https://api.engramdynamics.org/
{"service": "engram-control-plane", "api_versions": ["1"], "docs": "/docs"}
Write your client against /v1. Paths under /v1 are the
published contract: a field is added, never removed or retyped, and a route keeps its meaning.
GET /v1 is the version document and carries a deprecations list that is
empty until something is genuinely scheduled for removal.
curl https://api.engramdynamics.org/v1
{"version": "1", "deprecations": [], "docs": "/docs"}
The unversioned paths (/corpora, /jobs/{id} and the rest) still work
and are what the web console, the hosted MCP server and older CLI installs already call. They are
frozen rather than supported: new conventions land on /v1 only, and a path that is ever
retired answers with a Deprecation header carrying the removal date for at least one
full release cycle first.
The full OpenAPI document is at /openapi.json and browsable at /docs, so you can generate a client rather than hand-write one.
Create an API key
Keys belong to the workspace, not to a person, so a key keeps working after the developer who
minted it moves on. Create one in the app under API keys, or from the API with a key that already
carries the admin scope:
curl -X POST https://api.engramdynamics.org/v1/api-keys \
-H "Authorization: Bearer <your key>" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-ingest",
"scopes": ["ingest", "query"],
"expires_at": "2027-01-01T00:00:00Z"
}'
{
"id": "8f3c...",
"name": "ci-ingest",
"prefix": "ek_Ab3xY9zQ",
"scopes": ["ingest", "query"],
"last_used_at": null,
"expires_at": "2027-01-01T00:00:00Z",
"revoked_at": null,
"created_at": "2026-09-13T10:04:11Z",
"key": "ek_live-secret-shown-once"
}
key appears in this response and nowhere else. Only its
SHA-256 is stored, so there is no endpoint that can hand it back. Put it in your secret manager
before you close the terminal.
Two rules worth knowing up front. scopes cannot be empty, and an unknown value is a
422 rather than a quietly dropped scope. expires_at is optional (null means it never
expires) and has to be in the future, because a key that expires the moment it is minted can never
be replaced.
Authenticate a request
One header, on every call:
curl https://api.engramdynamics.org/v1/corpora \
-H "Authorization: Bearer <your key>"
The same header takes a session token from the app, which is why the console and your scripts hit identical routes. Python:
import os
import requests
BASE = "https://api.engramdynamics.org/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ENGRAM_API_KEY']}"}
bases = requests.get(f"{BASE}/corpora", headers=HEADERS, timeout=30).json()
for item in bases["items"]:
print(item["id"], item["name"], item["documents_ready"], "ready")
JavaScript:
const BASE = "https://api.engramdynamics.org/v1";
const headers = { Authorization: `Bearer ${process.env.ENGRAM_API_KEY}` };
const res = await fetch(`${BASE}/corpora`, { headers });
const page = await res.json();
console.log(page.items.map((b) => b.name));
Agents use the same key. Engram's hosted MCP server takes it on the same
Authorization header, so there is one credential to mint, scope and revoke across
REST, the CLI and every connected assistant. See MCP quick start.
Scopes
A key carries any combination of three scopes. Give a key the narrowest set that does its job: a CI key that only pushes documents cannot then ask questions or mint another key.
| Scope | What it opens |
|---|---|
ingest | Create a document base, upload and commit documents, upsert, delete a document, register and sync sources, start onboarding. |
query | Ask questions: /chat, /chat/stream
and the MCP query route. |
admin | The workspace-admin surface: mint and revoke keys, subscribe webhooks, source administration, single sign-on, IP allowlist, KMS key, audit export. |
A call made with a key that lacks the scope gets a 403 naming what is missing, so the fix is
obvious: API key missing required scope: ingest. A key holding admin acts
as a workspace admin; without it, it acts as a member.
List and revoke keys
The listing shows every key the workspace has ever held, revoked ones included, because the tombstone is the record an auditor asks for. Secrets are structurally absent from it.
curl https://api.engramdynamics.org/v1/api-keys \
-H "Authorization: Bearer <your key>"
curl -X DELETE https://api.engramdynamics.org/v1/api-keys/8f3c... \
-H "Authorization: Bearer <your key>"
Revocation bites on the very next request: authentication re-reads the tombstone every time, so
there is no cached-credential window. Revoking twice returns the key unchanged rather than erroring,
so a retrying script cannot be told a shutdown failed that actually succeeded.
last_used_at answers "is anything still calling with this?" before you pull the trigger.
Your first call
Create a document base, then read it back:
curl -X POST https://api.engramdynamics.org/v1/corpora \
-H "Authorization: Bearer <your key>" \
-H "Content-Type: application/json" \
-d '{"name": "Support handbook"}'
{
"id": "c_7a1f...",
"name": "Support handbook",
"source_type": "upload",
"status": "new",
"n_documents": 0,
"documents_ready": 0,
"documents_onboarding": 0,
"documents_failed": 0,
"last_synced_at": null,
"created_at": "2026-09-13T10:06:02Z"
}
From here: load documents, run onboarding, ask a question.
Next
Read Conventions and errors before you write retry logic, then walk the ingest protocol in Documents and ingest. Key handling in depth is on API keys and scopes.