Skip to main content

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.

DeploymentAPI baseWebhook base
Self-hosted, running locallyhttp://localhost:3001http://localhost:3001
Behind the web apphttps://<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/clerk is 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_URL is 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 pathWhat it covers
/api/agentsAgent definitions, stats, triage preview, status
/api/archivalMessage archival — config, jobs, trim, queries
/api/authAuthorization administration — rules, moderation, channel grants. Not a login flow
/api/authorization-edgesReBAC relationship edges
/api/bot-assignmentWhich bots serve which channels, groups, and defaults
/api/bot-groupsBot grouping
/api/botsTenants, bots, and their platform integrations
/api/clerk-adminClerk users, organizations, memberships
/api/configRead-only runtime configuration
/api/context-sources, /api/context-tracesContext assembly inputs and traces
/api/current-userThe calling principal, and the tenants it can reach
/api/dashboardDashboard status and activity
/api/database, /api/migrationsSchema and migration inspection
/api/definitionsAgent, tool, and capability definitions; the tool marketplace
/api/email-auth, /api/email-threadsEmail sender authorization and threads
/api/identity-linksLinking platform identities to users
/api/interactionsMessage and chat history
/api/keysProvider API keys (BYOK), always tenant-scoped
/api/otelOpenTelemetry ingest proxy
/api/platforms, /api/platform-usersPlatform stats and platform-side users
/api/systemMetrics, services, logs, info, performance
/api/telegram-authTelegram login flow
/api/tenantTenant selection for the session
/api/usageUsage and quota, per tenant
/api/usersUsers, stats, chats, quotes
/api/weftWeft apps — member-level read of apps, commands, bindings, and published specs
/api/admin/gdprRight-to-erasure requests, tenant-admin — see Data export & erasure. There is no export endpoint
/api/admin/weftWeft apps — templates, apps, records, bindings, install, spec editing (drafts + publish)
/api/tools, /api/integrations, /api/admin/integrationsExternal 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.

Earlier versions of this page documented a { "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.

GuaranteeStateActive when
Every /api route requires a principal✅ Activealways, except the public allowlist — see Authentication
Tenant scope taken from the principal✅ Activealways
Inbound webhook rate limit✅ Activealways, except NODE_ENV=test
API request rate limiting❌ Not implementednever
External tool routers (/api/tools, /api/integrations, /api/admin/integrations)⚙️ Off by defaultTOOL_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.