Skip to content

App API Playbook — /api/

Reference for the /api/ surface: the service endpoints that METIS's own apps expose for their operational needs — agent records and integration webhooks here, Coherence in coherence-PLAYBOOK.md.

If you are integrating with METIS from outside, you want /api/v1/ instead — the open API, documented in API.md. Despite the paths, /api/v1/ is a separate surface rather than a version of this one, and the two do not share authentication. See the summary below.

Live schema: /api/openapi.json — Swagger UI: /api/docs


General Conventions

These conventions describe /api/ only. /api/v1/ has its own auth, error envelope and conventions — see API.md.

Authentication

Endpoints on this surface take the shared bearer token unless noted otherwise:

Authorization: Bearer <API_TOKEN>

Endpoints whose writes must be attributable to a specific person instead require a per-user token, obtained from POST /api/v1/auth/login and sent the same way; those are marked where they appear. Webhook endpoints (/api/hook/*, /api/coherence/hook/*) carry no bearer token and verify the caller by the means their own vendor provides — the Telegram webhook requires Telegram's secret-token header and refuses anything else with 403. "No bearer token" is not "no authentication"; check the endpoint's own entry below before assuming a webhook is open.

Browser session cookies do not authenticate any endpoint on this surface.

CORS

Browser CORS is enabled for /api/*.

  • Allowed origins come from CORS_ALLOWED_ORIGINS (comma-separated env var).
  • Allowed methods: GET, POST, PUT, PATCH, OPTIONS.
  • Allowed request headers: Authorization, Content-Type.
  • Preflight OPTIONS requests return CORS headers for allowed origins.

Credentials are not allowed, so a cross-origin browser cannot send a session cookie here.

Routing

Prefix Auth Documented in
/api/agents Bearer (shared API_TOKEN) this file
/api/chat None (visitor-facing) this file
/api/hook Vendor webhook secret (Telegram: header) this file
/api/coherence Bearer (shared API_TOKEN) or per-user token coherence-PLAYBOOK.md
/api/janus Bearer (the runtime's own service token; the shared API_TOKEN is refused) live schema, tag Janus

Response shapes

  • Top-level responses use typed Ninja Schema classes (no raw dicts).
  • Nullable fields use Optional[T] = None.
  • No deep nesting beyond one level.
  • List endpoints return [] when nothing matches (not 404).

Side effects

  • GET endpoints do not create records.
  • Notes created by the system rather than by a person are recorded with no author.

API v1 — /api/v1/

The open API for external systems integrating with METIS lives at /api/v1/, and is documented in full in API.md: authentication, the error envelope, the access model, every endpoint, and the public field projections.

It is a separate surface from the one this playbook covers, with its own authentication — neither the shared API_TOKEN nor a browser session authenticates /api/v1/. App-owned parts of it are documented by their owning app: outreach-PLAYBOOK.md for /api/v1/outreach/*.

Live schema: /api/v1/openapi.json — Swagger UI: /api/v1/docs


Endpoints

GET /api/agents/{slug}/

Return a single agent by slug.

Param In Type Required Description
slug path string yes Agent identifier

Response 200 — AgentOut

{
  "slug": "onboarding",
  "name": "Onboarding Agent",
  "prompt": "...",
  "infos": {}
}

Errors: 404 if the slug does not match any agent.


POST /api/chat/{agent_slug}/message/ (unauthenticated)

Web-channel endpoint for talking to an agent. Unauthenticated because visitors have no account — not because the exchange is throwaway: each message is recorded on the agent's chat record (a NoteThread, the same one the Telegram channel writes) and feeds agent memory. visitor_id is what keeps a visitor's successive messages on one thread.

Param In Type Required Description
agent_slug path string yes Target agent slug

Request body

{
  "body": "Hello *world*",
  "visitor_id": null
}
Field Required Notes
body yes Human message text. Empty/whitespace-only returns 400.
visitor_id no Omit on first message; send previously returned value to continue same thread.

Response 200 — AgentChatOut

{
  "visitor_id": "83ac84d2-9f2b-4ba4-8d49-fd5eac15f946",
  "thread_key": "web:visitor:83ac84d2-9f2b-4ba4-8d49-fd5eac15f946:agent:12",
  "human_html": "Hello <i>world</i>",
  "agent_html": "<b>Hi</b> back",
  "created_at": "2026-04-05T10:34:12.412312+00:00"
}

The HTML fields use the same Telegram-compatible markdown renderer as bot replies.

Errors:

  • 400 for empty body
  • 404 if agent_slug does not exist
  • 503 if the agent runtime is not configured

POST /api/hook/agents/{slug} (Telegram secret token)

Receives Telegram webhook updates for an agent.

Requires Telegram's X-Telegram-Bot-Api-Secret-Token header, matching the secret registered for that agent when its webhook was set. An update without a matching header is refused with 403 before the body is read; it is not a Telegram update, and the refusal is what makes a forged one visible in the access log. A verified update returns {"ok": true}.

An agent whose webhook was registered without a secret refuses every update until its webhook is set again — that registration is what generates and stores the secret, and there is no way to secure it by hand from outside.

Param In Type Required Description
slug path string yes Agent identifier
X-Telegram-Bot-Api-Secret-Token header string yes The secret registered for this agent's webhook

Errors:

  • 403 if the secret token is missing, wrong, or none is registered for the agent
  • 404 if slug does not exist

Behavior (for a verified update):

  • For text messages from private, group, and supergroup chats:
  • runs the shared agent chat runtime
  • stores chat notes
  • sends the agent reply back to the originating Telegram chat
  • sends a Q&A mirror message to agent.config.telegram.echo_channel (when configured)
  • For all other updates, falls back to the raw echo path (best-effort mirror to the echo channel).

Request / Response Schemas

AgentOut

Field Type Nullable
slug string no
name string no
prompt string no
infos object no

Coherence's own schemas — the person shape it returns, the conversation patch body, and the version_conflict 409 — are documented with the endpoints that use them in coherence-PLAYBOOK.md. The live schema at /api/openapi.json is authoritative for every shape on this surface.