> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up A2A federation between tenants

> Register a peer agent endpoint, send federated tasks across tenant boundaries, accept or reject inbound federated calls, audit the peer registry, and walk a full supplier federation example.

# Set up A2A federation between tenants

The [A2A federation model](/concepts/a2a-federation-model) explains what
federation is; this guide is the working order of operations for standing up
a federation edge between two tenants — the peer registration, the handshake
that authenticates cross-tenant task traffic, the accept/reject policy on
inbound calls, and the audit trail you revoke through when the edge ends.

## What a federated request looks like

Orbit speaks the [A2A protocol v1.0.0](https://a2a-protocol.org/), so a
federated edge is not tenant-to-tenant only — your agent can task any
A2A-conformant peer, and an A2A client you let in can task your agent. When
both ends are Orbit tenants, both directions share one setup shape.

A federated request is one prompt handed to a third-party agent: the
requesting tenant persists an outbound envelope, signs it, forwards it to a
registered peer URL, and polls the returned task id until the peer answers.
Context beyond the prompt does not travel — the peer receives the envelope
message only.

## Register a peer endpoint

A peer is registered because someone hands you their AgentCard URL. On Orbit
the agent owner copies it from **Agents → {agent} → A2A → Copy share URL**;
that works only after the agent's discovery mode is `public` (or `tenant`,
if you share over an authenticated channel). See
[the discovery-mode table](/agents/a2a-federation#discovery-modes).

Register the URL so your agent knows the peer exists:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc/a2a/peers \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "peer_url": "https://partner.example.com/api/v1/agents/order-status-agent/.well-known/agent.json"
  }'
```

On add, the platform fetches the card, stores it on the peer row, and
surfaces the peer's skill catalog in the dashboard — that fetch is the
capability announcement. If the card is an Orbit card, it carries a
`signatures[]` block (the A2A Signed Agent Card extension) so the peer's
identity is verified without a shared secret. The URL must be a public FQDN
over HTTPS — localhost, private IP tiers, and link-local hosts are refused
at registration.

Two Orbit tenants federate by each registering the other's AgentCard URL —
one call on each side, and the edge is symmetric.

## Authenticate the handshake

Every task endpoint — inbound and outbound — requires the HMAC-signed
header. AgentCard signatures authenticate the card, not the call.

```
X-A2A-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>
```

Sign `${ts}.${raw_body}` exactly as sent, within a ±60-second replay
window. Share the per-org A2A secret (`DEVOTEL_A2A_HMAC_SECRET`; it falls
back to `DEVOTEL_API_SECRET_KEY` on fresh deployments) with the peer over a
side channel — never in the request itself. If either side rotates the
key, re-share before traffic resumes, or every task retries the signature
gate (see
[Troubleshooting: A2A federation](/troubleshooting/a2a-federation)).

## Send a federated task (outbound)

With a peer in the registry, delegate a task:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc/a2a/tasks/outbound \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "peer_url": "https://partner.example.com/api/v1/agents/order-status-agent/.well-known/agent.json",
    "skill": "order_status",
    "message": {
      "role": "user",
      "parts": [{ "kind": "text", "text": "Status of order ORD-89721 for contact cnt_42" }]
    }
  }'
```

The POST persists an outbound row, signs, and forwards. Poll
`GET …/a2a/tasks/{taskId}` (sign over `${ts}.`, empty body) until the peer
answers.

**What forwards:** the skill name and the text parts you include — nothing
more. Any tenant, conversation, or contact identifiers you want the peer to
act on must be explicit payload fields on the message itself.

**What does not forward:** your conversation history, contact profile
fields, memory entries, or metadata of any kind. The receiving agent gets
the prompt and runs it against whatever its own tenant owns.

## Accept or reject inbound calls

Inbound hits your agent's task endpoints with a valid signature, only when
a signing key is registered — the one signed-envelope boundary is fail-open
until the first key is provisioned; after that, every unsigned or
mismatched envelope returns a fixed-operator-safe `401`. The policy lever
you own is per-agent: to expose an agent to peers at all, its discovery
mode must be `public` (or `tenant` for share-URL-on-a-side-channel). An
agent with discovery `disabled` accepts tasks only from callers who already
know its URL — even so, the endpoint never announces "agent exists" to an
unauthenticated probe: the public card route answers a flat `404`.

To reject a peer by policy rather than by signature: nothing rejects
inbound on a per-peer basis beyond the shared-secret gate. Revoke the peer
from the registry (next section) and rotate the HMAC key — that severs
their ability to sign. The fail-open boundary is documented on the
[concept page](/concepts/a2a-federation-model); do not ask a peer to send
unsigned once a key exists.

## Audit the registry and revoke a peer

Peer rows and every inbound/outbound task sit on per-tenant tables
(`agent_a2a_peers`, `agent_a2a_tasks`), so a federation edge is auditable
end-to-end from your side alone.

* **List every edge:** `GET /api/v1/agents/{agentId}/a2a/peers` — the full
  per-agent peer registry.
* **Revoke an edge:** `DELETE /api/v1/agents/{agentId}/a2a/peers/{peerId}` —
  removes the row; the peer's URL stops resolving and outbound tasks to it
  error at the peer step.
* **Trace the round-trip:** `GET /api/v1/agents/{agentId}/a2a/tasks` lists
  both directions with status and latency — the audit trail that survives a
  revocation.

Audit log events `agent.a2a.peer_added` / `agent.a2a.peer_removed` fire on
each registry mutation, so a peer add or revoke is attributable to an
operator.

## Worked example: returns-agent ⇄ order-status-agent

A customer-success org runs a `returns-agent`; its supplier runs an
`order-status-agent` on their own Orbit tenant.

1. The supplier flips `order-status-agent` discovery to `public` and hands
   over its share URL.
2. Customer success registers it:
   `POST /agents/returns-agent/a2a/peers` with the supplier URL.
   The returns-agent now sees the supplier's `order_status` skill.
3. A return landed in the success inbox — the operator delegates:

```json theme={null}
{
  "peer_url": "https://supplier.example.com/api/v1/agents/order-status-agent/.well-known/agent.json",
  "skill": "order_status",
  "message": {
    "role": "user",
    "parts": [{ "kind": "text", "text": "Order ORD-89721 current status?" }]
  }
}
```

4. The supplier's agent answers on its own data; the success tenant polls
   the returned task id and gets a `completed` envelope with the status
   text. Neither side saw the other's contact records.

When the contract ends, `DELETE` the peer row and rotate the shared HMAC
key.

## Errors

The four failure classes — discovery refused, signature rejection, peer
errors, registration refused on the URL — map one-to-one to gates you hit
above. When one fires, work through
[Troubleshooting: A2A federation](/troubleshooting/a2a-federation) before
re-tasking.

## See also

* [A2A federation →](/agents/a2a-federation) — endpoint-by-endpoint
  reference: discovery modes, the two transports, peer registry.
* [A2A federation model →](/concepts/a2a-federation-model) — the concept
  page this guide assumes.
* [Troubleshooting: A2A federation →](/troubleshooting/a2a-federation) —
  the failure taxonomy this guide's gates can land in.
