Skip to content

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.

MethodPathAuthPurpose
POST/mcpBearer JWTJSON-RPC 2.0 endpoint for MCP clients: initialize, ping, tools/list and tools/call
POST/v1/invokeBearer JWTThe same handler as /mcp, at its original path
POST/v1/list-toolsBearer JWTThe tools you may call, as {"tools":[...]}
GET/v1/subscribe?session_id=<id>Bearer JWTAn SSE stream of the session's tools/call responses

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.

HeaderDirectionMeaning
AuthorizationRequestBearer <jwt>. Required whenever a verifier is configured
X-Session-IDRequest, optionalReuse a session you own. Omit it and aegis mints one
traceparentRequest, optionalW3C trace context. aegis adopts its trace ID as the correlation ID
X-Session-IDResponseThe session used for the call, returned on HTTP 200. Sessions are stored in Redis and expire one hour after aegis creates them. After that, omit the header to get a new one
X-Correlation-IDResponseTies the call to its audit record and to the traceparent sent upstream
bash
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#

MethodPathAuthPurpose
GET/healthzNoneReturns {"status":"ok","service":"aegis-gateway"} with HTTP 200
GET/metricsMETRICS_TOKEN bearer, or none with METRICS_PUBLIC=1Prometheus text format. Returns 503 when neither variable is set and 401 for a missing or wrong token
GET/dashboardNone for the pageThe operator console compiled into the binary

/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.

MethodPathAuthPurpose
GET/admin/v1/overviewcatalog:readCounts of servers, users, roles and policies, shadow findings and the audit row count
GET/admin/v1/audit/verifycatalog:read, operatorVerifies the audit hash chain. Returns ok, rows and first_break_seq
GET/admin/v1/audit/events?limit=&decision=catalog:readAudit rows, newest first. limit defaults to 100 and is clamped to 1 through 1000. decision matches one exact value, such as allow, deny or block
GET/admin/v1/shadowscatalog:readShadow and typosquat findings, report-only
GET/admin/v1/quarantinecatalog:readTools held by the integrity monitor
POST/admin/v1/quarantinecatalog:writeReleases a tool and re-pins its current definition
GET/admin/v1/approvalscatalog:readPending approval requests
POST/admin/v1/approvals/:idcatalog:writeApproves or denies a held call
GET/admin/v1/killswitchcatalog:readActive kill-switches. A tenant admin sees only its own tenant's
POST/admin/v1/killswitchcatalog:write, operatorActivates or deactivates a kill-switch
GET/admin/v1/serverscatalog:readLists catalog servers
POST/admin/v1/serverscatalog:writeRegisters a server
PATCH/admin/v1/servers/:namecatalog:writeUpdates a server
DELETE/admin/v1/servers/:namecatalog:writeRemoves a server and the policies scoped to it. Returns policies_deleted
GET/admin/v1/rolescatalog:readRoles, with how many policies reference each
POST/admin/v1/rolescatalog:writeCreates a role
DELETE/admin/v1/roles/:namecatalog:writeDeletes a role, with its policies and memberships
GET/admin/v1/policiescatalog:readLists policy rules
POST/admin/v1/policiescatalog:writeCreates a policy. Returns its id
DELETE/admin/v1/policies/:idcatalog:writeDeletes a policy
GET/admin/v1/userscatalog:readSubjects and their roles
POST/admin/v1/users/:subject/rolescatalog:writeGrants a role to a JWT subject
DELETE/admin/v1/users/:subject/roles/:rolecatalog:writeRevokes a role
GET/admin/v1/billingcatalog:readCommercial. Your tenant's tier, quota and usage this period
POST/admin/v1/billingcatalog:read, operatorCommercial. Sets a tenant's tier

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.

EndpointFieldsRules
POST /admin/v1/serversname, base_url, enabled (optional, default true), secret_ref (optional)name is 1 to 64 characters of [A-Za-z0-9_-], and aegis-admin is reserved. base_url must be http or https. Loopback, link-local, unspecified, multicast and broadcast IP literals are refused, including octal, hex and IPv4-mapped spellings of them. Private RFC 1918 addresses are allowed. Hostnames are checked as written, not resolved, so also restrict the gateway's egress with a network policy. Outside the default tenant, secret_ref must start with <tenant>/
PATCH /admin/v1/servers/:namebase_url, enabled, secret_ref, all optionalAn omitted field is left unchanged, and a body with none of them returns 400. A PATCH cannot clear a secret_ref
POST /admin/v1/rolesname1 to 64 characters of [A-Za-z0-9_-]
POST /admin/v1/policiesrole, server (optional), tool_pattern (optional, default *), effecteffect is allow, deny or approve. tool_pattern is 1 to 128 characters, and * in it matches any run of characters. Omit server to apply the rule to every server. An unknown role or server returns 404
POST /admin/v1/users/:subject/rolesroleThe role must exist in your tenant, or the call returns 404. The subject is created on its first grant
POST /admin/v1/approvals/:iddecisionapprove or deny. Deciding your own request returns 404
POST /admin/v1/killswitchaction, scope, tenant, server (optional), tool (optional)action is activate or deactivate. scope is tenant, server or tool. Server scope needs server, and tool scope needs server and tool. Values must be non-empty and contain no / or :
POST /admin/v1/quarantineserver, toolReturns 404 when that tool is not quarantined in your tenant
POST /admin/v1/billingtenant, tiertier is free, growth or enterprise
bash
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)#

MethodPathAuthPurpose
GET/.well-known/openid-configurationNoneDiscovery document for the gateway's own issuer
GET/.well-known/jwks.jsonNonePublic signing keys
GET/oauth/authorizeNoneStarts the authorization-code flow. PKCE S256 is required, and plain is rejected
POST/oauth/tokenPKCE verifierExchanges a code for an RS256 access token
POST/scim/v2/UsersSCIM bearerCreates a user
GET/scim/v2/Users/:idSCIM bearerReads a user
PATCH/scim/v2/Users/:idSCIM bearerUpdates a user. active: false removes its role memberships
DELETE/scim/v2/Users/:idSCIM bearerDeletes a user and its memberships
POST/scim/v2/GroupsSCIM bearerCreates a group, which maps to a role, with members. An existing role of the same name is reused
PATCH/scim/v2/Groups/:idSCIM bearerAdds or removes members
DELETE/scim/v2/Groups/:idSCIM bearerDeletes the group's role, with its memberships and policies

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)#

MethodPathAuthPurpose
POST/billing/webhookStripe-Signature headerReceives Stripe subscription events and sets tenant tiers

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.