Learn
A2A agent card example: agent-card.json, required fields and signing
A working A2A v1.0 agent card for /.well-known/agent-card.json, each field marked required or optional, the old agent.json path and how to sign it.
7 min read 1450 words
- A2A
- agent card
- signing
- discovery
An A2A agent card is the JSON document an A2A server publishes to say who it is and how to call it. The standard location is https://{domain}/.well-known/agent-card.json. Version 1.0.0 of the specification requires 8 top-level fields: name, description, supportedInterfaces, version, capabilities, defaultInputModes, defaultOutputModes and skills. Version 0.2.6 recommended /.well-known/agent.json instead.
Where does the agent card go: agent-card.json or agent.json?
An A2A server must publish a card, according to section 8.1 of the specification. Section 8.2 names 3 ways for a client to find one: the well-known URI, a registry or catalog, and direct configuration. Section 14.3 registers the URI suffix agent-card.json with the status Permanent and says the resource MUST return an AgentCard object.
The older name was agent.json. The 0.2.6 specification recommends /.well-known/agent.json. The 0.3.0 specification recommends /.well-known/agent-card.json, and so does 1.0.0. The 1.0.0 text does not mention agent.json.
The GitHub releases date 0.2.6 to 17 July 2025, 0.3.0 to 30 July 2025 and 1.0.0 to 12 March 2026. A 1.0.1 patch followed on 28 May 2026, and the spec says patch numbers do not affect compatibility. Serve the current card at agent-card.json. The scanner reads both paths.
A2A agent card example
This card follows the 1.0.0 field list in section 4.4.1. It describes an invented order-status agent on example.com, so replace every value.
{
"name": "Order status agent",
"description": "Answers questions about the status of orders placed on example.com.",
"supportedInterfaces": [
{
"url": "https://example.com/a2a/v1",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"provider": {
"organization": "Example Shop",
"url": "https://example.com"
},
"version": "1.0.0",
"documentationUrl": "https://example.com/docs/order-agent",
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{
"id": "order-status",
"name": "Order status",
"description": "Returns the status and delivery estimate of one order, given an order number.",
"tags": ["orders", "shipping"],
"examples": ["Where is order 10432?"]
}
]
}
To list the fields that a published card is missing, compare its keys with the required 8. An empty array means none is missing:
curl -sS https://example.com/.well-known/agent-card.json \
| jq '["name","description","supportedInterfaces","version","capabilities","defaultInputModes","defaultOutputModes","skills"] - keys'
Which agent card fields are required?
Section 4.4.1 lists 14 top-level fields. Eight are required. The example above uses all 8, plus the optional provider and documentationUrl.
| Field | Required | What it holds |
|---|---|---|
name |
Yes | A human-readable name for the agent |
description |
Yes | What the agent is for, for people and other agents |
supportedInterfaces |
Yes | Ordered list of interfaces. The first entry is preferred |
version |
Yes | The version of the agent, such as 1.0.0 |
capabilities |
Yes | The optional features the agent supports |
defaultInputModes |
Yes | Media types the agent accepts, across all skills |
defaultOutputModes |
Yes | Media types the agent returns |
skills |
Yes | What the agent can do, as a list of skill objects |
provider |
No | The organization and URL behind the agent |
documentationUrl |
No | A link to more documentation |
securitySchemes |
No | How to authenticate: API key, HTTP, OAuth 2.0, OpenID Connect or mutual TLS |
securityRequirements |
No | Which of those schemes a caller must use |
signatures |
No | JSON Web Signatures computed over the card |
iconUrl |
No | A link to an icon |
Four of the required fields are arrays, and section 5.7 says a required array MUST hold at least one element. The nested objects have their own required members:
- Each
supportedInterfacesentry:url,protocolBindingandprotocolVersionare required.tenantis optional. The coreprotocolBindingvalues areJSONRPC,GRPCandHTTP+JSON. - Each
skillsentry:id,name,descriptionandtagsare required.examples,inputModes,outputModesandsecurityRequirementsare optional. provider:organizationandurlare both required onceprovideris present.capabilities: the field is required, and every member is optional. They arestreaming,pushNotifications,extensionsandextendedAgentCard.
The spec names a2a.proto as the single normative definition of these objects. A generated JSON file is non-normative, and the spec says it is not committed. There is no JSON Schema to validate against, so the tables of section 4 are what to read.
The card is public. Section 14.3 says it SHOULD NOT hold credentials or internal detail. A card that sets capabilities.extendedAgentCard to true offers a second card, with more detail, to clients that authenticate.
What changed between v0.3 and v1.0 cards?
The what's new page lists 3 changes to the card's fields. url, preferredTransport and additionalInterfaces became one list, supportedInterfaces. protocolVersion moved from the card into each interface. supportsAuthenticatedExtendedCard became capabilities.extendedAgentCard.
A v0.3 card has a top-level url and no supportedInterfaces. The scanner takes it for an agent, because it accepts a url. It then reads the card as Served with an error, because it finds no transport. Moving url and protocolVersion into a supportedInterfaces entry fixes it.
How do you sign an agent card?
Signing is optional. Section 8.4 says a card MAY carry a JSON Web Signature, and section 14.3 says a client SHOULD verify one when it is present. The procedure of section 8.4.2 has 5 steps:
- Take the card without its
signaturesmember. The spec also removes members that hold default values, such as an empty array, so leave those out of the card. - Canonicalize the rest with RFC 8785.
- Write a protected header with
alg,typandkid, and optionallyjku. The spec saystypSHOULD beJOSE. - Sign
base64url(header), a dot andbase64url(canonical card). - Put the base64url header in
protectedand the base64url signature insignature, inside asignaturesarray.
This script does all 5 with Node's built-in crypto. It makes a throwaway key to show the shape. In production, load your stored private key and publish its public half under the same kid.
// node sign-card.mjs agent-card.json
import { readFileSync, writeFileSync } from 'node:fs';
import { generateKeyPairSync, sign } from 'node:crypto';
// RFC 8785 canonical JSON, enough for strings, booleans, integers, arrays and objects.
const canonical = (v) =>
v === null || typeof v !== 'object' ? JSON.stringify(v)
: Array.isArray(v) ? `[${v.map(canonical).join(',')}]`
: `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(',')}}`;
const { signatures, ...card } = JSON.parse(readFileSync(process.argv[2], 'utf8'));
const { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' });
const header = { alg: 'ES256', typ: 'JOSE', kid: 'key-1', jku: 'https://example.com/.well-known/jwks.json' };
const protectedValue = Buffer.from(JSON.stringify(header)).toString('base64url');
const input = `${protectedValue}.${Buffer.from(canonical(card)).toString('base64url')}`;
const signature = sign('sha256', Buffer.from(input), { key: privateKey, dsaEncoding: 'ieee-p1363' }).toString('base64url');
writeFileSync('agent-card.signed.json', JSON.stringify({ ...card, signatures: [{ protected: protectedValue, signature }] }, null, 2));
writeFileSync('jwks.json', JSON.stringify({ keys: [{ ...publicKey.export({ format: 'jwk' }), kid: 'key-1', use: 'sig', alg: 'ES256' }] }, null, 2));
Serve agent-card.signed.json as the card and jwks.json at the jku address. Sign again after every edit, because a changed version no longer matches the signature. To read the header back out of a signed card:
jq -r '.signatures[0].protected' agent-card.signed.json \
| python3 -c "import sys,base64,json;p=sys.stdin.read().strip();p+='='*(-len(p)%4);print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))"
{
"alg": "ES256",
"typ": "JOSE",
"kid": "key-1",
"jku": "https://example.com/.well-known/jwks.json"
}
A client then does the reverse, as section 8.4.3 describes. It finds the key by kid and jku, or in a trusted key store. It removes default-valued members and signatures, canonicalizes the card and checks the signature.
How does AgenticWebScore check an agent card?
The A2A agent card check is an unscored agent convention marked draft. It is asked only of a site where the scan saw an A2A agent. That means an agent-card.json with a name and either supportedInterfaces or url, or an AI catalog entry for an agent card. A card that exists only at agent.json does not trigger the check.
Once asked, the check reads /.well-known/agent-card.json and then /.well-known/agent.json. It uses the first that holds a JSON object.
- Present. The card has
name,description,version, acapabilitiesobject, at least 1 input mode, 1 output mode and 1 skill, and at least 1supportedInterfacesentry withurl,protocolBindingandprotocolVersion. - Served with an error. The card has a name, description, version and skills but lacks a required field such as a usable interface, or the JSON is not an agent card.
- Not used. Both paths answered, and neither served a JSON card.
- Not measured. A path was not requested, did not answer, or answered 406 to the scan's
Acceptheader.
The check counts skills and does not read each skill's id, name, description or tags. It reads the card and does not call the endpoint in supportedInterfaces.
The signed agent card check is also unscored. It is out of scope when no card is published.
- Present. A signature verifies. The protected header names
alg,typ,kidandjku, and the key set atjkuis on the card's own host and publishes thatkid. - Not used. The card carries no
signatures, because signing is optional. - Served with an error. A signature does not verify, or the key set has no such
kid. - Not measured. The key set is on another host, was not requested or did not answer.
This check differs from the spec in 2 places:
- The spec marks
jkuoptional and allows a trusted key store. A card signed that way reads Served with an error here. - The spec removes default-valued members before canonicalizing, in section 8.4.3. This check removes only
signatures. A card that follows the spec's own example and still carries"extensions": []therefore reads Served with an error. Leave empty arrays out of the card.
On 20 October 2026 the scan found no A2A agent on cloudflare.com, github.com, resend.com or vercel.com. MDN was not asked, because the scan found no API or agent surface there. This site's record of 25 October found none either. Both checks were out of scope on every measured site. The example above is built from the spec's field list and not copied from a live card.
To point an ARD manifest at a card, add an entry whose type is application/a2a-agent-card+json and whose url is the card's address. The entry format is in ard.json and the ai-catalog.json rename. To see whether the scan finds an agent card on your site, scan it.