A2A Gateway
Overview
A2A (Agent2Agent) is an open protocol that lets AI agents from different providers exchange tasks over a standard interface. In Apinizer, A2A is a first-class API proxy type — you create an A2A Gateway the same way you'd create a REST, SOAP, or AI Gateway, and configure its behavior on a dedicated routing tab. An A2A Gateway works in one of two directions:
- Expose Agent — the proxy publishes itself as an A2A agent, backed directly by its own LLM configuration
- Passthrough — the proxy forwards tasks through to an external A2A agent, applying skill filtering and agent-card rewriting along the way
The A2A Gateway menu item takes you to a card-based list of your A2A Gateways, the same layout used for AI Gateways — open a card to reach that proxy's routing, policies, and trace/traffic/analytics/tasks tabs.
Coming from an older release? A2A used to be configured on a separate "Inbound Agent" screen with its own authentication settings, reachable at a dedicated path. That model has been replaced entirely — see Migrating from the Previous Model. New to agent-to-agent concepts? See AI Fundamentals for a primer.
Quick Start
Create a new A2A Gateway the same way you'd create any other API proxy: give it a name and a single client-facing path.
Open the proxy's A2A Routing tab and pick Expose Agent or Passthrough.
For Expose Agent, set the LLM backend on the proxy's AI Routing tab (required before you can save) and describe the agent's skills on the A2A Routing tab. For Passthrough, add one or more external agents directly on the routing tab (endpoint and authentication) and, optionally, restrict which of their discovered skills are advertised.
Attach any of Apinizer's regular authentication policies (API Key, JWT, OAuth2, ...) to the proxy — the same way you would on any other proxy type.
Deploy the proxy. Its client path becomes the single endpoint A2A clients connect to, for both discovery and task submission.
Point an A2A client at the proxy's path — it fetches the agent card from the standard discovery path, then sends tasks over that same path.
Modes
Expose Agent
An Expose Agent proxy is the agent — there's no separate backend proxy reference to configure. Its LLM backend is the proxy's own AI Routing configuration, and the five guardrail policies — Personal Data Masking, Prompt Protection, Data-Loss Protection (DLP), Off-Topic Protection, and Retry-Storm (Loop) Protection — apply to A2A traffic the same way they do on a regular AI Gateway. AI-Gateway-only policies (Semantic Cache, Token Quotas, Prompt Decorator, Prompt Templates, RAG Injection, Context-Integrity Protection) are not available here, even in Expose Agent mode — see Guardrail Policies below.
Passthrough
Forwards tasks to an external agent, defined directly on the proxy's A2A Routing tab — rewriting the fetched agent card (hostname, advertised authentication scheme, and skill list) before handing it to the caller.
Once an agent's endpoint is saved, Apinizer fetches and previews its agent card automatically.
Leave it empty to advertise every discovered skill, or restrict which ones this proxy exposes.
Each agent is probed periodically and reports Healthy, Down, or Unknown status with its last response time.
Discovery and task calls can't reach cloud-metadata endpoints, loopback, or link-local addresses — see Security and Governance.
A2A Agents Are Configured Inline
There's no standalone "A2A connection" screen to visit first — an external agent is defined at the exact place it's used:
- On a Passthrough proxy's A2A Routing tab, where it can be one of several aggregated agents for discovery (see Multi-Target Aggregation).
- Inside the A2A Call (LLM) / A2A Call (Fixed) policy, as a single agent a model delegates a sub-task to as part of any AI Gateway's own tool-call loop — no A2A Gateway required for that path at all.
Each definition carries its own endpoint, authentication — including an OAuth2 client-credentials flow for agents that require one, acquired automatically at runtime — discovered skill catalog, health status, and approved-catalog fingerprint. Nothing is shared by reference between proxies or policies.
The endpoint you enter is the address tasks are sent to. Its agent card is looked for at that same address first, and if the card isn't served there, at the standard discovery path below it (.../.well-known/agent-card.json) — so entering an agent's task endpoint is enough for discovery to work, and entering its card URL directly keeps working as before.
Outbound Agent Call
When you want to send a task from Apinizer to an external A2A agent, the component that actually delivers it is the A2A Call (LLM) policy (attached to an AI Gateway). As with Passthrough mode, the target agent is defined inline, on the policy's own screen:
Attach the A2A Call (LLM) policy to an AI Gateway.
In the A2A Agent (Inline) section, enter the endpoint, authentication, and mTLS settings if required.
The Discover Skills button connects to the agent, fetches its agent card, and confirms its skill list — it runs before you save, so there's no separate "test connection" step.
Leave it empty to allow every discovered skill; to restrict, pick from the Allowed Skills list.
This agent definition is subject to the same SSRF protection as Passthrough mode (see Security and Governance). The policy screen's A2A Connection field is kept for backward compatibility: it's disabled once an inline agent is defined, and only one of the two is ever used at runtime.
This policy can also be added and updated through the APIops REST API — see API Reference: Policies.
Agent Card, Task Lifecycle, and Streaming
Agent Card
Every A2A Gateway publishes a self-describing agent card, unauthenticated, at the proxy's client path plus /.well-known/agent-card.json — required so a client can discover the agent before it has credentials. The card includes the agent's name, description, skills, and supported input/output modalities, plus an advertised authentication scheme derived automatically from the proxy's own authentication policies (JWT and OAuth2 are reflected as bearer/OAuth2 schemes, Basic as a basic scheme) — an unauthenticated proxy's card advertises no scheme at all. There's no separate per-agent auth-mode selector anymore, and the card can never claim a scheme the gateway doesn't actually enforce. An authenticated client can also fetch the same card over the JSON-RPC channel.
Protocol Version
The only A2A protocol version Apinizer speaks is 1.0 (the field is kept for a future version expansion) — unlike MCP, there's no version to choose.
Task Submission and Lifecycle
A client starts a task with SendMessage. In Expose Agent mode Apinizer accepts it and returns a task ID immediately, then works it in the background; in Passthrough mode the backend agent creates and owns the task, and its answer — task ID included — is returned to the client unchanged.
| State | Meaning |
|---|---|
| Submitted | Received, processing not yet started |
| Working | Being processed |
| Completed | Finished successfully |
| Failed | Ended with an error |
| Canceled | Canceled before completion |
In Expose Agent mode, Apinizer's own agent only ever advances a task through submitted → working → completed / failed / canceled / rejected. The remaining two states (input-required, auth-required) are never produced by Apinizer's own agent — they can only be observed on external agents Apinizer talks to as an outbound client.
A client polls status with GetTask, lists a proxy's tasks with ListTasks, and can cancel an in-progress task with CancelTask — cancellation actually interrupts the task's processing on the worker, not just its recorded status.
In Passthrough mode the backend agent owns the tasks it creates, so GetTask and CancelTask resolve in two steps: the proxy first looks for a task it tracks itself (a streaming relay is tracked by the gateway), and if the ID isn't one of those, the call is forwarded to the backend agent and its answer is returned as-is — including its own "task not found" if the ID is unknown there too. This is what lets a client poll the exact task ID a Passthrough SendMessage returned. Whether a given task ID may be read or canceled is decided by the backend agent, the same way it decides authorization for a delegated message. ListTasks and SubscribeToTask always answer from the gateway's own task records and are not forwarded; on a proxy that aggregates more than one agent, GetTask and CancelTask return an error rather than guessing which agent owns the task.
The gateway's own task records are automatically cleaned up some time after reaching a terminal state (completed/failed/canceled/rejected); active tasks are retained indefinitely. A task created by a non-streaming Passthrough call is retained for as long as its backend agent keeps it.
Streaming
A client can request streaming task updates with SendStreamingMessage, delivered over Server-Sent Events. If the connection drops, SubscribeToTask replays the task's stored history so the client can pick up from where it left off.
Push Notifications
For long-running tasks, a client can register a webhook with CreateTaskPushNotificationConfig — Apinizer calls it (with an authentication token) once the task reaches a terminal state, so the client doesn't have to keep a connection open or poll for the result.
Multi-Modality Task Parts
A task can carry parts beyond plain text — image and structured data parts are also supported.
Supported task part types are text, structured data, and image. Audio and video are not covered.
Multi-Target Aggregation
A Passthrough proxy can aggregate up to 10 agents on its A2A Routing tab behind one endpoint, but only for discovery: their skills are merged into a single advertised agent card, with a colliding skill name resolved deterministically the same way MCP tool names are. Sending an actual task is different — the A2A protocol gives a message no field to say which agent it's meant for, so once more than one agent is configured, SendMessage returns an explicit error rather than guessing which agent should receive it. Configure more than one agent only if you want a combined skill catalog for discovery; keep exactly one agent if you intend to actually delegate tasks through the proxy.
Security and Governance
Authentication
Same model as MCP: whatever policy chain you attach to the proxy. The agent-card discovery endpoint is exempt from it by design.
Identity-Based Access Control (Passthrough discovery only)
In Passthrough mode, you can restrict which skills the proxy advertises to a given credential, role, or organization — a caller not covered by any rule sees no skills once at least one rule is configured. This narrows what appears in the agent card, not what a SendMessage call is allowed to do: the A2A wire message carries no per-call skill identifier, so — unlike MCP's per-tool-call enforcement — this can't gate an individual task at call time; actual authorization for a delegated message is up to the backend agent. In Expose Agent mode this setting isn't applied at all.
Call Quotas
Cap SendMessage volume per minute, hour, or day, scoped per calling credential. Because a message carries no per-call skill identifier, a "per tool" quota on this surface collapses to a single per-proxy bucket rather than a genuinely per-skill one — set the scope to per-credential for meaningful enforcement:
{
"callQuota": { "scope": "PER_CREDENTIAL", "period": "MINUTE", "limit": 30 }
}
Catalog Drift Protection
The same mechanism as MCP, applied to an inline agent's advertised skill list instead of a tool catalog: a fingerprint is captured at the last approved discovery, a periodic health check compares the live card against it, and drift either logs and surfaces a warning (the default) or blocks task delegation through that agent until it's re-approved.
Token Passthrough
The same opt-in model as MCP: off everywhere by default. Forwarding the caller's own token to a backend agent requires explicitly enabling it on the agent's definition, optionally restricted to an allow-list of accepted audiences, with the agent's own credential resolution skipped once a forward succeeds.
Guardrail Policies
Five of Apinizer's Advanced Guardrails — Personal Data Masking, Prompt Protection, Data-Loss Protection (DLP), Off-Topic Protection, and Retry-Storm (Loop) Protection — can be attached to an A2A Gateway, in both Expose Agent and Passthrough mode, the same way as on any other proxy type. They run through the proxy's regular policy chain and scan the JSON-RPC message parts (params.message.parts[]) on the request side and the task/message result envelope on the response side. Two of these five — Prompt Protection and Off-Topic Protection — can additionally call out to an external LLM judge for a second opinion, the same way they do on an AI Gateway. The remaining AI policy types (Semantic Cache, Token Quotas, Prompt Decorator, Prompt Templates, RAG Injection, Context-Integrity Protection) depend on an actual LLM invocation and are rejected if you try to attach them to an A2A Gateway; see Advanced Guardrails: MCP and A2A Gateways.
Egress Protection
Outbound A2A calls — agent-card fetch, task submission, health probes, and push-notification dispatch — can never reach cloud-metadata endpoints, loopback, link-local, multicast, or carrier-grade-NAT addresses, regardless of configuration. An agent definition (or, for push notifications, the proxy itself) can optionally be allowed to reach other private-network ranges (for example an in-cluster agent), but that opt-in never lifts the addresses above.
This same protection is shared with MCP Gateway outbound calls.
Environment Variables in Configuration
Most of the text you type into an A2A Gateway's configuration accepts an environment variable reference in ${VARIABLE_NAME} form, resolved per environment when the proxy runs — so one configuration moves from test to production without being edited.
Fields that accept a variable:
| Where | Fields |
|---|---|
| Outbound agent call | Skill name, message template |
| Access rules | Allowed skill list, identity rule credential/role/organization name, allow and deny lists |
| Token passthrough | Accepted audience list |
The message template additionally accepts #{...} context variables, evaluated per request after the environment variables have been substituted.
Resolution is fail-closed, exactly as on the MCP Gateway: an undefined variable leaves the text as written, which matches no real skill or audience, so the call is denied instead of falling through to an unintended match — and a caller cannot send the literal text ${VARIABLE_NAME} as a skill name to slip past an access rule.
Push notification settings are the one deliberate exception: the webhook address and its token come from the calling client at runtime, not from an operator, so they are always treated as literal text. Resolving them would let a caller ask the gateway to place one of its own environment values into a webhook it controls.
Logging and Analytics
Every A2A call is recorded in your traffic logs with the request's correlation ID, so a delegated task can be followed end to end; an inbound gateway request additionally records the task ID, context ID, and current task state. Per-call detail for an outbound delegation — which agent it resolved to, skill, duration, and outcome — is captured in the request's trace execution detail rather than as flat log columns. These calls also appear in trace groups alongside the rest of a request's chain, and roll up into your usage reports.
An A2A Gateway's own detail page carries this same trace/traffic/analytics view under proxy-specific tabs — A2A Trace, A2A Traffic, and A2A Analytics — plus a Tasks tab where you can browse in-flight and historical tasks without leaving the proxy. The tab lists the tasks the gateway itself tracks: every Expose Agent task, and a Passthrough task created by a streaming call. A task created by a non-streaming Passthrough call belongs to the backend agent, so it is visible in the traffic log but not in this tab — and a client's GetTask for it is answered by that agent (see Task Submission and Lifecycle).
Migrating from the Previous Model
- The separate A2A "Inbound Agent" screen is gone; A2A is now configured directly on an A2A Gateway's A2A Routing tab.
- Per-agent authentication mode (None / API Key) no longer exists — authentication is whatever policy chain you attach to the proxy, the same as any other proxy type.
- The old dedicated inbound path is gone; clients must be repointed at the A2A Gateway's own client path.
- Existing inbound agent definitions aren't carried forward automatically — recreate them as an A2A Gateway. Task history for tasks that already ran is retained.
- Outbound A2A connections are no longer a standalone, shared resource. The connection screen is gone; an external agent is now defined inline, directly on a Passthrough proxy's A2A Routing tab or inside the A2A Call (LLM) / A2A Call (Fixed) policy that uses it (see A2A Agents Are Configured Inline). This is auto-migrated on upgrade: any proxy or policy that referenced an outbound A2A connection under the old model gets that connection copied inline automatically — there's nothing to recreate. If the same connection was shared by more than one proxy or policy, each one ends up with its own independent copy (credential included) after the upgrade; keep that in mind when you next rotate the credential. Agent-card discovery, health probing, catalog drift protection, and token passthrough all still work the same way; they're just scoped to the agent's own definition instead of a shared record.