> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# 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](https://a2a-protocol.org/latest/specification/) 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:

```bash
curl https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/a2a/.well-known/agent-card.json
```

Replace the following:

* `YOUR_PRODUCT_ID`: the ID of the Product
* `YOUR_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:

```bash
curl -X POST https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/a2a \
  -H "Authorization: Bearer a2a_YOUR_SURFACE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-1",
    "method": "SendMessage",
    "params": {
      "message": {
        "role": "ROLE_USER",
        "parts": [{ "text": "What is your return policy?" }],
        "messageId": "msg_123"
      },
      "metadata": { "skill": "answer_questions" }
    }
  }'
```

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 `assistant` unless you set another **Skill name**. If a Capability already uses that name, Runtype adds `_orchestrator` to 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](/user-guide/products-surfaces/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`. For `SendMessage`, the error message starts with `Task execution failed:`. For `SendStreamingMessage`, the stream sends a `task/error` event. 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.pushNotificationConfig` but never calls the webhook. To detect completion, use `SendStreamingMessage`, `SubscribeToTask`, or poll with `GetTask`.
* `SendMessage` returns after the task finishes and ignores `params.configuration.blocking`. For a long-running task, use `SendStreamingMessage`.

## Message format

Requests and responses follow these v1.0 conventions:

* Each message part has exactly one of `text`, `data`, `url`, or `raw`: `{ "text": "Hello" }`, `{ "data": { "key": "value" } }`, `{ "url": "https://example.com/file.pdf" }`, or `{ "raw": "YWJj" }`. Don't add a `kind` field.
* A message's `role` is `ROLE_USER` or `ROLE_AGENT`.
* Task states use `TASK_STATE_*` values.
* A `SendMessage` result holds the task in its `task` field.
* Each SSE event's `result` contains either `statusUpdate` or `artifactUpdate`. 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](#how-invocation-works).

### SendMessage

A successful `SendMessage` response wraps the finished task in `result.task`:

```json
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "result": {
    "task": {
      "id": "atask_01jq8z7m1nd4e5f6g7h8j9k0p1",
      "contextId": "ctx_1767225600000_a1b2c3d",
      "status": { "state": "TASK_STATE_COMPLETED" },
      "artifacts": [
        {
          "artifactId": "3f2b8c1e-7d4a-4b9e-a5c6-2e8f1d0b9a47",
          "name": "response",
          "parts": [{ "text": "Our return policy allows returns within 30 days." }]
        }
      ],
      "metadata": {}
    }
  }
}
```

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:

```plaintext
event: task/status
data: {"jsonrpc":"2.0","id":"req-1","result":{"statusUpdate":{"taskId":"atask_01jq8z7m1nd4e5f6g7h8j9k0p1","contextId":"ctx_1767225600000_a1b2c3d","status":{"state":"TASK_STATE_WORKING"}}}}

event: task/artifact
data: {"jsonrpc":"2.0","id":"req-1","result":{"artifactUpdate":{"taskId":"atask_01jq8z7m1nd4e5f6g7h8j9k0p1","contextId":"ctx_1767225600000_a1b2c3d","artifact":{"artifactId":"3f2b8c1e-7d4a-4b9e-a5c6-2e8f1d0b9a47","name":"response","parts":[{"text":"Our return policy "}]},"append":false,"lastChunk":false}}}

event: task/artifact
data: {"jsonrpc":"2.0","id":"req-1","result":{"artifactUpdate":{"taskId":"atask_01jq8z7m1nd4e5f6g7h8j9k0p1","contextId":"ctx_1767225600000_a1b2c3d","artifact":{"artifactId":"3f2b8c1e-7d4a-4b9e-a5c6-2e8f1d0b9a47","parts":[{"text":"allows returns within 30 days."}]},"append":true,"lastChunk":true}}}

event: task/status
data: {"jsonrpc":"2.0","id":"req-1","result":{"statusUpdate":{"taskId":"atask_01jq8z7m1nd4e5f6g7h8j9k0p1","contextId":"ctx_1767225600000_a1b2c3d","status":{"state":"TASK_STATE_COMPLETED"}}}}
```

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:

```json
{
  "jsonrpc": "2.0",
  "id": "req-2",
  "method": "GetTask",
  "params": {
    "id": "atask_01jq8z7m1nd4e5f6g7h8j9k0p1",
    "historyLength": 10
  }
}
```

### ListTasks

`ListTasks` lists the Surface's tasks, most recently updated first, with `pageSize` and `pageToken` for cursor pagination:

```json
{
  "jsonrpc": "2.0",
  "id": "req-3",
  "method": "ListTasks",
  "params": {
    "pageSize": 25,
    "status": "TASK_STATE_COMPLETED",
    "includeArtifacts": false
  }
}
```

`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:

```json
{
  "jsonrpc": "2.0",
  "id": "req-4",
  "method": "CancelTask",
  "params": {
    "id": "atask_01jq8z7m1nd4e5f6g7h8j9k0p1"
  }
}
```

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:

```json
{
  "jsonrpc": "2.0",
  "id": "req-5",
  "method": "SubscribeToTask",
  "params": {
    "id": "atask_01jq8z7m1nd4e5f6g7h8j9k0p1"
  }
}
```

## Task states

A task reports one of these states:

| State                       | Description                                    |
| --------------------------- | ---------------------------------------------- |
| `TASK_STATE_SUBMITTED`      | Task received and queued.                      |
| `TASK_STATE_WORKING`        | Task execution is in progress.                 |
| `TASK_STATE_COMPLETED`      | Task execution finished successfully.          |
| `TASK_STATE_FAILED`         | Task execution failed.                         |
| `TASK_STATE_CANCELED`       | Task execution was canceled.                   |
| `TASK_STATE_INPUT_REQUIRED` | Task requires additional input.                |
| `TASK_STATE_AUTH_REQUIRED`  | Task requires authentication or authorization. |
| `TASK_STATE_REJECTED`       | Task was rejected.                             |
| `TASK_STATE_UNSPECIFIED`    | Runtype can't determine the task state.        |

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:

```json
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "error": {
    "code": -32002,
    "message": "Skill not found: answer_question",
    "data": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "SKILL_NOT_FOUND",
        "domain": "a2a-protocol.org",
        "metadata": { "detail": "{\"availableSkills\":[\"answer_questions\"]}" }
      }
    ]
  }
}
```

The Surface returns these standard JSON-RPC error codes:

| Code     | Reason             | Meaning                                                                        |
| -------- | ------------------ | ------------------------------------------------------------------------------ |
| `-32700` | `PARSE_ERROR`      | The request isn't valid JSON.                                                  |
| `-32600` | `INVALID_REQUEST`  | The request is invalid, or a daily execution limit was reached.                |
| `-32601` | `METHOD_NOT_FOUND` | The method doesn't exist.                                                      |
| `-32602` | `INVALID_PARAMS`   | The method parameters are invalid.                                             |
| `-32603` | `INTERNAL`         | The task couldn't run, for example because the Capability pauses for approval. |

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:

| Code     | Reason            | Meaning                                                             |
| -------- | ----------------- | ------------------------------------------------------------------- |
| `-32001` | `TASK_NOT_FOUND`  | The task doesn't exist, or the A2A Surface key can't see it.        |
| `-32002` | `SKILL_NOT_FOUND` | The skill doesn't exist, or the A2A Surface key can't run it.       |
| `-32003` | `UNAUTHORIZED`    | The A2A Surface key is missing or invalid, or the skill can't run.  |
| `-32004` | `RATE_LIMITED`    | The A2A Surface key exceeded its rate limit.                        |
| `-32005` | `TASK_CANCELED`   | The task already finished, so it can't be canceled.                 |
| `-32006` | `CONTEXT_INVALID` | Reserved for an invalid context. Runtype doesn't currently send it. |

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](/user-guide/products-surfaces/setting-up-an-a2a-surface): create a Surface and configure its endpoints and keys
* [Connecting external agents](/user-guide/products-surfaces/connecting-external-agents): connect an external agent to an A2A Surface
* [Surface orchestration modes](/user-guide/products-surfaces/surface-orchestration-modes): choose Delegate or Managed mode
* [Scoping API keys to capabilities](/user-guide/products-surfaces/scoping-api-keys-to-capabilities): limit the Capabilities that each key can run