> ## Documentation Index
> Fetch the complete documentation index at: https://agentheya.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent-to-Agent (A2A)

> Connect external AI agents to your AgentHeya agent using the Google A2A protocol.

AgentHeya agents expose a fully spec-compliant [Google A2A v0.3.0](https://a2a-protocol.org/latest/specification/) endpoint. Any A2A-compatible agent framework — LangGraph, CrewAI, Claude Managed Agents, custom orchestrators — can discover your agent, send tasks, receive streaming responses, and hand off conversations with full history.

**A2A endpoint:** `https://a2a.agentheya.com/agent/{agent_id}`

Every agent has its own endpoint. `{agent_id}` is your agent's immutable
10-character id — find it as the **A2A Server** URL on the dashboard **API Keys**
page, or in **Details → A2A / External Agents**. Because the id never changes,
the endpoint keeps resolving even after you rename the agent (the agent's link
name also works in place of the id).

<Note>
  A global entry point at `https://a2a.agentheya.com` still exists for
  backward-compatible callers: POST there with your key and it resolves to the
  agent that key is bound to. New integrations should prefer the per-agent
  `/agent/{agent_id}` URL — its card is publicly discoverable without a key.
</Note>

***

## Getting an API key

An API key (`sk_ah_...`) is the only credential needed to call your agent's A2A endpoint. There are two ways to create one:

| Path             | Who does it            | How                                                                                                 |
| ---------------- | ---------------------- | --------------------------------------------------------------------------------------------------- |
| **Manual**       | Agent owner, once-off  | Dashboard → API Keys page → Generate key                                                            |
| **Programmatic** | Agent owner, automated | Call `create_invocation_api_key` on `manage.agentheya.com/mcp` using a `sk_mgmt_...` management key |

The programmatic path is designed for agent owners who want to provision keys for partners or integrations without using the dashboard. See [Management MCP](/manage-mcp) for the full workflow.

All requests to the A2A endpoint must include:

```http theme={null}
Authorization: Bearer sk_ah_...
```

***

## Agent discovery

Before sending tasks, an external agent can read your agent's capability card.
The per-agent card is **public — no API key required** — so any A2A-aware agent
can discover your agent from its URL alone:

```http theme={null}
GET https://a2a.agentheya.com/agent/{agent_id}/.well-known/agent-card.json
```

The card describes your agent's name, description, supported input/output formats, capabilities (streaming, push notifications), and example queries, plus the JSON-RPC `url` to POST tasks to. An API key is only needed to *call* the agent, not to read its card.

<Note>
  The legacy root card at `https://a2a.agentheya.com/.well-known/agent-card.json`
  still works: without a key it returns the generic platform card; with a
  `Bearer sk_ah_...` key it returns that key's agent card. Prefer the per-agent
  URL above for keyless discovery.
</Note>

### Customising your agent card

From the **Details** page in your dashboard, expand the **A2A / External Agents** section to edit:

| Field                    | Purpose                                                                                                                                              |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **A2A Description**      | A task-focused description for other agents (e.g. "Handles billing questions and refund requests"). Falls back to your general description if blank. |
| **Example Queries**      | One per line. Help external agents decide whether to route here. Up to 10, each up to 200 characters.                                                |
| **Extra Discovery Tags** | Comma-separated tags (e.g. `billing, ecommerce`). Appended to the default `agentheya` tag.                                                           |
| **Documentation URL**    | Link to your own integration docs, if you have them.                                                                                                 |

***

## Sending a task

### Non-streaming (`message/send`)

```json theme={null}
POST https://a2a.agentheya.com/agent/{agent_id}
Authorization: Bearer sk_ah_...
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "req-1",
  "method": "message/send",
  "params": {
    "message": {
      "kind": "message",
      "role": "user",
      "parts": [
        { "kind": "text", "text": "What's the status of order #12345?" }
      ]
    }
  }
}
```

The response is a complete `Task` object with an `artifacts` array containing the agent's reply. `status.state` is usually `"completed"`; when the agent needs more from you to proceed, the task instead parks in **`"input-required"`** — `status.message` carries the agent's question, and you answer by sending a follow-up `message/send` with the same `taskId`.

**Protocol version**: this endpoint speaks A2A **0.3** (the default when no `A2A-Version` header is sent, per the spec). Requests pinning `A2A-Version: 1.x` receive a `-32009 VersionNotSupportedError` with `supportedVersions: ["0.3"]`.

### Streaming (`message/stream`)

Same request body, but use `message/stream` as the method. The response is `text/event-stream` (SSE). Each `data:` line is a JSON-RPC response with the same `id`.

Frame sequence:

1. `Task` — state = `working`, artifacts empty
2. `TaskStatusUpdateEvent` (repeated) — state = `working`, message contains text deltas
3. `TaskArtifactUpdateEvent` (per file, if any)
4. `TaskStatusUpdateEvent` — state = `completed`, `final: true`

***

## Multi-turn conversations

Use `contextId` to group related tasks into a conversation thread — a new task created with an existing `contextId` is seeded with the previous task's conversation, so the agent remembers the thread across tasks. Use `taskId` to continue an existing **non-terminal** task (i.e. one parked in `input-required`); terminal tasks reject follow-ups per the A2A spec:

```json theme={null}
{
  "method": "message/send",
  "params": {
    "message": {
      "kind": "message",
      "role": "user",
      "contextId": "ctx_abc123",
      "taskId": "task_xyz789",
      "parts": [{ "kind": "text", "text": "And what about order #67890?" }]
    }
  }
}
```

The agent sees all prior turns in the task's history, so follow-up questions have full context.

***

## Handing off a conversation

When an external agent wants to hand off a conversation mid-flight — with full history — it passes the prior turns in `message.metadata['x-handoff-history']`:

```json theme={null}
{
  "method": "message/send",
  "params": {
    "message": {
      "kind": "message",
      "role": "user",
      "parts": [{ "kind": "text", "text": "The customer needs billing help. Taking over now." }],
      "metadata": {
        "x-handoff-history": [
          { "role": "user",    "content": "Hi, my invoice looks wrong" },
          { "role": "agent",   "content": "I can see the issue. Let me transfer you to billing." }
        ]
      }
    }
  }
}
```

Two formats accepted:

* **Simple** — `{ role: "user"|"assistant"|"agent", content: string }` — easiest for non-A2A callers
* **Native A2A** — full A2A `Message` objects with `parts` arrays

Up to 100 prior turns are accepted. They're prepended into the task's history so the receiving agent has the full conversation context before it responds.

### Handing back

Two mechanisms, depending on how the conversation arrived:

* **Plain delegation** (no handoff envelope): hand-back is implicit in the task lifecycle. When the task reaches `completed`, the calling agent polls `tasks/get` or receives a push notification and continues from there. Use the same `contextId` across both agents to keep the thread logically connected.
* **Bridged handoff** (the message carried an `agentheya.handoff/v1` envelope — see [Peer patterns](/peer-patterns)): the receiving agent gets a `handoff_release` tool. Calling it stamps `agentheya/handoff-release` metadata (with `outcome`, `summary`, and `return_values` validated against the originator's `expected_return` contract) on the response message AND POSTs the originator's `resume.a2a_callback`, so both blocking and async originators learn the conversation is theirs again.

The receiving agent can also park the task in `input-required` (via its `request_caller_input` tool) when it needs something only the calling side can provide — answer on the same `taskId` to resume.

***

## Files

### Sending files to the agent

Include `FilePart` entries in `message.parts`:

```json theme={null}
{
  "kind": "file",
  "file": {
    "name": "invoice.pdf",
    "mimeType": "application/pdf",
    "bytes": "<base64-encoded content>"
  }
}
```

Or by URL:

```json theme={null}
{
  "kind": "file",
  "file": {
    "name": "invoice.pdf",
    "mimeType": "application/pdf",
    "uri": "https://storage.example.com/invoice.pdf"
  }
}
```

Files are saved to the agent's storage and made available to its file-reading tools automatically.

### Receiving files from the agent

If the agent creates or writes new files during its run, they appear as `FileArtifact` entries in the response. Files ≤ 256 KiB are returned as inline base64; larger files get a signed download URL (6-hour TTL).

***

## Push notifications

Instead of polling `tasks/get` for completion, you can register a webhook callback when sending the task:

```json theme={null}
{
  "method": "message/send",
  "params": {
    "message": { ... },
    "configuration": {
      "pushNotificationConfig": {
        "url": "https://your-agent.example.com/a2a-callback",
        "token": "your-optional-bearer-token"
      }
    }
  }
}
```

When the task completes (or fails), we POST the full `Task` object to your URL. If `token` is set, it's included as `Authorization: Bearer <token>`.

You can also register or update a push config after task creation:

```json theme={null}
{
  "method": "tasks/pushNotificationConfig/set",
  "params": {
    "id": "<taskId>",
    "pushNotificationConfig": {
      "url": "https://your-agent.example.com/a2a-callback"
    }
  }
}
```

***

## Checking task status

```json theme={null}
{
  "method": "tasks/get",
  "params": {
    "id": "<taskId>",
    "historyLength": 10
  }
}
```

`historyLength` caps how many history messages are returned (default: all, max stored: 200).

***

## Cancelling a task

```json theme={null}
{
  "method": "tasks/cancel",
  "params": { "id": "<taskId>" }
}
```

Best-effort — marks the task as `canceled`. In-flight runs complete normally before the state is updated.

***

## Error codes

| Code     | Meaning               |
| -------- | --------------------- |
| `-32700` | JSON parse error      |
| `-32600` | Invalid request       |
| `-32601` | Method not found      |
| `-32602` | Invalid params        |
| `-32603` | Internal error        |
| `-32001` | Task not found        |
| `-32002` | Task not cancelable   |
| `-32004` | Unsupported operation |

***

## A2A and the Inbox

Every A2A conversation is automatically mirrored to the agent owner's **Inbox** dashboard page. The owner can read the full thread and take over the conversation directly from there using the standard inbox/handoff UI.
