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 |
/mesh/{id} |
mesh | Network isolation | Sidecar, asya-lab |
GET |
/mesh/{id}/stream |
mesh | Network isolation | Sidecar, asya-lab |
GET |
/mesh/{id}/active |
mesh | Network isolation | Sidecar |
POST |
/mesh/{id}/progress |
mesh | Network isolation | Sidecar |
POST |
/mesh/{id}/final |
mesh | Network isolation | x-sink, x-sump |
POST |
/mesh/{id}/fly |
mesh | Network isolation | Sidecar (FLY events) |
POST |
/mesh/config-reload |
mesh | Network isolation | Operators |
GET |
/health |
api + mesh | Public | K8s probes |
External API Routes (api deployment)#
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)#
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 /mesh/{id}/active#
Allows sidecars to check whether a task is still accepting updates (not timed out or completed).
Response 200: { "active": true }
Response 410 Gone: { "active": false }
POST /mesh/{id}/progress#
Reports per-actor progress. Called by the sidecar at three checkpoints: received -> processing -> completed.
Progress formula: (len(prev) + status_weight) x 100 / total_actors
| Actor state | status_weight |
|---|---|
received |
0.1 |
processing |
0.5 |
completed |
1.0 |
Request application/json:
{
"prev": ["actor-a"],
"curr": "actor-b",
"next": ["actor-c"],
"status": "processing",
"message": "actor-b: processing input"
}
Response 200: { "status": "ok", "progress_percent": 38.3 }
POST /mesh/{id}/final#
Reports final task status. Called by x-sink (on success) or x-sump (on failure).
Request application/json:
{
"id": "task-abc",
"status": "succeeded",
"result": { "output": "processed text" },
"current_actor_name": "x-sink",
"metadata": { "s3_uri": "s3://bucket/task-abc/result.json" }
}
Response 200: { "status": "ok" }
POST /mesh/{id}/fly#
Broadcasts ephemeral FLY events from the sidecar to SSE clients. Body limited to 1 MiB. The type field determines the SSE event type (defaults to partial).
FLY events are broadcast via PG LISTEN/NOTIFY for cross-process delivery (mesh → api gateway) and via in-process NotifyFLY channels for same-process subscribers. Events are NOT written to the database — they exist only in memory during task execution.
Request application/json — arbitrary JSON, passed through verbatim.
Response 200 — empty body.
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".