Backend integration
Call a platform agent from Foundry, Copilot Studio or watsonx
Expose an agent over A2A v1.0 / v0.3: the well-known Agent Card, bearer authentication with an API key, message:send and JSON-RPC examples, task states and the input-required approval pause.
An agent whose published manifest sets a2a.expose: true is served as an A2A server — a public Agent Card, message:send, task lookup and cancel, in both the A2A v1.0 and v0.3 wire generations — at https://<api-host>/v1/a2a/<agentId>. Microsoft Foundry, Copilot Studio, IBM watsonx Orchestrate and any other A2A client register that URL and call it with a platform API key. Every call is a real, metered, audited run; an approval pause surfaces as the A2A task state input-required. The other direction — Agentic Workforce ME calling their agents — is Connect an external agent.
1. Expose the agent
In the manifest, turn on a2a.expose, list the caller tenant ids in allowed_callers (silence is deny; allow_any_caller is the explicit opt-in for any authenticated caller) and optionally name the card and its skills. Publish the version.
{
"a2a": {
"expose": true,
"allowed_callers": ["<caller tenant id>"],
"allow_any_caller": false,
"card": {
"name": "Contract Risk Analyst",
"description": "Reviews a contract and lists risk clauses.",
"skills": ["analyze", "summarize"]
}
}
}Then create an API key for the caller tenant (Admin → Settings → API keys, or POST /v1/api-keys — see API keys & authentication). Keys carry the tenant, so no X-Tenant-Id header is needed. A partner organisation therefore needs an account and a key on this deployment: there is no OAuth authorization server for inbound A2A yet (roadmap, Phase 3).
2. The well-known card
curl -sS "https://<api-host>/v1/a2a/<agentId>/.well-known/agent-card.json"
# add -H "A2A-Version: 0.3" for the v0.3 card shape| Request header | Card served |
|---|---|
none | v1.0 card — supportedInterfaces = HTTP+JSON 1.0, JSON-RPC 1.0 and JSON-RPC 0.3, all at one base URL; securitySchemes.bearer; capabilities {streaming:false, pushNotifications:false}; signatures: []. |
A2A-Version: 0.3 | v0.3 card — url, protocolVersion: '0.3', preferredTransport: 'JSONRPC', additionalInterfaces (HTTP+JSON), security. |
3. Authenticate
Every callable route needs Authorization: Bearer hive_…. The authenticated tenant must be in allowed_callers:
| Failure | Response |
|---|---|
| no / bad credential | 401 UNAUTHORIZED |
| tenant not allowed by the manifest | 403 A2A_CALLER_FORBIDDEN |
| agent not exposed / unknown | 404 NOT_FOUND |
Task reads are caller-scoped: only the tenant that created a task can fetch or cancel it.
4. Send a message
curl -sS -X POST "https://<api-host>/v1/a2a/<agentId>/message:send" \
-H "Authorization: Bearer hive_…" \
-H "A2A-Version: 1.0" \
-H "Content-Type: application/json" \
-d '{
"message": {
"role": "ROLE_USER",
"messageId": "m-001",
"contextId": "conv-42",
"parts": [{ "text": "Review clause 12 for liability risk." }]
},
"configuration": { "returnImmediately": false }
}'curl -sS -X POST "https://<api-host>/v1/a2a/<agentId>" \
-H "Authorization: Bearer hive_…" -H "A2A-Version: 1.0" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{
"message":{"role":"ROLE_USER","messageId":"m-002",
"parts":[{"text":"Summarise clause 12."}]}}}'curl -sS -X POST "https://<api-host>/v1/a2a/<agentId>" \
-H "Authorization: Bearer hive_…" -H "A2A-Version: 0.3" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{
"message":{"kind":"message","role":"user","messageId":"m-003",
"parts":[{"kind":"text","text":"Summarise clause 12."}]},
"configuration":{"blocking":true}}}'- Parts:
{text}and{data}(JSON-stringified into the prompt); file parts are ignored. No text/data part →400 A2A_CONTENT_TYPE_NOT_SUPPORTED; over 16 000 characters →413 A2A_MESSAGE_TOO_LARGE. returnImmediately: false(1.0) /blocking: true(0.3) — the default — runs the agent inline and returns the final task, or aninput-requiredtask when an approval paused it. Non-blocking returnssubmitted; pollGetTask/tasks/get.contextIdis echoed back (the run's thread id when it has one);messageId,contextIdandmetadataare stored on the run input and never shown to the model.- Tasks are one-shot: a message carrying
taskIdis refused (501/-32004). One JSON-RPC endpoint dispatches by method name:SendMessage,GetTask,CancelTask(1.0) andmessage/send,tasks/get,tasks/cancel(0.3). Streaming (message:stream, SSE) and push-notification configs ship in the same release as this page (before that they answer-32004/-32003); list methods answer-32004.
# poll a task (always send the version header)
curl -sS "https://<api-host>/v1/a2a/<agentId>/tasks/<taskId>" \
-H "Authorization: Bearer hive_…" -H "A2A-Version: 1.0"
# cancel
curl -sS -X POST "https://<api-host>/v1/a2a/<agentId>/tasks/<taskId>:cancel" \
-H "Authorization: Bearer hive_…" -H "A2A-Version: 1.0"5. Task states
| Run status | A2A 0.3 | A2A 1.0 | Meaning |
|---|---|---|---|
queued | submitted | TASK_STATE_SUBMITTED | non-blocking send answered here; poll |
running | working | TASK_STATE_WORKING | |
waiting_approval | input-required | TASK_STATE_INPUT_REQUIRED | an approval is pending — status.message names the tool and approval id |
succeeded | completed | TASK_STATE_COMPLETED | artifacts[0] = { name: "output", parts: [{ text }] } |
failed | failed | TASK_STATE_FAILED | status.message = <code>: <message> |
cancelled | canceled | TASK_STATE_CANCELED | CancelTask / tasks/cancel, or a platform-side cancel |
expired | failed | TASK_STATE_FAILED | the approval SLA elapsed |
input-required is an approval
When the agent's HITL policy pauses the run, the task parks in input-required until a person decides it in Operate → Approvals or via POST /v1/approvals/:id/decision (Human-in-the-loop approvals). The A2A caller cannot answer it — by design, a checkpointed interrupt resumes only through the approvals decision path. Poll until the state is completed, failed or canceled; CancelTask on a paused task voids the pending approvals.
6. Register in the vendors' orchestrators
Every registration needs the same two things: the A2A base URL https://<api-host>/v1/a2a/<agentId> (append /.well-known/agent-card.json for the card) and an API key sent as a bearer. The vendor pages linked below are authoritative for their screens.
Microsoft Foundry Agent Service — A2A tool
Add an Agent-to-agent (A2A) tool to the Foundry agent, pointing at the card or endpoint URL, with the API key as the tool's bearer secret. Foundry speaks A2A 1.0 (JSON-RPC) and 0.3; both are served at that base. Expect text in, text out, no streaming.
Microsoft Copilot Studio — agent over A2A
Connect to an agent over A2A with the endpoint URL and API-key / bearer authentication (header Authorization, value Bearer hive_…). Copilot Studio's auto-discovery looks for /.well-known/agent.json, which is not served — fill the card / endpoint fields explicitly.
IBM watsonx Orchestrate — discover and import
Orchestrate imports A2A 0.3.0 cards and attaches the agent as a collaborator of a native agent (ADK: connect external agents). Create a connection whose secret is the API key, then:
orchestrate agents discover \
-u "https://<api-host>/v1/a2a/<agentId>/.well-known/agent-card.json" \
--app-id <connection holding the API key as a bearer token>Orchestrate then calls the JSON-RPC 0.3 interface listed in the card. If the importer sends no A2A-Version header it receives the 1.0 card (see the warning in section 2).
7. Exposing agents as MCP tools (POST /v1/mcp)
The same exposed agents are also offered to MCP clients — Claude's MCP connector, OpenAI hosted MCP tools, Copilot Studio MCP tools, Foundry Toolboxes — as tools over streamable HTTP. There is no separate flag: an agent is an MCP tool iff it is A2A-exposed and allowed_callers admit the caller. Ships in the same release as this page.
| Item | Value |
|---|---|
| Endpoint | POST /v1/mcp — JSON-RPC initialize, ping, tools/list, tools/call; stateless (no Mcp-Session-Id); GET → 405, DELETE → 204. |
| Tool names | hive.<agent-slug> for the whole agent, plus hive.<agent-slug>.<skill-id> per named card skill. |
| Input / output | { task, context?, thread_id? } (task ≤ 16 000 chars) → { text, run_id, status, thread_id }. A call is an A2A blocking SendMessage: a real, metered, audited run. |
| Approvals | A run paused for approval comes back as a tool error naming the approval id; it resumes only through POST /v1/approvals/:id/decision. |
| Auth | Authorization: Bearer with an API key (hive_…) or an access token from the tenant's configured OIDC identity provider (aud = this resource or the tenant client id). Every 401 carries WWW-Authenticate: Bearer realm="hive", resource_metadata=…. |
| Discovery (RFC 9728) | GET /.well-known/oauth-protected-resource[/v1/mcp] — resource, scopes_supported: ["hive:agents:invoke"], the tenant IdP as authorization server; name the tenant with ?tenant=<slug|id> or X-Tenant-Id. |