Agents Agent-to-agent (A2A) Agent cards & errors

Agent cards & errors

Agent cards (discovery)

Every non-retired agent has an A2A 1.0 discovery document. It's generated from the agent's current state, so there's nothing to edit and nothing to drift out of sync:

import os
import httpx

headers = {"Authorization": f"Bearer {os.environ['ANY_TENANT_KEY']}"}

r = httpx.get(
  "https://api.forgebench.ai/v1/agents/invoice-classifier/.well-known/agent-card.json",
  headers=headers,
)
r.raise_for_status()
card = r.json()

# the tenant's whole registry (no name/id — every live agent's card)
r = httpx.get("https://api.forgebench.ai/v1/agents/cards", headers=headers)
r.raise_for_status()
cards = r.json()
  • url always points at /v1/agents/{id}/tasks on Forgebench, not at wherever the agent runs — so a caller that discovers this card, whether that's another workspace's agent, a partner's orchestrator, or an enterprise agent registry crawling for cards, can only ever reach the agent through the gate.
  • skills mirror the agent's live MCP tool bindings. Revoking a tool drops it from the card on the next read, the same way it drops off the allowlist.
  • The card is signed (EdDSA, canonical JSON, the same key that signs usage reports). Verify against GET /v1/billing/export/pubkey if you're consuming cards from a deployment you don't control.

Errors specific to A2A

Beyond the general refusal codes on Errors and refusals, opening, reading, or answering a task has its own shapes:

StatusCauseBody
403not_permitted — returned for every reason a caller can be refused: unknown callee, another workspace's callee, no binding, a revoked binding, a binding pending approval, or a paused/retired callee. All of it collapses to the same response, because telling a caller which of those is true would leak facts about an agent it has no relationship with. The precise reason still lands on the audit log.{"detail":{"code":"not_permitted","message":"not permitted to call that agent"}}
404The task doesn't exist, or exists but you're not its caller or callee.{"detail":"task not found"}
409task_not_working — a reply came in for a task that isn't currently working (already finished, or canceled while a callee was mid-handler).{"detail":{"code":"task_not_working","state":"canceled"}}
422A pull result posted "state": "working". Only a push endpoint's immediate response may say working; a pull reply must be completed, failed, or input_required.{"detail":"a pull result must be completed, failed, or input_required"}

not_permitted covers every cause. To tell them apart during setup, check GET /v1/agents/{agent_id}/agents on the caller, or its Calls tab in the console:

  • no entry for the callee under outbound → no binding was ever granted
  • entry present but showing pending approval → waiting on the callee's owner
  • entry present but shown revoked → the binding was cut

Next

Cookie settings

Our Cookie Policy lists every cookie and what it does.