Skip to content

HTTP Routes

An a2a-serve agent serves these routes on every listener, and a relay serves the relay routes.

Route
GET /, GET /.well-known/agent-card.json the agent card, which names the pairing bearer scheme where callers need a pairing: on the local URL, on the private URL with --private-access paired, and on the public URL unless --public-access anyone. On the public URL it also names the sign-in scheme
POST / A2A JSON-RPC
other paths A2A HTTP+JSON (REST)
POST /pair exchange a pairing code for a caller or node token
/.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server, /oauth/… Google sign-in on the public URL, when --allow-email is set
/hitl/<token> answer a question in a browser
/inspect/… the owner's web inspector (a2a-serve inspect); on the local listener, and on the private listener of a tailnet
/n/<node>/… webhook nodes. The card of a node is public only if the node is advertised; other cards go to callers who may call the node
/mailbox/api/… the node API used by a2a-serve mailbox
GET /skill.md, GET /mailbox/skill.md skills for agents calling this one (the a2a skill of the server's a2a), and for nodes
GET /download/a2a, GET /download/a2a-serve the server's a2a and a2a-serve binaries, for nodes

Sign-in schemes

On the public URL, the agent card names a google scheme when sign-in is on. It is an alternative to pairing, so the card's security requirements are [{pairing}, {google}].

Settings google scheme Client flow
--allow-email openIdConnect with https://accounts.google.com/.well-known/openid-configuration Google sign-in with the shared app, then the token exchange at /oauth/token
--allow-email, --google-client-id, --google-client-secret oauth2 authorization code flow with PKCE: authorizationUrl <url>/oauth/authorize, tokenUrl <url>/oauth/token, oauth2MetadataUrl <url>/.well-known/oauth-authorization-server a loopback redirect against the agent
neither none a2a-serve pair join

With the shared app, the client puts nonce = base64url(SHA-256(<agent URL>)) in its Google sign-in request, with the agent URL's trailing slash removed. It then sends the ID token to /oauth/token with grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token=<ID token> and subject_token_type=urn:ietf:params:oauth:token-type:id_token. The agent checks the nonce and returns its own access token.

Every agent accepts ID tokens of the shared app. The nonce binds an ID token to one agent, so that agent can't replay it at another. Google drops the nonce from refreshed ID tokens, so the client exchanges the ID token once and uses the agent's token from then on. With the owner's own app, the Google ID token stays in the agent, which needs no binding. The agent keeps accepting the token exchange then.

The shared app is planned: until it is registered, the card names no scheme without --google-client-id.

Browser requests

State-changing requests (any method except GET, HEAD and OPTIONS) to the A2A routes, /pair, /n/… and /mailbox/api/… need Content-Type: application/json or application/a2a+json, or the A2A-Version header. Others get 415. A browser sends such a request to another site only after a CORS preflight, and the server allows CORS only for the agent card.

On every route, the server refuses state-changing requests that a browser marks as cross-site, with Sec-Fetch-Site or an Origin that doesn't match the host. They get 403. The forms of /hitl/<token> and the inspector rely on this check, and the inspector's forms also carry a CSRF token.

Request limits

Each listener handles at most 1024 requests at once, including open streams. It answers requests over this limit with 503 and Retry-After.

/pair, /oauth/… and /hitl/<token> serve callers before they authenticate. They share 256 of those slots, and one client may hold 32 of them. The server tells clients apart as it does for failed pairing attempts, so anonymous callers through the relay share one client. A request over the client's limit gets 429, and a request over the shared limit gets 503. Both carry Retry-After.

Route Largest body Time to send the body
POST /pair, /hitl/<token> 4 KiB 10 seconds in total
POST /oauth/token 64 KiB 10 seconds in total
A2A routes, /n/… 32 MiB 30 seconds without progress

A body over the limit gets 413. When the time runs out, the request fails and the server closes the connection. The timeouts end once the body is read, so a response stream that follows can stay open.

Signatures and headers

The server wakes a webhook node with a POST signed in X-A2A-Serve-Signature: t=<unix time>,v1=<hex HMAC-SHA256 of "<t>.<body>">. The key is the hex HMAC-SHA256 of a2a-serve wake signing key under the node token, so a node checks wakes with its token alone, using a2a-serve mailbox verify. Retries of a wake keep its X-A2A-Serve-Wake ID. Calls between agents carry X-A2A-Serve-Hops, the number of servers a call passed through. A call with a count of 1 or more gets at most the authenticated role.

Relay routes

A relay that a2a-serve relay run runs serves these routes on its port.

Route
GET /.well-known/a2a-serve-relay the relay's versions, and whether people may sign in: {"a2a_serve": "<version>", "agentmesh": "<version>", "signin": true}
/a2a-serve/control/… the relay's control API: GET status, POST invites, GET members, POST members/remove. It needs the admin token as a bearer token. After a wrong token, the next wrong tokens get 429 with Retry-After for 1 to 10 seconds
/a2a-serve/signin/… the relay's identity provider, an OpenID Connect issuer at <relay-url>/a2a-serve/signin with the client ID a2a-serve: GET .well-known/openid-configuration, GET jwks, the device flow (RFC 8628) at POST device/authorize and POST token, which also takes the refresh_token grant, and the pages people sign in on: GET device, GET callback and POST approve. ID tokens are signed with EdDSA and last an hour. 404 when sign-in is off
/admin/…, POST and PUT /policies, /console/…, /metrics 404
other paths Agent Mesh's control plane, routers and ingress, such as /info, /register, /refresh, /readyz and /ingress/…, with the path cleaned of dot segments. They answer 503 while the relay starts. CONNECT, TRACE and other methods that Agent Mesh doesn't serve get 405