AgenticWebScore
Scan

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.

Which agent card fields are required? — Field, Required, What it holds
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 supportedInterfaces entry: url, protocolBinding and protocolVersion are required. tenant is optional. The core protocolBinding values are JSONRPC, GRPC and HTTP+JSON.
  • Each skills entry: id, name, description and tags are required. examples, inputModes, outputModes and securityRequirements are optional.
  • provider: organization and url are both required once provider is present.
  • capabilities: the field is required, and every member is optional. They are streaming, pushNotifications, extensions and extendedAgentCard.

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:

  1. Take the card without its signatures member. The spec also removes members that hold default values, such as an empty array, so leave those out of the card.
  2. Canonicalize the rest with RFC 8785.
  3. Write a protected header with alg, typ and kid, and optionally jku. The spec says typ SHOULD be JOSE.
  4. Sign base64url(header), a dot and base64url(canonical card).
  5. Put the base64url header in protected and the base64url signature in signature, inside a signatures array.

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, a capabilities object, at least 1 input mode, 1 output mode and 1 skill, and at least 1 supportedInterfaces entry with url, protocolBinding and protocolVersion.
  • 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 Accept header.

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, kid and jku, and the key set at jku is on the card's own host and publishes that kid.
  • 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 jku optional 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.