A2A Agent Cards Explained: How to Publish and Validate .well-known/agent-card.json
The A2A protocol's Agent Card is a JSON file at /.well-known/agent-card.json declaring an agent's skills, protocolVersion, and securitySchemes. Field-by-field guide.
The Agent Card is the front door of the A2A (Agent2Agent) protocol — the Google-initiated, now Linux Foundation-governed standard for agent-to-agent collaboration. It is a single JSON document that tells any client agent everything it needs to start working with yours: who you are, what you can do, where your endpoint lives, and how to authenticate. No card, no interoperability.
This post covers where the card lives, what every field means, the parts implementers most often get wrong, and how to validate one.
Where the Card Lives
The A2A specification designates a well-known location, per RFC 8615:
https://{your-domain}/.well-known/agent-card.json
A client that knows only your domain can GET that path and bootstrap the entire relationship from the response. Early A2A deployments (pre-0.3.0) served the card at /.well-known/agent.json instead — many clients still check both paths, and if you're migrating, serving the card at both for a transition period is the pragmatic move.
The spec's discovery guidance also allows two other channels — curated registries holding vetted cards, and direct configuration for tightly coupled internal systems — but the well-known URI is the mechanism that makes an agent publicly discoverable, and it's what registry crawlers and the new ARD catalog format build on.
A Complete Example
{
"protocolVersion": "0.3.0",
"name": "Acme Travel Agent",
"description": "Books flights, hotels, and ground transport for corporate travel.",
"version": "1.4.2",
"url": "https://api.acme.com/a2a/v1",
"preferredTransport": "JSONRPC",
"provider": {
"organization": "Acme Corp",
"url": "https://acme.com"
},
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{
"id": "book-flight",
"name": "Book a flight",
"description": "Searches fares and books flights on major carriers.",
"tags": ["travel", "flights"],
"examples": ["Book me a flight from SFO to JFK next Tuesday"]
}
],
"securitySchemes": {
"oauth": {
"type": "openIdConnect",
"openIdConnectUrl": "https://auth.acme.com/.well-known/openid-configuration"
}
}
}
Field-by-Field
| Field | Required | What it does |
|---|---|---|
protocolVersion | Yes | A2A spec version the agent implements (e.g. 0.3.0) |
name / description | Yes | Human-readable identity; LLM clients also use these for routing decisions |
version | Yes | Your agent's own release version |
url | Yes | The A2A service endpoint clients actually call — not the card's own URL |
preferredTransport | No | JSONRPC (default), GRPC, or HTTP+JSON; additional endpoints go in additionalInterfaces |
capabilities | Yes | Protocol features: streaming (SSE task updates), pushNotifications (webhook callbacks) |
defaultInputModes / defaultOutputModes | Yes | Media types accepted and produced, overridable per skill |
skills | Yes | The list of things the agent can do — see below |
securitySchemes / security | No, but expected in production | How to authenticate, in OpenAPI 3 security-scheme format |
Skills: The Part That Does the Selling
A skill is not a function signature — it's a capability advertisement. Each skill carries an id, name, description, tags, and free-text examples. Client agents (and the LLMs inside them) read these to decide whether to engage your agent at all, before any message is exchanged. Vague skill descriptions are the single most common reason a technically working A2A agent never gets traffic: "Handles requests" tells a routing LLM nothing; "Searches fares and books flights on major carriers" is a match target.
Treat examples the way you'd treat few-shot prompts — realistic, specific, phrased the way a user would actually ask.
Security Schemes: Where Cards Meet OAuth
securitySchemes reuses the OpenAPI 3 format, so the values will look familiar: openIdConnect with a discovery URL, oauth2 with flow definitions, apiKey, or http bearer. The openIdConnect type is the most common in production cards, and it chains well-known documents together: the agent card at /.well-known/agent-card.json points to an OIDC discovery document at /.well-known/openid-configuration, which in turn declares the token endpoint, grant types, and JWKS.
That second document is worth inspecting before you integrate — a card can name an issuer whose discovery document is missing, stale, or lacking the grant types the card implies. The OIDC Inspector fetches and parses any provider's discovery document and JWKS from a bare domain, which makes it a quick way to verify the auth half of an agent card's claims.
One rule the spec is emphatic about: secrets never go in the card. The card says how to authenticate, never contains credentials, and everything — card and endpoint alike — is HTTPS only.
Extended Cards and Caching
Two operational details that separate a demo card from a production one:
- Authenticated extended card. If your agent exposes more skills to authenticated partners than to the public, set
supportsAuthenticatedExtendedCard: trueand serve the fuller card from theagent/getAuthenticatedExtendedCardmethod. The public card stays minimal; partners get the real surface after auth. - Cache headers. Registry crawlers and clients re-fetch cards on a schedule. Serve the card with
Cache-Control: public, max-age=3600(tune to your release cadence) so you're not paying origin hits for a static document, and bumpversionwhen the card changes.
A Validation Checklist
Before you announce an agent card, verify:
GET https://{domain}/.well-known/agent-card.jsonreturns200withContent-Type: application/json— from a cold client, no auth, no cookies.- The JSON parses and includes
protocolVersion,name,url, and at least one skill. urlpoints at the live A2A endpoint and answers JSON-RPC — a card whose endpoint 404s is worse than no card.- Every
openIdConnectUrlinsecuritySchemesresolves to a valid discovery document. capabilitiesonly claims what's implemented — declaringstreaming: truewithout SSE support breaks clients mid-task.- If you migrated from
agent.json, the legacy path either serves the same card or redirects.
Five minutes of checking beats debugging a partner integration that fails on step one of discovery.
Follow Trango Compute on LinkedIn
We post updates on new tools, context engineering patterns, and LLM cost research.