Canonical definition

/.well-known/ucp – Definition of the UCP Business Profile

/.well-known/ucp is the fixed URL where a business publishes its UCP profile: a public JSON document declaring the protocol version it speaks, the services and capabilities it supports, its payment handlers, and the public keys used to verify its signed responses. It is the single entry point of the protocol – an agent that cannot fetch and parse this file cannot transact with the store at all.

In one sentence

A store’s machine-readable business card for AI agents, served unauthenticated over HTTPS at a path every agent already knows to look for.

What the document contains

{
  "ucp": {
    "version": "2026-08-25",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/overview/",
          "transport": "rest",
          "endpoint": "https://store.example.com/ucp/v1",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/catalog/search",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/catalog_search.json"
        }
      ]
    },
    "payment_handlers": {}
  },
  "keys": [
    {
      "kid": "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U",
      "kty": "OKP", "crv": "Ed25519",
      "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs",
      "use": "sig", "alg": "EdDSA"
    }
  ]
}

Required members

MemberStatusNote
ucp.versionRequiredDate format, e.g. 2026-08-25
ucp.servicesRequiredMust be present even when empty – and must serialize as {}, never []
ucp.payment_handlersRequiredSame rule
ucp.capabilitiesOptionalLegal to omit, but a store with none has nothing to sell an agent
keysOptionalTop-level, sibling of ucp. An RFC 7517 JWK Set
ucp.supported_versionsOptionalMap of older versions to complete profiles you host yourself
The empty-registry trap

All three registries are JSON objects keyed by reverse-domain name. An empty PHP or JavaScript array encodes as [], which fails schema validation with [] is not of type 'object'. A store with no payment handlers must emit "payment_handlers": {}.

Hosting rules

  • Served over HTTPS. Plain HTTP is rejected.
  • Content-Type: application/json.
  • Access-Control-Allow-Origin: * – agents fetch it cross-origin.
  • Cache-Control: public with max-age of at least 60 seconds.
  • No 3xx redirects. A platform does not follow them when fetching a profile.
  • Public and unauthenticated by design. Never put secrets, internal URLs or staff contacts in it.

Keys live in keys[], not signing_keys[]

Protocol version 2026-04-08 published signing keys under signing_keys[]. Since 2026-08-25 the canonical field is the top-level keys[] array – an RFC 7517 JWK Set, which makes the profile simultaneously a valid JWK Set a signer can reuse as its Web Bot Auth key source.

A profile that advertises 2026-08-25 while still using signing_keys[] is not rejected outright – but no verifier reads that field, so it publishes no usable key at all. The reverse also holds: a key is not effectively revoked until it is absent from keys[], which is why rotation goes new-key-first.

Serving it from Magento 2

The path contains a dot segment, and Magento’s router forbids dots in front names, so a static file or a naive route will not work. Two failure modes are common:

  • A file dropped at pub/.well-known/ucp is served by nginx as application/octet-stream with no CORS headers – it fails validation.
  • On LiteSpeed, PATH_INFO is frequently empty for dot-segment paths, so a router matching only getPathInfo() returns 404.

angeo/module-ucp serves the endpoint from a PHP controller with correct headers and multi-source path matching, and ships bin/magento angeo:ucp:validate to check the generated profile – including authority binding, which platforms enforce silently.

How to check any store’s profile

curl -sI https://store.example.com/.well-known/ucp
# HTTP/2 200
# content-type: application/json
# access-control-allow-origin: *
# cache-control: public, max-age=300

curl -s https://store.example.com/.well-known/ucp | python3 -m json.tool

Questions

Does the UCP profile have to be at exactly /.well-known/ucp?
Yes for a business. That path is where every agent looks. Platforms publish their own profile at a URI they advertise in the UCP-Agent header instead.
Is the UCP profile public?
Yes, and unauthenticated by design. Everything in it is world-readable, so it must contain no secrets, no internal hostnames and no private key material.
What happens if my UCP profile returns a redirect?
The fetch fails. A platform must not follow 3xx responses when fetching a profile, so an http-to-https or trailing-slash redirect makes the store undiscoverable.
Should I declare a UCP capability before the endpoint exists?
No. Negotiation activates whatever both sides declare, so an advertised capability with no endpoint behind it produces failing requests rather than a graceful skip.
Why does my empty UCP profile fail schema validation?
Almost always the empty-registry encoding. services, capabilities and payment_handlers are JSON objects, and an empty array encodes as [] instead of {}.

Related

Verified 28 August 2026 against the official specification repository at tag v2026-08-25 (profile.json, ucp.json, capability.json, service.json). Disclosure: we publish the open-source UCP modules referenced above.