API Reference
Mesachat exposes a REST API over HTTP. It is the same API the web app and admin console use — there is no separate public tier, and no separate set of guarantees for external callers.
Derived from backend/src/api/. Where behaviour depends on a configuration flag this reference
names the flag rather than asserting what is true in your deployment; see
Guarantee status.
Base URL
There is no dedicated public API hostname. The backend has no public DNS record of its own; it
is reached through the app origin, which proxies /api/* to it.
| Deployment | API base | Webhook base |
|---|---|---|
| Self-hosted, running locally | http://localhost:3001 | http://localhost:3001 |
| Behind the web app | https://<your-app-host>/api/… | value of WEBHOOK_BASE_URL |
Two things follow, and both bite people:
/api/*and/webhooks/*are different surfaces. Webhooks are mounted at the server root, not under/api.POST /api/webhooks/clerkis not a route. See Webhooks.- The webhook origin may differ from the app origin. Deployments that keep the app private
still have to let Telegram and Clerk in, so webhooks are commonly published on their own
hostname. Whatever
WEBHOOK_BASE_URLis set to is what the platform is told to call.
No version prefix
Paths are /api/<router>, with no /v1. The API is versioned by deployment, not by URL: it ships
with the app that consumes it, and endpoint shapes can change in a release. Pin to a deployment you
control rather than assuming stability across upgrades.
The API surface
Every router mounted under /api (backend/src/api/server.ts):
| Base path | What it covers |
|---|---|
/api/agents | Agent definitions, stats, triage preview, status |
/api/archival | Message archival — config, jobs, trim, queries |
/api/auth | Authorization administration — rules, moderation, channel grants. Not a login flow |
/api/authorization-edges | ReBAC relationship edges |
/api/bot-assignment | Which bots serve which channels, groups, and defaults |
/api/bot-groups | Bot grouping |
/api/bots | Tenants, bots, and their platform integrations |
/api/clerk-admin | Clerk users, organizations, memberships |
/api/config | Read-only runtime configuration |
/api/context-sources, /api/context-traces | Context assembly inputs and traces |
/api/current-user | The calling principal, and the tenants it can reach |
/api/dashboard | Dashboard status and activity |
/api/database, /api/migrations | Schema and migration inspection |
/api/definitions | Agent, tool, and capability definitions; the tool marketplace |
/api/email-auth, /api/email-threads | Email sender authorization and threads |
/api/identity-links | Linking platform identities to users |
/api/interactions | Message and chat history |
/api/keys | Provider API keys (BYOK), always tenant-scoped |
/api/otel | OpenTelemetry ingest proxy |
/api/platforms, /api/platform-users | Platform stats and platform-side users |
/api/system | Metrics, services, logs, info, performance |
/api/telegram-auth | Telegram login flow |
/api/tenant | Tenant selection for the session |
/api/usage | Usage and quota, per tenant |
/api/users | Users, stats, chats, quotes |
/api/weft | Weft apps — member-level read of apps, commands, bindings, and published specs |
/api/admin/gdpr | Right-to-erasure requests, tenant-admin — see Data export & erasure. There is no export endpoint |
/api/admin/weft | Weft apps — templates, apps, records, bindings, install, spec editing (drafts + publish) |
/api/tools, /api/integrations, /api/admin/integrations | External tool connections — gated, see below |
GET /api/health sits outside the routers, on the app itself.
Response format
There is no response envelope. Handlers return the resource directly:
curl -H "Authorization: Bearer $TOKEN" http://localhost:3001/api/users
[ { "id": "user_123", "name": "Alice" } ]
Errors carry an HTTP status and a JSON body whose error is a string:
{ "error": "Bot not found" }
Some validation failures add detail alongside it, but there is no code field and no
machine-stable error taxonomy. Branch on the HTTP status; treat error as human-readable text.
{ "data": …, "error": { "code", "message" } }envelope. No such envelope has ever been implemented. If you wrote a client against it, it is parsing responses that do not exist.
Rate limiting
API requests are not rate-limited. There are no request tiers, no per-tenant quotas enforced at
the HTTP layer, and no X-RateLimit-* headers — searching the backend for them returns nothing.
The one limiter in the codebase applies to inbound webhooks only
(backend/src/api/routes/webhooks.ts): 600 requests per client IP per minute, across the whole
/webhooks surface. It emits IETF draft-7 RateLimit headers, not X-RateLimit-*, and is skipped
when NODE_ENV=test. Its purpose is to stop forged or replayed deliveries from driving work
before signature verification — not to meter legitimate use.
Separately, /api/usage exposes per-tenant quota counters. Those are an accounting and
enforcement mechanism inside the product, not an HTTP rate limit: exceeding one does not produce
a 429.
If you are putting this API on a public network, put a limiter in front of it. One is not built in.
Guarantee status
This reference states what the code does and under which named condition — never what is true in a
particular deployment. Check your environment; do not trust this table for it. Convention as in
backend/docs/AUTHORIZATION.md § Guarantee status, in the source repository.
| Guarantee | State | Active when |
|---|---|---|
Every /api route requires a principal | ✅ Active | always, except the public allowlist — see Authentication |
| Tenant scope taken from the principal | ✅ Active | always |
| Inbound webhook rate limit | ✅ Active | always, except NODE_ENV=test |
| API request rate limiting | ❌ Not implemented | never |
External tool routers (/api/tools, /api/integrations, /api/admin/integrations) | ⚙️ Off by default | TOOL_INTEGRATIONS_ENABLED=true. Otherwise every route under them returns 503 before any router logic runs |
Quick example
# Bots the caller can see. Tenant scope comes from the token, not the query string.
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3001/api/bots
Continue to Authentication for how to obtain $TOKEN, or
REST API for the endpoint list.