What is /.well-known/ucp?
One JSON file at a fixed address on your store that tells a UCP agent what it can do there: find products, build a cart, check out and pay. Here is what goes in it, what each part lets an agent do, and how to publish it so it passes.
https://yourstore.com/.well-known/ucp
The address follows the well-known URI convention, so an agent never has to search for it. Without the file, an agent is left reading your pages like a crawler, guessing at products and forms, and it usually fails at checkout. With it, the agent gets a contract: the capabilities you offer, the transports it can use and the payment methods you accept, each with its endpoint and schema.
The profile, in the order an agent reads it
The smallest profile that passes against release 2026-08-25, split into its five parts.
-
{ "ucp": { "version": "2026-08-25",1ucp.versionrequiredWhich release the store follows
The agent reads this first and applies that release's rules to everything below. A release is a date, YYYY-MM-DD.
Must be a published release. "2026", "1.0.0" or a typo fails.
opens way in 01: Finding the profile All releases -
"services": { "dev.ucp.shopping": [ { "version": "2026-08-25", "spec": "https://ucp.dev/2026-08-25/specification/overview", "transport": "rest", "schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json", "endpoint": "https://yourstore.com/ucp" } ] },2ucp.servicesrequiredWhere the agent sends requests
One entry per transport: REST, MCP or A2A to call your services, Embedded to open your checkout in the buyer's browser. Without a reachable service, the rest of the profile is a promise.
Every entry names its transport and endpoint, at the same version as ucp.version.
opens way in 04: Calling its services Transports -
"capabilities": { "dev.ucp.shopping.checkout": [ { "version": "2026-08-25", "spec": "https://ucp.dev/2026-08-25/specification/shopping/checkout", "schema": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json" } ] },3ucp.capabilitiesoptional, but a store that sells declares checkoutWhat the agent may do here
Catalog search lets it find products, cart and checkout let it buy, identity linking lets it act for a signed-in buyer, a permalink sends the buyer to your website.
Every capability declares a schema. An extension names its parent in extends, and is dropped if the parent is missing.
opens way in 02: Acting for the buyer Every capability -
"payment_handlers": { "com.example.processor": [ { "id": "processor-1", "version": "2026-08-25", "spec": "https://example.com/ucp/handler", "schema": "https://example.com/ucp/handler/schema.json" } ] } },4ucp.payment_handlersrequired, even when emptyHow the agent pays
The payment methods you accept and how to tokenise them, so card data never passes through the agent. Each handler has a unique id.
Handlers version on their own schedule, so their version need not match ucp.version.
opens way in 04: Calling its services Payment handlers in use -
"keys": [ { "kty": "EC", "crv": "P-256", "kid": "store-2026-10", "x": "…", "y": "…" } ] }5keysoptional, at the rootHow the agent checks your messages
The public keys (a JWK Set) an agent uses to verify messages your store signs, such as order webhooks.
Sits beside ucp, not inside it, and every key has a kid. Since 2026-08-25 it is keys; it used to be signing_keys.
{
"ucp": {
"version": "2026-08-25",
"services": {
"dev.ucp.shopping": [
{
"version": "2026-08-25",
"spec": "https://ucp.dev/2026-08-25/specification/overview",
"transport": "rest",
"schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json",
"endpoint": "https://yourstore.com/ucp"
}
]
},
"capabilities": {
"dev.ucp.shopping.checkout": [
{
"version": "2026-08-25",
"spec": "https://ucp.dev/2026-08-25/specification/shopping/checkout",
"schema": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
}
]
},
"payment_handlers": {
"com.example.processor": [
{
"id": "processor-1",
"version": "2026-08-25",
"spec": "https://example.com/ucp/handler",
"schema": "https://example.com/ucp/handler/schema.json"
}
]
}
},
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "store-2026-10",
"x": "…",
"y": "…"
}
]
}
Replace com.example.processor with your payment provider's handler, and the endpoints with yours. Every dev.ucp.* entry carries the same version as ucp.version.
What an agent finds when the profile is right
The same trace every status page shows, on an illustrative store that offers all five ways in. Check your store to see yours.
6 requests · 350 ms at the store · public requests only
-
Finding the profileOpen
-
Acting for the buyerOffered
-
In the buyer's browserDeclared
-
Calling its servicesOpen
-
Sending the buyer to the websiteOffered
- 2 requestsFinding the profile/.well-known/ucp · 200
- read from profileActing for the buyerdeclared · accounts.yourstore.com
- not calledIn the buyer's browserEmbedded · declared
- 2 requestsCalling its servicesREST, MCP · answered
- read from profileSending to the websitedeclared · /buy
- 2 requestsOutside UCPhomepage · /llms.txt
- 01 PROFILEGET /.well-known/ucp → 200, 3.0 KB of JSON120 ms
- 01 PROFILEGET /robots.txt → allows /.well-known/ucp–
- 01 PROFILEValid against the 2026-08-25 schema; keys publishedlocal
- 04 SERVICESPOST /mcp tools/list → 200140 ms
- 04 SERVICESGET /ucp → 20090 ms
- 03 NOTEEmbedded service declared; not called by this check–
- OUTSIDE UCPGET / → 200, Organization schema found–
- OUTSIDE UCPGET /llms.txt → 200, 2.3 KB–
The request it has to pass
Any server will do. What matters is the answer an agent gets, and the live check tests each line.
GET /.well-known/ucp HTTP/1.1 Host: yourstore.com Accept: application/json
200 OKat that exact URL: no redirect to www or another hostContent-Type: application/jsonnot text/plain or text/htmlCache-Control: public, max-age=300public, at least 60 seconds; never private or no-storeETagorLast-Modifiedso an agent can revalidate its copy cheaplyrobots.txtallows /.well-known/ucp, and no firewall rule turns agents away
How to serve it
location = /.well-known/ucp {
default_type application/json;
alias /var/www/ucp.json;
add_header Cache-Control "public, max-age=300";
}
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/.well-known/ucp') {
return new Response(JSON.stringify(PROFILE), {
headers: {
'content-type': 'application/json',
'cache-control': 'public, max-age=300',
},
});
}
return fetch(request);
},
};
app.get('/.well-known/ucp', (req, res) => {
res.type('application/json')
.set('Cache-Control', 'public, max-age=300')
.json(profile);
});
# Upload ucp.json to a public bucket, route /.well-known/ucp to it # and set the object's metadata: Content-Type: application/json Cache-Control: public, max-age=300
