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

# A2A edge

> Expose installed Checkfu agents and consume signed external A2A agents through existing governance.

Checkfu can expose an installed agent as an
[Agent2Agent Protocol](https://a2a-protocol.org/latest/specification/) server.
The protocol boundary is a projection of Checkfu's existing authority: an
Agent Card describes one live installation and SurfaceScope, an A2A task is
one Session, and task state and artifacts come from that Session's event log.
A2A never grants access, settles work outside the log, or connects two Checkfu
agents inside one Workspace.

<Info>
  Checkfu currently serves A2A v1.0 over JSON-RPC 2.0 and SSE. Send
  `A2A-Version: 1.0` on every RPC request. The protocol treats an absent header
  as v0.3, which Checkfu deliberately rejects with `VersionNotSupportedError`.
</Info>

## Find the endpoint

Each active AgentInstallation and SurfaceScope pair has two URLs:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
GET  /a2a/v1/workspaces/{workspace_id}/agent-installations/{installation_id}/surface-scopes/{surface_scope_id}/agent-card.json
POST /a2a/v1/workspaces/{workspace_id}/agent-installations/{installation_id}/surface-scopes/{surface_scope_id}
```

The Agent Card is public and cacheable for 60 seconds. It carries an `ETag`,
the exact JSON-RPC endpoint, supported text modes, streaming capability, and a
projection of the installed release's active skills. An archived agent or an
inactive or mismatched installation/surface placement returns `404` instead of
publishing a stale card.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl \
  "https://api.checkfu.com/a2a/v1/workspaces/wrkspc_0123456789abcdef0123456789abcdef/agent-installations/aini_your_installation/surface-scopes/surf_your_surface/agent-card.json"
```

## Issue an A2A Principal credential

An A2A caller authenticates as one existing person Principal. Use an identity-admin
API key to issue an audience-bound bearer; the `secret` appears only in this
response:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl https://api.checkfu.com/v1/principals/prin_fedcba9876543210fedcba9876543210/access-credentials \
  -H "Authorization: Bearer $CHECKFU_API_KEY" \
  -H "Checkfu-Version: 2026-08-27" \
  -H "Idempotency-Key: a2a-partner-agent" \
  -H "Content-Type: application/json" \
  --data '{"audience":"a2a"}'
```

The same PrincipalAccessCredential lifecycle endpoints used by API MCP callers list,
rotate, and revoke this bearer. Its audience is immutable: an `a2a` bearer is
rejected by `/v1/mcp`, and an `api_mcp` bearer is rejected by the A2A endpoint.
Every request also rechecks the Principal, credential, installation placement,
Session admission, PermissionAssignments, and ActionPolicy. Give the Principal `invoke`, `steer`, and
`observe` on the installed agent and SurfaceScope required for the operations it
will use.

Task and occurrence identity is scoped to that Principal, AgentInstallation,
and SurfaceScope. A provider-native conversation or another Principal using the
same opaque context or message ID neither replays nor becomes visible as an A2A
task.

## Send and observe a task

Call the card's JSON-RPC URL with the issued bearer:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl \
  "https://api.checkfu.com/a2a/v1/workspaces/wrkspc_0123456789abcdef0123456789abcdef/agent-installations/aini_your_installation/surface-scopes/surf_your_surface" \
  -H "Authorization: Bearer $CHECKFU_A2A_CREDENTIAL" \
  -H "A2A-Version: 1.0" \
  -H "Content-Type: application/json" \
  --data '{
    "jsonrpc":"2.0",
    "id":"request-1",
    "method":"SendMessage",
    "params":{
      "message":{
        "role":"ROLE_USER",
        "messageId":"partner-message-1",
        "contextId":"partner-conversation-1",
        "parts":[{"text":"Review the repository and report the result."}]
      },
      "configuration":{"returnImmediately":true,"historyLength":20}
    }
  }'
```

`SendMessage` blocks until the task reaches a terminal or interrupted state by
default, as A2A v1 requires. Set `returnImmediately: true` to receive the
submitted or working Task and poll it with `GetTask`; use
`SendStreamingMessage` or `SubscribeToTask` for ordered SSE updates. Reusing the
same `messageId` is idempotent and returns the same Session-backed task.

The endpoint supports `SendMessage`, `SendStreamingMessage`, `GetTask`,
`ListTasks`, `CancelTask`, and `SubscribeToTask`. Only `text/plain` input and
output are admitted in this version. Push notifications, protocol extensions,
non-text message parts, and ZDR Sessions are rejected rather than accepted with
weaker custody or incomplete behavior.

This interface is routed by its installation-and-surface URL, so omit the A2A
`tenant` request field (the Agent Card does not advertise one). Message
extension URIs and `referenceTaskIds` are also rejected in this version rather
than silently ignored; a terminal task cannot accept another message or a new
subscription.

<Warning>
  A2A is only an external interoperability edge. To let one Checkfu agent call
  another in the same Workspace, use multiagent threads or a shared conversation
  surface; routing that traffic through A2A would bypass the canonical
  workspace collaboration model.
</Warning>

## Consume an external A2A agent

An external A2A agent is a governed ToolSource, not a coordinator-roster member. An operator
first reviews a ProviderPackage whose egress ceiling admits exactly the public
Agent Card GET and JSON-RPC POST, then registers the card URL and its reviewed
ES256 public JWK:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "name": "review-peer",
  "provider": "review-peer",
  "source": {
    "kind": "a2a",
    "card_url": "https://peer.example.com/.well-known/agent-card.json",
    "verification_keys": [{
      "kty": "EC",
      "crv": "P-256",
      "x": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
      "y": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
      "kid": "reviewed-2026-08",
      "alg": "ES256"
    }]
  }
}
```

Sync verifies a detached ES256 signature over canonical card JSON and chooses
one A2A v1 JSON-RPC interface. Checkfu rejects unsigned cards, unsupported
required extensions, non-text input or output, and cards without exactly one
supported security alternative. A supported alternative is one Bearer scheme
with no scopes, one OAuth2 authorization-code scheme whose exact,
duplicate-free required scopes the card declares, or one mTLS scheme with no
scopes. Ambiguous alternatives and other OAuth2 flows fail closed. Each card skill becomes an always-ask Tool; the
Tool stores the card, endpoint, and key fingerprints plus only the selected
scheme name, flow, and scope set—never the card body, OAuth endpoints, or a
credential.

Rotate trust with `POST /v1/tool-sources/{id}/source-revisions` and a required
`Idempotency-Key`, not an ordinary sync. For a source already backing
Connections, first publish a higher host-configured ProviderPackage whose A2A
source contains both the old and new keys and declares every live predecessor
version compatible. Then submit the candidate source, exact package identity,
and the complete sorted list of dependent Connection IDs with their current
versions. Checkfu requires a monotonically higher compatible package for each
dependent Connection, verifies discovery before it publishes the source, and
durably resumes the version-fenced Connection migration after interruption. An
unused source still has to match the exact current host package, but has no
earlier Connection package binding to migrate. Once the peer signs
with the new key, repeat the operation with another higher host-configured package
that removes the old key. Restoring an old key is the same forward-only process
with a still-higher host-configured package. Rotation and rollback perform the bounded,
credential-free card discovery GET, but never read a credential or call an
executable provider endpoint. Audit records distinguish a source
revision (or failed revision) from an ordinary sync and record each dependent
Connection package migration separately.

Attach the discovered Tool and its Connection to an Agent as usual. At runtime,
the CapabilityGateway rechecks PermissionAssignments, ActionPolicy, ActionApproval, the frozen ProviderPackage,
the live Connection revision and scopes, the signed binding, and exact egress
before custody injects the peer credential. An OAuth2 selection requires a
custody-managed OAuth ProviderPackage and scopes within both its reviewed
ceiling and the Connection. Checkfu never follows a card-declared authorization,
token, refresh, or metadata URL: the already-qualified Connection custody
adapter obtains, refreshes, revokes, and injects the token only at its outbound
transport edge. The token never appears on the Tool, task custody, event log,
sandbox boundary, or result receipt.

An mTLS selection requires a custody-managed mTLS ProviderPackage and a
deployment-reviewed Worker certificate binding. The integration configuration
names only the binding, a generation marker, certificate expiry, and the exact
allowed peer hostnames; the certificate and private key are uploaded through
the Worker platform and are not JSON configuration. Hosted setup activates the
Connection without a credential form. Certificate rotation keeps the
Connection ID, but the operator must advance the generation/expiry metadata and
use the ordinary reconnect link so the sealed custody record advances before
provider traffic resumes. Checkfu checks the configured hostname set and the
Worker fetcher performs ordinary server-certificate hostname verification.

If the signed Agent Card declares streaming and the frozen Connection custody
adapter supports it, Checkfu sends one `SendStreamingMessage`. It attaches the
first active task before accepting ordered artifact or status updates, requires
the exact JSON-RPC, task, and context identity throughout, and closes the SSE
body at terminal state. Resume uses `SubscribeToTask`, never another send. A
disconnect after durable task identity falls back to the same fenced `GetTask`
observer; a disconnect before identity is indeterminate. If either the card or
custody adapter lacks streaming support, Checkfu sends one `SendMessage` and
uses `GetTask` as the baseline observer. In every mode, the final task becomes
one CapabilityGateway ToolInvocation result and one `exec.tool_invocation` event.

Replay the exact pending call after an observation failure: it retains the
original ActionApproval and resumes only the matching durable child. A lost or
invalid send response before task attachment is never replayed.

Polling starts immediately, then uses deterministic task-specific exponential
backoff with bounded jitter. A syntactically valid HTTP `Retry-After` on a
temporary `429` or `503` response may extend the local delay; other statuses,
malformed values, and non-HTTP date formats are ignored. Peer guidance and
local backoff are both capped, the immutable hard deadline always wins, and a
failure releases the fenced observer while retaining the exact next due time
for resume. A host-lifetime ceiling also bounds concurrent polls for one
reviewed Agent Card coordinate without holding a task lease while queued.
Operational counters and poll spans contain only closed outcome, schedule,
attempt-bucket, and fresh/resume labels—never peer coordinates, task IDs,
request IDs, provider content, or credentials.

SSE parsing is strict and bounded: the response must negotiate A2A v1 and
`text/event-stream`, the total stream and each event have fixed byte ceilings,
artifact chunks must be ordered, and task/context identity cannot change. A
caller cancellation or hard deadline interrupts provider reading, releases the
owned response body and fenced custody lease, and leaves no provider content in
durable task state.

Every attached task has an immutable hard deadline. A bounded scheduled sweep
finds due structural custody without calling the peer, expires the child, and
asks the CapabilityGateway to settle that exact ToolInvocation and Session execution
as indeterminate with a terminal audit record. The child records reconciliation
completion only after those authorities accept it, so a process loss resumes
the same settlement on a later bounded sweep without another provider request
or a second durable result. This expiry is Checkfu's local indeterminate
settlement, not evidence that the remote peer canceled or stopped its task.

Outbound consume supports A2A v1.0 JSON-RPC with `text/plain` messages;
Bearer, reviewed OAuth2 authorization-code, or reviewed mTLS card
authentication; bounded `GetTask` polling; and signed-card-gated SSE through
`SendStreamingMessage` and `SubscribeToTask`. When a signed card instead
declares push notifications, Checkfu registers a task-scoped HTTPS callback
with a generated Bearer value, stores only its digest, and accepts bounded
`application/a2a+json` notifications as wake signals. The callback body never
settles the call or enters durable custody: Checkfu re-reads the exact task with
authenticated `GetTask`, and polling remains the missed-notification fallback.

API-key, OIDC, non-authorization-code OAuth2, and required protocol extensions
remain rejected rather than silently degraded.
