Agent cards & errors
Agent cards (discovery)
Every non-retired agent has an A2A 1.0 discovery document, generated from the agent's current state rather than hand-maintained — there's nothing to edit and nothing that can drift out of sync with the agent it describes:
curl https://api.forgebench.ai/v1/agents/invoice-classifier/.well-known/agent-card.json \
-H "Authorization: Bearer $ANY_TENANT_KEY"
# the tenant's whole registry (no name/id — every live agent's card)
curl https://api.forgebench.ai/v1/agents/cards \
-H "Authorization: Bearer $ANY_TENANT_KEY"curl https://api.forgebench.ai/v1/agents/706608c0-4160-452a-86ac-ded1186326ac/.well-known/agent-card.json \
-H "Authorization: Bearer $ANY_TENANT_KEY"
# the tenant's whole registry (no name/id — every live agent's card)
curl https://api.forgebench.ai/v1/agents/cards \
-H "Authorization: Bearer $ANY_TENANT_KEY"urlalways points at/v1/agents/{id}/taskson the control plane, not at wherever the agent actually runs — so a caller that discovers this card, whether that's another tenant's agent, a partner's orchestrator, or an enterprise agent registry crawling for cards, can only ever reach the agent through the gate.skillsmirror 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 againstGET /v1/billing/export/pubkeyif 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:
| Status | Cause | Body |
|---|---|---|
403 | not_permitted — returned for every reason a caller
can be refused: unknown callee, another tenant's callee, no binding,
a revoked binding, a binding pending approval, or a paused/retired
callee. All of it collapses to the same response on purpose — 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"}} |
404 | The task doesn't exist, or exists but you're not its caller or callee. | {"detail":"task not found"} |
409 | task_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"}} |
422 | A 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"} |
Since not_permitted collapses every cause into one code, the fastest way
to tell them apart while you're setting things up is checking
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

