Skip to navigation

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

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:

    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:

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.

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.

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:

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

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:

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