aegis · Reference
API reference
All 44 aegis HTTP endpoints: tool invocation, SSE streaming, health, metrics, the admin API, OAuth, SCIM and the billing webhook, with auth requirements.
On this page
aegis serves every endpoint from one listener on port 8080. The gateway registers 44 HTTP method and path pairs. 30 of them are in the MIT core. The other 14 serve commercial modules (OAuth issuance, SCIM provisioning and billing) and answer 404 or 503 until you configure them.
Error bodies depend on the endpoint. The tool-traffic endpoints answer with a JSON-RPC 2.0 error object, {"jsonrpc":"2.0","error":{"code":...,"message":...},"id":...}. The admin API, and a failed authentication on any JWT-protected route, answer with a plain {"error":"..."} object. The OAuth endpoints add an error_description, and SCIM uses the standard SCIM error schema. Error codes explains each JSON-RPC code.
Tool traffic#
These endpoints sit behind the JWT middleware. A missing or invalid bearer token, or a token for a tenant outside AUTH_ALLOWED_TENANTS, gets HTTP 401 before any handler runs. With no verifier configured, aegis serves every caller as anonymous, so configure AUTH_JWKS_URL or AUTH_HS256_SECRET before you expose the gateway. Set DATABASE_URL as well: without it RBAC is off and every tool call is allowed.
initialize returns the protocol version (default 2025-06-18, overridden by MCP_PROTOCOL_VERSION), the capabilities {"tools":{"listChanged":false}} and the server name aegis-gateway. ping returns an empty result. tools/call requires params.name, and a call without it gets -32600. A notification, which is a request with no id, gets HTTP 202 and an empty body. Any other method gets -32601. aegis does not proxy MCP resources or prompts.
curl -s -i -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"echo","arguments":{"message":"hello"}},"id":1}'
The HTTP status tells you where a call stopped. A body that is not JSON gets 400 with -32700, and invalid JSON-RPC gets 400 with -32600. An unknown or expired session gets 401, a session owned by another subject gets 403, and an unreachable session store gets 500. An RBAC deny gets 403 with -32003, a rate limit 429 with -32029, and an enforced monthly quota (commercial) 402 with -32060. Every other refusal, such as an unknown tool, a threat block, an approval hold, the kill-switch or an unavailable upstream, returns HTTP 200 with a JSON-RPC error body. Check error.code, not only the status.
/v1/list-tools needs no request body and returns only your tenant's tools. With RBAC on, the list is filtered by policy, so a subject with no roles gets an empty list. It returns 502 with an {"error":"..."} body when the gateway has upstream servers but none of them returned tools for your tenant.
/v1/subscribe first sends {"jsonrpc":"2.0","result":{"status":"connected"},"id":null}, then each upstream tools/call response that aegis forwards on that session. Refusals are not streamed. A missing session_id gets 400, an unknown or expired session 401, and a session you do not own 403. Each session allows 64 subscribers, and the next one gets 429. A slow subscriber drops events instead of slowing the call path. Subscriptions live in the memory of the replica that accepted them, so keep a session on one replica if you rely on the stream.
Public endpoints#
/healthz is a static response. It checks no dependencies, so use it for liveness only, not as proof that Redis, Postgres or ClickHouse are reachable. Prefer METRICS_TOKEN over METRICS_PUBLIC=1 for any scrape path that leaves your cluster.
Admin API#
The admin API sits on the protected router under /admin/v1 and takes the same bearer JWT. Access is an RBAC grant on the virtual server aegis-admin. GETs need catalog:read, and mutations need catalog:write, except the billing tier override, which needs catalog:read and the operator. Every admin endpoint returns 503 when DATABASE_URL is unset. Results are scoped to your tenant.
Three actions are operator-only. The operator is any caller in the default tenant. A token with no tenant claim maps to default, so give every customer an explicit tenant claim before you expose the gateway.
Both audit endpoints return 503 when CLICKHOUSE_URL is unset, and the billing endpoints return 503 while billing is off. Other admin errors are 400 for invalid input, 403 for a missing grant or an operator-only action, 404 for an unknown name or id, 409 for a duplicate and 502 for a backend failure. A body that the framework cannot decode is refused before these checks, with a plain-text message: 400 for malformed JSON, 415 without Content-Type: application/json, and 422 for a missing or mistyped field. A change that would leave your tenant with no subject holding catalog:write on aegis-admin is rolled back with 400. Role, policy and membership changes take effect within POLICY_REFRESH_SECS (default 30), for tool traffic and admin checks alike.
Request bodies#
All bodies are JSON.
curl -s -X POST http://localhost:8080/admin/v1/policies \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"role":"beta-only","server":"mock-primary","tool_pattern":"echo","effect":"approve"}'
A successful create returns HTTP 201 with the new policy's id. For the approval flow that an approve policy starts, see Hold, approve and stop tool calls.
Identity (commercial)#
The four OAuth endpoints return 404 until OAUTH_ISSUER is set. /oauth/authorize accepts only the client_id in OAUTH_CLIENT_ID and a redirect_uri that exactly matches an entry in OAUTH_REDIRECT_URIS. /oauth/token takes a form-encoded body with grant_type=authorization_code, code, redirect_uri, client_id and code_verifier. Codes are single-use and expire after 60 seconds. Access tokens last OAUTH_TOKEN_TTL_SECS (default 3600) and carry no tenant claim, so they resolve to the default tenant. Set OAUTH_SIGNING_KEY_PEM in production. Without it, aegis generates a temporary signing key at startup, and its tokens stop validating after a restart or on another replica.
SCIM is on when DATABASE_URL is set together with SCIM_BEARER_TOKEN (tenant from SCIM_TENANT, default default) or SCIM_BEARER_TOKENS (comma-separated tenant:token pairs). Otherwise it returns 503, and a wrong token gets 401. The token is separate from the JWT used for tool traffic. userName must equal the subject claim your identity provider puts in its JWTs. SCIM manages users, groups and memberships. It never writes policies, so the rules attached to each role stay under the admin API. A SCIM change that would leave your tenant with no administrator is refused with 409. See Identity and access.
Billing webhook (commercial)#
The route is live when BILLING_ENABLED, DATABASE_URL and STRIPE_WEBHOOK_SECRET are all set. aegis verifies the v1 HMAC-SHA256 signature in-process against STRIPE_WEBHOOK_SECRET (comma-separate two secrets during rotation) and refuses a timestamp outside STRIPE_WEBHOOK_TOLERANCE_SECS (default 300). Bodies are capped at 256 KiB, and a larger one gets 413. The endpoint returns 404 when billing is off, and 400 for a missing, invalid or stale signature, in which case nothing changes. It returns 500 on a transient database error so that Stripe redelivers. Events are deduplicated by id. An event older than the last one applied to that tenant is ignored, so a replayed or out-of-order delivery cannot restore an old tier.