Gateway API#
Complete reference for all asya-gateway HTTP endpoints. Routes are split across two deployments (api and mesh). See core-gateway.md for deployment architecture and auth configuration.
Route Map#
| Method | Path | Deployment | Auth | Caller |
|---|---|---|---|---|
GET |
/.well-known/agent.json |
api | Public | External agents |
POST |
/a2a/ |
api | A2A auth | External agents |
POST |
/mcp |
api | MCP auth | LLM clients, asya-lab |
GET |
/mcp/sse |
api | MCP auth | LLM clients, asya-lab |
POST |
/tools/call |
api | MCP auth | LLM clients, asya-lab |
GET |
/stream/{id} |
api | MCP/A2A auth | Any client (A2A, MCP, UI) |
GET |
/.well-known/oauth-protected-resource |
api | Public | MCP clients |
GET |
/.well-known/oauth-authorization-server |
api | Public | MCP clients |
POST |
/oauth/register |
api | Open or token | MCP clients |
GET |
/oauth/authorize |
api | Public | MCP clients |
POST |
/oauth/token |
api | Public | MCP clients |
POST |
/mesh |
mesh | Network isolation | Sidecar (fanout) |
GET |
/api/v1/mesh/{id} |
mesh | Network isolation | Sidecar (pre-flight check) |
POST |
/api/v1/mesh/{id}/events |
mesh | Network isolation | Sidecar (all status + FLY events) |
GET |
/mesh/{id} |
mesh | Network isolation | asya-lab |
GET |
/mesh/{id}/stream |
mesh | Network isolation | Sidecar, asya-lab |
POST |
/mesh/config-reload |
mesh | Network isolation | Operators |
GET |
/health |
api + mesh | Public | K8s probes |
External API Routes (api deployment)#
Rearchitected adapters
In the new architecture, A2A routes are served by the standalone
a2a-adapter binary (:8083) and MCP routes by mcp-adapter (:8082).
Both use mesh-api as their backend via the two-step dispatch pattern.
The routes and request/response formats are unchanged.
A2A Protocol#
GET /.well-known/agent.json#
Returns the public Agent Card. Unauthenticated — required by the A2A spec for discovery.
Response 200 application/json:
{
"name": "asya-gateway",
"version": "1.0.0",
"supportedInterfaces": [{
"url": "https://gateway.example.com/a2a/v1",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}],
"capabilities": {
"streaming": true,
"extendedAgentCard": true
},
"securitySchemes": {
"apiKey": { "apiKeySecurityScheme": { "location": "header", "name": "X-API-Key" } },
"bearer": { "httpAuthSecurityScheme": { "scheme": "bearer", "bearerFormat": "JWT" } }
},
"security": [{ "apiKey": {} }, { "bearer": {} }],
"skills": [...]
}
POST /a2a/#
A2A JSON-RPC 2.0 endpoint. All A2A operations use this single endpoint. Requires authentication when any A2A auth env var is set.
Request application/json — JSON-RPC 2.0 envelope:
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": { ... }
}
Supported methods: message/send, message/stream, tasks/get, tasks/list, tasks/cancel, tasks/resubscribe, agent/authenticatedExtendedCard, pushNotification/set, pushNotification/get, pushNotification/list, pushNotification/delete.
Response 200 application/json — JSON-RPC 2.0 response.
Blocking behavior: The message/send method holds the connection open and streams SSE events (progress updates, FLY partial payloads) until the task reaches a terminal state (completed, failed, canceled, input_required, or auth_required). The client receives the final task result as the last event in the stream. Use message/stream for an explicitly streaming variant, or tasks/get for polling.
Auth errors 401 — missing/invalid credentials.
MCP Protocol#
POST /mcp#
MCP Streamable HTTP transport. Accepts MCP JSON-RPC 2.0 requests. The mark3labs/mcp-go library handles protocol framing.
Request application/json:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "my-tool",
"arguments": { "key": "value" }
}
}
Common methods: initialize, tools/list, tools/call.
Response 200 application/json — MCP JSON-RPC 2.0 response.
Auth — Bearer token required when ASYA_MCP_API_KEY or ASYA_MCP_OAUTH_ENABLED=true is configured. Returns 401 with WWW-Authenticate: Bearer on failure.
GET /mcp/sse#
MCP SSE transport. Establishes a persistent SSE connection for MCP session management. Use for clients that require SSE-based MCP sessions.
Response 200 text/event-stream — SSE stream of MCP protocol messages.
Auth — same as POST /mcp.
POST /tools/call#
REST convenience wrapper for MCP tool invocation. Simpler than the full MCP protocol; does not require session management.
Request application/json:
{
"name": "text-processor",
"arguments": {
"text": "hello world",
"model": "gpt-4"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Tool name as registered |
arguments |
object | No | Tool-specific input parameters |
Response 200 application/json — MCP CallToolResult:
{
"content": [
{
"type": "text",
"text": "{\"task_id\":\"5e6fdb2d\",\"status_url\":\"/mesh/5e6fdb2d\"}"
}
],
"isError": false
}
Error responses:
400— missingnamefield or invalid JSON404— tool not found in registry500— tool handler failed
Auth — same as POST /mcp.
GET /stream/{id}#
Protocol-agnostic SSE stream for ephemeral FLY events. Provides live per-token streaming for any client (A2A, MCP, UI) without protocol-specific wrapping. Complementary to /mesh/{id}/stream (internal mesh access) but exposed on the API gateway.
Response 200 text/event-stream:
event: partial
data: {"type":"text_delta","token":"Hello"}
event: partial
data: {"type":"text_delta","token":" world"}
⚠️ FLY events are ephemeral — clients must connect before or during task execution. Historical FLY replay is NOT supported.
Auth — Bearer token required when ASYA_MCP_API_KEY or ASYA_MCP_OAUTH_ENABLED=true is configured.
OAuth 2.1 Endpoints#
Only registered when ASYA_MCP_OAUTH_ENABLED=true. All public (called before auth is established).
GET /.well-known/oauth-protected-resource#
RFC 9728 Protected Resource Metadata. Points MCP clients to the authorization server.
Response 200 application/json:
{
"resource": "https://gateway.example.com",
"authorization_servers": ["https://gateway.example.com"]
}
GET /.well-known/oauth-authorization-server#
RFC 8414 Authorization Server Metadata. Exposes all OAuth endpoint URLs and capabilities.
Response 200 application/json:
{
"issuer": "https://gateway.example.com",
"authorization_endpoint": "https://gateway.example.com/oauth/authorize",
"token_endpoint": "https://gateway.example.com/oauth/token",
"registration_endpoint": "https://gateway.example.com/oauth/register",
"scopes_supported": ["mcp:invoke", "mcp:read"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"]
}
POST /oauth/register#
RFC 7591 Dynamic Client Registration. Registers a new OAuth client.
Optionally protected by ASYA_MCP_OAUTH_REGISTRATION_TOKEN — when set, requires Authorization: Bearer <registration-token>.
Request application/json:
{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"scope": "mcp:invoke mcp:read"
}
| Field | Type | Required | Description |
|---|---|---|---|
redirect_uris |
string[] | Yes | Allowed redirect URIs (localhost or HTTPS) |
client_name |
string | No | Human-readable client name |
scope |
string | No | Requested scopes; defaults to all supported |
Response 201 application/json:
{
"client_id": "b3e7f9a2-...",
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"scope": "mcp:invoke mcp:read"
}
Error responses:
400— missingredirect_urisor invalid JSON401— registration token required but missing/wrong
GET /oauth/authorize#
Authorization Code endpoint. Auto-approves registered public clients (no user login UI — gateway is designed for machine-to-machine flows). PKCE S256 is required.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
client_id |
Yes | Registered client ID |
redirect_uri |
Yes | Must match a registered URI |
response_type |
Yes | Must be code |
code_challenge |
Yes | PKCE S256 challenge (base64url of SHA-256 of verifier) |
code_challenge_method |
Yes | Must be S256 |
scope |
No | Requested scopes; defaults to client's registered scopes |
state |
No | Opaque value echoed back to redirect URI |
Response 302 Found — redirect to redirect_uri?code=<code>&state=<state>.
Authorization codes expire after 5 minutes and are single-use.
POST /oauth/token#
Token exchange and refresh.
Request application/x-www-form-urlencoded
Authorization Code exchange:
| Field | Required | Description |
|---|---|---|
grant_type |
Yes | authorization_code |
code |
Yes | Code from /oauth/authorize redirect |
client_id |
Yes | Registered client ID |
redirect_uri |
Yes | Must match the URI used in the auth request |
code_verifier |
Yes | PKCE verifier (raw random string, SHA-256 matches challenge) |
Refresh Token:
| Field | Required | Description |
|---|---|---|
grant_type |
Yes | refresh_token |
refresh_token |
Yes | Token from previous response |
client_id |
Yes | Registered client ID |
Response 200 application/json:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJl...",
"scope": "mcp:invoke mcp:read"
}
Access tokens are HMAC-SHA256 JWTs containing iss, aud, sub (client ID), scope, iat, exp, jti claims. Refresh tokens are opaque, stored hashed in PostgreSQL, and rotated on each use.
Health#
GET /health#
Response 200 text/plain: OK
Mesh Routes (mesh deployment) — Deprecated#
Deprecated — replaced by asya-mesh-api
The mesh routes below are from the legacy mode=mesh gateway deployment.
New deployments should use the standalone asya-mesh-api binary.
See Mesh API spec for the new API and migration guide.
Called exclusively by sidecars and crew actors within the cluster. No authentication — unreachable externally by network topology.
Task Lifecycle#
POST /mesh#
Creates a fanout child task. Called by the sidecar when the runtime returns an array response, triggering fan-out to parallel actor pipelines.
Request application/json:
{
"id": "task-abc-1",
"parent_id": "task-abc",
"prev": ["actor-a"],
"curr": "actor-b",
"next": ["actor-c", "actor-d"]
}
Response 201 application/json: { "status": "created", "id": "task-abc-1" }
GET /mesh/{id}#
Returns full task state.
Response 200 application/json:
{
"id": "task-abc",
"parent_id": null,
"context_id": "ctx-xyz",
"status": "running",
"route": { "prev": ["actor-a"], "curr": "actor-b", "next": ["actor-c"] },
"payload": { ... },
"result": null,
"progress_percent": 33.0,
"current_actor_name": "actor-b",
"message": "actor-b: processing",
"actors_completed": 1,
"total_actors": 3,
"created_at": "2026-03-08T10:00:00Z",
"updated_at": "2026-03-08T10:00:05Z"
}
Task status values: pending, running, succeeded, failed, paused, canceled, auth_required.
Error 404 — task not found.
GET /mesh/{id}/stream#
SSE stream of task update events. Replays historical progress and status updates first, then streams live updates. Sends keepalive comments every 15 seconds. Closes when task reaches a final status.
Response 200 text/event-stream:
Progress update:
event: update
data: {"id":"task-abc","status":"running","progress_percent":33.0,"curr":"actor-b","task_state":"processing","message":"actor-b: processing","timestamp":"2026-03-08T10:00:05Z"}
FLY event (e.g. LLM token stream):
event: partial
data: {"type":"text_delta","token":"Hello"}
⚠️ FLY events are ephemeral — they are broadcast in real-time via PG LISTEN/NOTIFY but not persisted. Clients connecting after task completion will NOT see historical FLY events, only progress and status updates.
The SSE event type defaults to partial. If the FLY payload contains an A2A-specific key (artifact_update, status_update, or message), the event type matches that key.
GET /api/v1/mesh/{id}#
Pre-flight check. The sidecar calls this before processing to detect canceled or paused messages. If the response indicates canceled or paused, the sidecar skips processing and routes the envelope to x-sink.
Response 200 application/json:
{
"id": "task-abc",
"status": "running"
}
Status values: pending, running, succeeded, failed, paused, canceled.
Error 404 — task not found (sidecar proceeds with normal processing).
POST /api/v1/mesh/{id}/events#
Unified event endpoint. All sidecar-to-gateway communication (progress updates, FLY streaming tokens, final status) goes through this single endpoint. The type field distinguishes event kinds.
Request application/json:
Status event (progress update):
{
"type": "status",
"status": "processing",
"data": {
"prev": ["actor-a"],
"curr": "actor-b",
"next": ["actor-c"],
"status": "processing",
"message": "actor-b: processing input"
}
}
FLY event (ephemeral streaming token):
{
"type": "fly",
"data": {"text": "Hello"}
}
Final status event (from x-sink/x-sump):
{
"type": "status",
"status": "succeeded",
"data": {
"id": "task-abc",
"status": "succeeded",
"result": { "output": "processed text" }
}
}
Response 204 — event accepted.
FLY events are delivered to SSE subscribers via in-process Go channels. FLY events are NOT written to the database — they exist only in memory during task execution.
Status ordering: The mesh-api enforces monotonic status progression. Status events with a lower-order status than the current one are silently dropped:
| Order | Statuses |
|---|---|
| 0 | pending |
| 1 | running |
| 2 | paused |
| 3 | succeeded, failed, canceled (terminal) |
Once a message reaches a terminal status, no other status can overwrite it. FLY events have no ordering constraint — they are appended in arrival order.
POST /mesh/config-reload#
Triggers immediate reload of tool registry from ConfigMaps. Used after deploying new AsyncActor resources.
Response 200: { "status": "ok" }
Common Error Formats#
Standard HTTP errors#
Plain text body (e.g., Method not allowed, Task not found).
OAuth errors#
application/json per RFC 6749 section 5.2:
{
"error": "invalid_grant",
"error_description": "code_verifier does not match challenge"
}
MCP/A2A errors#
JSON-RPC 2.0 error envelope:
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32601, "message": "Method not found" }
}
Auth failures on MCP routes return 401 with WWW-Authenticate: Bearer realm="asya-gateway".