For AI agents

Configure Engram on the user's behalf

If your user has handed you their Engram API key (eng_live_…), you can call the four endpoints below to check their state and configure their BYOK provider for them, no portal hop required. Bearer auth, JSON in / JSON out, predictable error shapes.

Not on this page yet: account signup via API. Email verification + a phishing-resistant flow are gating that, so we'll ship a device-code OAuth pattern (the gh-CLI / Vercel pattern) when we're confident it's the right shape. For now, the user signs up via the browser at portal.lumetra.io and hands you the resulting API key.

Authentication

Every endpoint accepts an Engram API key via the standard Authorization: Bearer header. The same endpoints also accept JWTs (browser cookie or bearer) so the portal UI keeps working on the exact same handlers.

Authorization: Bearer eng_live_...

Unverified email addresses are rejected with 403 { "code": "email_not_verified" } on every endpoint except GET /auth/me, which intentionally allows unverified tenants through so you can detect a half-onboarded user.

Base URL

All four endpoints live under https://portal.lumetra.io/api. The portal frontend proxies the same prefix to the engram-admin backend, so a single base URL works for browsers, agents, and CLI tools.

Probe state: GET /auth/me

The first call your agent should make. Tells you whether the key is valid, whether the user's email is verified, and whether BYOK is already configured. Allowed pre-verification so you can render a "check your email" hint instead of failing opaquely.

curl https://portal.lumetra.io/api/auth/me \
  -H "Authorization: Bearer $ENGRAM_API_KEY"

# 200 OK
{
  "tenant_id": "…",
  "email": "user@example.com",
  "email_verified": true,
  "billing_plan": "indie",
  "billing_status": "active",
  "byok_configured": false,
  "is_admin": false
}

If email_verified is false, surface a message asking the user to click the verification link in their inbox, then re-probe.

Read current BYOK config: GET /account/byok

Returns the current BYOK state with the key safely redacted (key_prefix: "sk-•••", never plaintext). Use this to decide whether to skip configuration or surface a "you already have a provider configured, switch?" prompt.

curl https://portal.lumetra.io/api/account/byok \
  -H "Authorization: Bearer $ENGRAM_API_KEY"

# 200 OK
{
  "enabled": true,
  "base_url": "https://api.deepseek.com",
  "has_key": true,
  "key_prefix": "sk-•••",
  "last_validated_at": "2026-05-19T17:15:10Z",
  "last_error": null,
  "components": { "synthesis": { … }, "classifier": { … }, … }
}

Configure BYOK: PUT /account/byok

Partial updates: send any subset of base_url, api_key, enabled. The base URL is SSRF-validated against an allowlist; private IPs and IMDS endpoints are rejected. The API key is encrypted server-side immediately and never stored or returned in plaintext. Response shape matches GET /account/byok.

curl -X PUT https://portal.lumetra.io/api/account/byok \
  -H "Authorization: Bearer $ENGRAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "base_url": "https://api.deepseek.com",
    "api_key": "sk-the-users-deepseek-key",
    "enabled": true
  }'

# 200 OK (same shape as GET /account/byok, with last_error cleared)

Be transparent with the user before doing this. Your agent will need to see the user's provider API key (sk-…) to POST it here. That's a meaningful boundary, so say so out loud. The key is encrypted at rest on Lumetra's side and never sent back, but the agent transcript itself will contain the key briefly before the POST. If you can route the key through a credential manager rather than chat input, do.

Validate the configured provider: POST /account/byok/validate

One-shot health check. No body required; the endpoint reads the stored config, queries the provider's /v1/models, picks a cheap chat model from what's returned, and sends a minimal ping via the OpenAI-compatible API. Always responds 200; success or failure is in the body so clients don't have to special-case 4xx for surfaced errors. Persists the result to byok_validated_at + byok_last_error so any subsequent GET reflects the latest state.

curl -X POST https://portal.lumetra.io/api/account/byok/validate \
  -H "Authorization: Bearer $ENGRAM_API_KEY"

# 200, success
{ "ok": true }

# 200, provider returned an error
{ "ok": false, "error": "Error code: 401, Invalid API key" }

# 400, BYOK not configured yet (call PUT /account/byok first)
{ "ok": false, "error": "BYOK not configured" }

# 503, BYOK globally disabled on the server
{ "ok": false, "error": "BYOK globally disabled by server" }

End-to-end happy path

  1. Ask the user for their Engram API key. They get it from their dashboard at portal.lumetra.io. Store it in your agent's secret store; treat as a bearer token.
  2. GET /auth/me, confirm the key works, email_verified is true, note whether byok_configured is already true. If it is, ask the user whether to keep their existing provider or switch.
  3. Pick a provider with the user. We recommend DeepSeek (https://api.deepseek.com): fast, cheap, OpenAI-compatible. Ask for the provider key.
  4. PUT /account/byok with { base_url, api_key, enabled: true }.
  5. POST /account/byok/validate, confirm the round-trip works. If ok: false, show the user the error string verbatim; the messages are usually actionable ("Invalid API key", "Model not found: …", etc.).
  6. Tell the user they're set. Their other agents (Claude Code, Cursor, ChatGPT connector, anything else they've wired up) will now route their store_memory / query_memory calls through this provider automatically.

Caveats & gotchas

  • Scopes aren't enforced today. A read-scoped API key can PUT /account/byok. We'll tighten this in a future pass; for now, treat any Engram API key as full-access.
  • Validate before "you're done." The PUT endpoint doesn't pre-test the credentials; it just stores them. A typo'd key will only surface on the first real store_memory call (with a confusing 412 from the MCP layer). Always follow PUT with validate.
  • The agent sees the provider key. Unavoidable for this flow today, since the agent has to POST it. If you build a UX around this, route the key through a credential manager input rather than chat input where possible, and clear the agent transcript afterwards.
  • Multi-component overrides are a separate endpoint family. PUT /account/byok/component/<role> handles per-component model + prompt + (Enterprise) provider overrides. Not documented here yet; ask if you need it.

Error shapes

All four endpoints return JSON with an error string on failure. Common cases:

401  { "error": "Invalid API key or token" }
401  { "error": "API key required. Use Authorization: Bearer YOUR_API_KEY" }
403  { "code": "email_not_verified" }      # every endpoint except /auth/me
400  { "error": "base_url must be http(s)" }
400  { "error": "base_url host 'foo' resolves to a private/loopback address; use a public provider URL" }
500  { "error": "server crypto unavailable: …" }   # rare; master key misconfigured on server

See also

  • All Engram integrations: 34 agents, frameworks, IDEs, and SDKs that talk to the same backend you're configuring.
  • Docs: REST + MCP reference for the memory surface itself (store_memory, query_memory, etc.).
  • engram-py / engram-js / engram-go: official SDKs for the memory surface.