# How AP2 Check validates AP2 endpoints

Validated against the published AP2 spec, version **0.2.0**.

We show what a clean run cannot establish – anything that needs a live purchase is listed as "Requires a live purchase" and left out of the score, never counted as a pass.

1. **Discovery** — You can give us a domain, a UCP profile URL, or an A2A Agent Card URL. A bare domain goes to /.well-known/ucp; for an Agent Card we also read the profile at /.well-known/ucp on the same origin. We send GET requests only and no credentials. A submitted card with no UCP profile alongside it gets a card-only warning, because UCP binds AP2 discovery to the business profile. If the profile or card does not advertise AP2, the result is "not detected" rather than a failure, because a merchant without AP2 is not broken.
2. **AP2 advertisement** — We look for the capability dev.ucp.common.payment.ap2_mandate (the legacy name dev.ucp.shopping.ap2_mandate passes with a warning) and validate the entry against UCP's capability schema. It must extend dev.ucp.shopping.checkout and checkout must be advertised alongside it. We also warn if its version differs from the UCP version, if its spec or schema URLs do not resolve, or if config.vp_formats_supported lacks dc+sd-jwt.
3. **Signing keys** — When a UCP profile is present and advertises AP2, it must publish keys (an error if it does not), and each key must be a valid public JWK with no private members. With only an Agent Card, signing keys are reported as unavailable, which is a warning. We warn if no ECDSA key is published, if key ids repeat, if a legacy signing_keys list does not match keys, or if config.jwks_uri is present and its key ids differ. jwks_uri is a non-standard field; UCP verifiers read keys.
4. **Agent card** — If the profile declares an A2A service, we fetch its Agent Card and require a UCP extension on it. The card and the profile must agree on the AP2 entry's version, extends and config, and we warn if they list AP2 under different capability names. A native AP2 extension on the card is checked on its own terms.
5. **Hygiene** — We check that the discovery document is served over HTTPS.
6. **Requires a live purchase** — Six things cannot be checked without a real transaction, so we list them and do not run them: merchant_authorization on checkout responses, mandate_required enforcement, SD-JWT and key-binding verification including the checkout_hash binding, open-mandate constraint evaluation, checkout and payment receipts, and AP2 error codes. They show as "—", never as a pass, and do not change the score.

## How AP2 validation works

We check how a live site advertises AP2 against the current AP2 specification. But the phase most validators skip is the one that matters most in practice.

For AP2 that phase is the live purchase: signing a mandate, verifying it, and returning a receipt. This checker never transacts. It never creates a checkout, cart, mandate or payment, so it cannot test any of that, and it shows those rows as "Requires a live purchase" rather than pretending. A clean run says the AP2 checks passed and that some need a live purchase to verify. It does not say your implementation is AP2 conformant.

Errors are requirements the UCP AP2 extension states as MUST. The ECDSA key rule is a warning because AP2 itself is reconsidering it (AP2 #268). Version, URL, key-id and presentation-format mismatches are warnings. We check against one tagged version of AP2 – v0.2.0 – and this page says which.

### What we intentionally don't check

We never transact, so everything in the "Requires a live purchase" block is out of scope for this check: signatures, key binding, the checkout_hash binding, selective disclosures, constraint evaluation, merchant_authorization, receipts and error codes. We read the AP2 entry, the checkout capability beside it, and the profile's keys; use the UCP check for the rest of your profile and your checkout endpoints.

Paste mode checks structure only. You can paste a UCP profile, an Agent Card, a mandate's decoded claims, or a receipt. Nothing is fetched, so URL-based checks are skipped. For a mandate we check its kind and its schema, and that a checkout mandate's checkout_jwt is a compact JWS in shape. We do not verify any signature. If you paste a raw SD-JWT instead of its decoded claims, we ask you to decode it first. Paste runs are never stored.
