> 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.

# Connecting external agents

Connect an external agent to your Runtype A2A Surface so it can discover and
invoke your Capabilities through the Agent-to-Agent (A2A) protocol. The Agent
Card lists each exposed Capability as an A2A skill, and the external agent
calls a skill by its name.

## Before you begin

Before you connect an external agent, prepare the following:

* An active A2A Surface in Runtype. To create one, see [Setting up an A2A Surface](/user-guide/products-surfaces/setting-up-an-a2a-surface).
* The Agent Card URL from the Surface's **Endpoints** tab.
* An account on the external agent platform with permission to add agent connections.

A Capability that can pause can't run over A2A. This includes a Flow with a
wait or an approval step and an Agent that requires tool approval. Expose
Capabilities that run to completion without pausing.

## Create an A2A key

The Agent Card URL is public, but every JSON-RPC request needs an A2A key. A2A
keys start with `a2a_`.

To create a key, follow these steps:

1. On your A2A Surface, open the **Keys** tab.
2. Click **Generate Key**.
3. Optional: In **Name (optional)**, enter a name such as `Partner Agent`.
4. In **Key Type**, select one of these options:
   * **Test: revealable, daily-capped (50/day)**: Use it to try the connection. The key stops working for the rest of the UTC day after 50 executions.
   * **Production: hash-only, uncapped**: Use it for a live partner integration.
5. Optional: In **Rate Limit (requests/minute)**, enter a per-minute limit. Leave it empty for no limit.
6. Click **Generate Key**.
7. Copy the key and share it with the external agent operator. You can't
   reveal a Production key again later, so copy it before you close the dialog.

To track usage and revoke access for each caller independently, create one key
per external agent or organization.

The external agent sends the key in an `Authorization: Bearer` header or an
`X-API-Key` header. A request over the key's limit returns error `-32004` with a
`Retry-After` header that gives the wait in seconds. For more information, see
[Request limits](/user-guide/products-surfaces/setting-up-an-a2a-surface#request-limits).

## Choose a connection method

Choose the method that matches the external agent platform.

### Connect through an A2A-compatible platform

For a platform with native A2A support, follow these steps:

1. In the external platform, create an agent connection.

2. Select **A2A** or **Agent-to-Agent** as the connection type.

3. Enter the Agent Card URL:

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

   Replace `YOUR_PRODUCT_ID` with your Product ID and `YOUR_SURFACE_ID` with your Surface ID.

4. Configure the connection to use a Bearer token.

5. Enter your A2A key as the token.

6. Save the connection and run its test.

The platform reads your skills from the Agent Card after it connects. If the
platform requires streaming, turn on **Enable Streaming** in the Surface's
**Agent Card** tab. The Agent Card advertises streaming only when this setting
is on.

### Build a custom integration

For a platform without native A2A support, fetch the Agent Card and send a
JSON-RPC request with the following Python code:

```python
import requests

agent_card_url = (
  "https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/"
  "surfaces/YOUR_SURFACE_ID/a2a/.well-known/agent-card.json"
)
response = requests.get(agent_card_url)
response.raise_for_status()
agent_card = response.json()

print(f"Agent: {agent_card['name']}")
print(f"Skills: {[skill['name'] for skill in agent_card.get('skills', [])]}")

a2a_url = (
  "https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/"
  "surfaces/YOUR_SURFACE_ID/a2a"
)
payload = {
  "jsonrpc": "2.0",
  "method": "SendMessage",
  "id": "request-1",
  "params": {
    "message": {
      "role": "ROLE_USER",
      "parts": [{"text": "What are your business hours?"}],
      "messageId": "message-1"
    },
    "metadata": {"skill": "YOUR_SKILL_NAME"}
  }
}
headers = {
  "Authorization": "Bearer a2a_YOUR_API_KEY",
  "Content-Type": "application/json"
}
result = requests.post(a2a_url, json=payload, headers=headers)
result.raise_for_status()
body = result.json()
if "error" in body:
  raise RuntimeError(f"A2A error {body['error']['code']}: {body['error']['message']}")

task = body["result"]["task"]
print(task["status"]["state"])
```

Replace the placeholders in the sample as follows:

* `YOUR_PRODUCT_ID`: your Product ID.
* `YOUR_SURFACE_ID`: your Surface ID.
* `YOUR_SKILL_NAME`: a skill name from the Agent Card.
* `a2a_YOUR_API_KEY`: your A2A key.

The response wraps the task in `result.task`. Runtype returns A2A errors as
JSON-RPC `error` objects with HTTP status `200`, so check the response body for
an `error` member as well as the HTTP status. If the skill name doesn't match an
exposed skill, the request returns error `-32002`, and the error data lists the
available skill names.

The Surface's orchestration mode decides where a request without
`metadata.skill` goes:

* **Managed mode**: The orchestrator skill routes the request to a matching
  Capability. To use it, remove the `metadata` object.
* **Delegate mode**: The request goes to the first Capability that the key can
  run. Keep `metadata.skill` so that each request reaches the skill you intend.

For more information, see [Surface orchestration modes](/user-guide/products-surfaces/surface-orchestration-modes).

To group related tasks, pass the `contextId` from the returned task in
`params.message`. A Flow or Agent Capability doesn't receive earlier turns from
the context, so include any context that it needs in the message text. For
streaming, task polling, cancellation, and the full request and response
format, see [How A2A works](/user-guide/products-surfaces/how-a2a-works).

## Test the connection

To verify that the external agent can invoke your Capabilities, follow these steps:

1. In the Product editor, open **Visual view**. On the Surface node, open the
   actions menu and select **Preview**. Send a message to a skill and confirm
   that it responds. This test uses your dashboard session, so it confirms the
   Surface and its Capabilities work before you involve the external platform.
2. From the external agent platform, fetch the Agent Card and confirm that it
   lists your skills.
3. From the external agent platform, invoke a skill with the `SendMessage`
   method.
4. In the Runtype dashboard, open **Logs** and confirm that the invocation
   appears.
5. Confirm that the external agent receives the response.

Clients can cache the Agent Card for up to one hour. If a skill that you added
or renamed is missing, fetch the card again without the cache.

## Troubleshoot the connection

Use the following table to match a symptom to its cause:

| Symptom                                                                                     | Cause and fix                                                                                                                                                                                                                                               |
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The Agent Card URL returns HTTP `404` with `A2A surface not found or not active`.           | The Surface isn't **Active**, or the Surface ID is wrong. Set the Surface to **Active** and copy the URL from the **Endpoints** tab.                                                                                                                        |
| The Agent Card URL returns HTTP `404` with `Agent not found`.                               | The Product ID is wrong. Copy the URL from the **Endpoints** tab.                                                                                                                                                                                           |
| A skill is missing from the Agent Card.                                                     | The Capability isn't connected to the Surface, or the Capability or its Surface connection is disabled. Connect and enable it, then fetch the card again.                                                                                                   |
| Error `-32003` with `Invalid API key format`.                                               | The key doesn't start with `a2a_`. Use a key from this Surface's **Keys** tab.                                                                                                                                                                              |
| Error `-32003` with `Invalid API key`.                                                      | The key is wrong, revoked, or belongs to a different Surface. Generate a key on this Surface.                                                                                                                                                               |
| Error `-32003` with `API key has expired`.                                                  | The key has expired. Generate a key.                                                                                                                                                                                                                        |
| Error `-32003` with `Skill "NAME" cannot execute`.                                          | The saved Agent behind the skill requires a Product Tenant or End User identity, which A2A calls can't provide. See [Agents that require end-user identity](/user-guide/products-surfaces/setting-up-an-a2a-surface#agents-that-require-end-user-identity). |
| Error `-32002` with `Skill not found`.                                                      | `metadata.skill` doesn't match a skill that the key can reach. Use a name from the error data or the Agent Card.                                                                                                                                            |
| Error `-32004` with `Rate limit exceeded`.                                                  | The key reached its request limit. Wait for the `Retry-After` time, then retry.                                                                                                                                                                             |
| Error `-32600` with `Test surface key daily limit reached`.                                 | A Test key reached 50 executions for the day. Use a Production key for the integration.                                                                                                                                                                     |
| Error `-32603` with `Flow can pause, but this protocol does not support flow continuation`. | The Capability can pause for a wait or an approval. Expose a Capability that runs to completion.                                                                                                                                                            |

For the full list of error codes, see [How A2A works](/user-guide/products-surfaces/how-a2a-works#errors).

## Use a fallback agent

Use a fallback when a local agent can't handle a request. This example sends
responses with confidence less than `0.5` to a Runtype skill:

```javascript
async function handleCustomerQuery(query, localAgent, a2aUrl, a2aApiKey, skillName) {
  const localResponse = await localAgent(query)

  if (localResponse.confidence < 0.5) {
    const response = await fetch(a2aUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${a2aApiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        method: 'SendMessage',
        id: 'request-1',
        params: {
          message: {
            role: 'ROLE_USER',
            parts: [{ text: query }],
            messageId: `message-${Date.now()}`,
          },
          metadata: { skill: skillName },
        },
      }),
    })

    if (!response.ok) {
      throw new Error(`A2A request failed with status ${response.status}`)
    }

    const body = await response.json()
    if (body.error) {
      throw new Error(`A2A error ${body.error.code}: ${body.error.message}`)
    }

    return body.result.task
  }

  return localResponse
}
```

Pass these arguments to `handleCustomerQuery`:

* `query`: the user's message.
* `localAgent`: an asynchronous local agent function that returns a `confidence` value.
* `a2aUrl`: the A2A endpoint URL from the **Endpoints** tab.
* `a2aApiKey`: your A2A key.
* `skillName`: a skill name from the Agent Card.

The Capability receives only the text in `query`. To carry context from earlier
escalations, include it in `query`.

## Next steps

* [How A2A works](/user-guide/products-surfaces/how-a2a-works): A2A protocol
  format and task lifecycle
* [Setting up an A2A Surface](/user-guide/products-surfaces/setting-up-an-a2a-surface):
  A2A Surface creation and authentication
* [Surface orchestration modes](/user-guide/products-surfaces/surface-orchestration-modes):
  Managed and Delegate routing
* [What are Surfaces?](/user-guide/products-surfaces/what-are-surfaces): Surface types and use cases