How A2A works
An A2A Surface lets external AI agents discover and call your Product’s Capabilities through the Agent-to-Agent (A2A) protocol. A caller reads the Surface’s Agent Card, then sends JSON-RPC requests that run a Capability as an A2A task. Frameworks such as LangChain, CrewAI, and Vercel AI SDK can call the Surface, and so can agents in a partner organization, without a custom integration.
Runtype A2A Surfaces use the A2A Protocol v1.0 wire format.
How discovery works
The Agent Card describes the Surface’s name, skills, JSON-RPC endpoint, and authentication schemes. The following command fetches it:
Replace the following:
YOUR_PRODUCT_ID: the ID of the ProductYOUR_SURFACE_ID: the ID of the A2A Surface
The Agent Card is public, so the request needs no key. Runtype serves the card only while the Surface is active. For an inactive Surface, the request returns 404. Runtype marks the card as cacheable for up to one hour, so a client that caches it can take up to an hour to see a changed name or skill list.
Each entry in the card’s skills array is one skill that a caller can run. The skill id is the exposed name of the Surface item, or the Capability name when you set no exposed name. An external agent Capability adds one skill for each skill that the external agent exposes. Pass that id as params.metadata.skill to run the skill.
For a lightweight connectivity check, send GET to /v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/a2a/info. The public response includes the protocol version, the JSON-RPC endpoint, and the Agent Card URL.
How invocation works
A caller sends every request as a JSON-RPC 2.0 POST to the A2A endpoint, with an A2A Surface key. The following command runs the answer_questions skill:
Replace a2a_YOUR_SURFACE_KEY with an A2A Surface key. You can also send the A2A Surface key in an X-API-Key header instead of Authorization.
The endpoint accepts these methods:
SendMessage: runs a task and returns it after it finishes.SendStreamingMessage: runs a task and streams updates over Server-Sent Events (SSE).GetTask: gets a task by ID.ListTasks: lists tasks with cursor pagination.CancelTask: cancels a task that hasn’t finished.SubscribeToTask: resumes the SSE stream for a task.
When SendMessage or SendStreamingMessage starts a Runtype run, the HTTP response carries the run’s ID in the X-Runtype-Execution-Id header. A request refused before a run starts carries no header, and neither does a skill backed by an external agent, which runs outside Runtype. A SendStreamingMessage call to a Claude-managed skill also carries no header: its stream starts before the run does, and the run’s ID is the task ID that the stream reports. Use the ID to correlate logs, quote it in a support request, or open the run with GET /v1/runs/{executionId} and an API key from your Runtype account.
The endpoint also accepts the slash-style method names that A2A 0.3 clients send, such as message/send. Use the v1.0 names in new integrations.
How requests route to a skill
The Surface’s orchestration mode decides which skill runs when a request names none:
- Delegate mode lists each Capability as a separate skill. The caller selects a skill with
params.metadata.skill. - Managed mode adds an orchestrator skill to the Agent Card, named
assistantunless you set another Skill name. If a Capability already uses that name, Runtype adds_orchestratorto the orchestrator skill’s name. The orchestrator skill routes a request to a matching Capability. Callers can still select an individual skill by name.
In Managed mode, omit params.metadata.skill to send the request to the orchestrator skill. In Delegate mode, always set params.metadata.skill. If you omit it, Runtype runs the first Capability that the key can run instead of returning an error. A scoped key can’t use the orchestrator skill, so the same fallback applies to a scoped key in Managed mode.
For more information about the two modes, see Surface orchestration modes.
What the Capability receives
For a Flow or Agent Capability, Runtype joins the text parts of params.message with newlines and passes the result as the user message. Runtype accepts data, url, and raw parts but doesn’t pass them to a Flow or Agent Capability.
A contextId groups related tasks, and you can filter ListTasks by it. It doesn’t replay earlier turns to a Flow or Agent Capability, so each task sees only the current message. To continue a conversation, include the context that the Capability needs in the message text.
An external agent Capability is different: Runtype forwards the full message, including every part and the contextId, to the external agent.
Limitations
A2A Surfaces have these limits:
- A2A can’t run a Capability that pauses for approval or input. If a Capability’s Flow has a pause step, or its Agent requires tool approval, the request fails with
-32603. ForSendMessage, the error message starts withTask execution failed:. ForSendStreamingMessage, the stream sends atask/errorevent. Remove pause steps and required tool approvals from Capabilities that you expose on an A2A Surface. - Runtype doesn’t deliver push notifications. It accepts
params.configuration.pushNotificationConfigbut never calls the webhook. To detect completion, useSendStreamingMessage,SubscribeToTask, or poll withGetTask. SendMessagereturns after the task finishes and ignoresparams.configuration.blocking. For a long-running task, useSendStreamingMessage.
Message format
Requests and responses follow these v1.0 conventions:
- Each message part has exactly one of
text,data,url, orraw:{ "text": "Hello" },{ "data": { "key": "value" } },{ "url": "https://example.com/file.pdf" }, or{ "raw": "YWJj" }. Don’t add akindfield. - A message’s
roleisROLE_USERorROLE_AGENT. - Task states use
TASK_STATE_*values. - A
SendMessageresult holds the task in itstaskfield. - Each SSE event’s
resultcontains eitherstatusUpdateorartifactUpdate. To detect completion, watch for a terminal task state and the end of the stream.
Method examples
The following examples show the params and results for each method. Send each one in the same JSON-RPC envelope and with the same headers as the invocation example.
SendMessage
A successful SendMessage response wraps the finished task in result.task:
To group a follow-up task with this one, set contextId in params.message.
SendStreamingMessage
SendStreamingMessage takes the same params as SendMessage. Each data: line in the stream holds a JSON-RPC response envelope. A stream is similar to the following:
A task/error event reports a failure. Parse the JSON-RPC payload in data, and use the event name only for debugging. The stream closes after the terminal status.
GetTask
GetTask takes the task id and an optional historyLength, which sets how many history messages to include:
ListTasks
ListTasks lists the Surface’s tasks, most recently updated first, with pageSize and pageToken for cursor pagination:
pageSize defaults to 50 and accepts up to 100. You can filter by contextId or status. Set includeArtifacts to true to include artifacts. The default is false. The result includes tasks, nextPageToken, pageSize, and totalSize.
CancelTask
CancelTask takes the id of a task that hasn’t finished:
A successful cancel returns the task with the TASK_STATE_CANCELED state. Canceling a task that already completed, failed, or was canceled returns -32005.
SubscribeToTask
SubscribeToTask takes the id of an existing task and resumes its SSE stream:
Task states
A task reports one of these states:
No A2A method resumes a task that is waiting for input or authorization.
Errors
Runtype returns errors as JSON-RPC error objects with a numeric code, a message, and a data array. The data array holds a google.rpc.ErrorInfo entry with a stable reason. When Runtype has extra detail, such as the list of valid skills, it adds the detail to metadata.detail as a JSON string:
The Surface returns these standard JSON-RPC error codes:
The following Runtype A2A Surface error codes are Runtype’s own. Other A2A servers can use these numbers for different errors, so branch on reason rather than on the numeric code:
When a key exceeds its rate limit, the response includes a Retry-After header with the number of seconds to wait. The streaming methods return the same error as a task/error event.
A Test key runs up to 50 tasks per day, and the count resets at midnight UTC. When a Test key reaches the limit, Runtype returns -32600 with a message that names the limit. Use a Production key for production traffic. Your account’s daily execution quota also applies, and reaching it also returns -32600.
Access control
An A2A Surface controls access in these ways:
- Public Agent Card: anyone can read the Agent Card. Every JSON-RPC request requires a valid A2A Surface key, which starts with
a2a_. - Rate limits: each A2A Surface key has its own per-minute and per-day limits.
- Capability scoping: a scoped A2A Surface key runs only the Capabilities in its scope.
- Execution logs: each A2A task appears in your execution records.
A scoped key never receives 403 on an A2A Surface. A skill outside the key’s scope returns -32002, and the error lists only the skills that the key can run. The orchestrator skill isn’t available to a scoped key. GetTask and CancelTask return -32001 for tasks outside the key’s scope, and ListTasks leaves those tasks out.
Next steps
- Setting up an A2A Surface: create a Surface and configure its endpoints and keys
- Connecting external agents: connect an external agent to an A2A Surface
- Surface orchestration modes: choose Delegate or Managed mode
- Scoping API keys to capabilities: limit the Capabilities that each key can run