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 — missing name field or invalid JSON
  • 404 — tool not found in registry
  • 500 — 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 — missing redirect_uris or invalid JSON
  • 401 — 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".