Reference

What /.well-known/ucp is, and how to serve it correctly

One file on your own domain decides whether an agent following the Universal Commerce Protocol can identify your store at all. This is what belongs in it, the five rules the specification sets for serving it, and the recipe for each platform.

What the file is

An agent that wants to transact with a business does not read your homepage. It fetches a JSON document from a fixed path on your domain, reads which protocol version you speak and which services and capabilities you expose, intersects that with its own, and negotiates from there. That document is the business profile, and the path is fixed.

quoted verbatim
“Profile documents are machine-readable discovery documents. Businesses publish their profile at /.well-known/ucp… A profile document is a JSON object with a required ucp member. For both business and platform profiles, ucp.version, ucp.services and ucp.payment_handlers are required. The services and payment_handlers registries MUST be present even when empty. ucp.capabilities is optional and MAY be omitted, though useful commerce profiles normally advertise at least one capability.”
UCP specification — Profile Structure

Two things follow from that paragraph and are worth stating before anything else. The profile carries protocol metadata and nothing else: there is no field for your company name, your description or a contact address, so a profile is not an “AI business card” however often it is described as one. And the smallest structurally valid document is almost empty:

the minimum that parsesnot useful
{
  "ucp": {
    "version": "2026-08-25",
    "services": {},
    "payment_handlers": {}
  }
}

That document is conformant and worthless. It tells an agent you speak the protocol and expose nothing it can act on. Publishing it to make a checker go green is the one mistake this whole page is trying to prevent.

The five rules for serving it

Conformance is not only about the JSON. The specification puts normative requirements on the response itself, under Profile Requirements → Hosting.

quoted verbatim
“Published artifacts MUST be served over HTTPS. Profile endpoints MUST NOT use redirects (3xx). Published artifacts MUST include a Cache-Control header with public and max-age of at least 60 seconds, and MUST NOT be served with private, no-store, or no-cache directives. Published artifacts SHOULD include a validator (ETag or Last-Modified) so consumers can revalidate cached copies efficiently.”
UCP specification — Profile Requirements, Hosting
What each rule means in practice
RuleThe requirementWhat goes wrong
HTTPSPublished artifacts MUST be served over HTTPS.An agent rejects a profile URL that is not https, without fetching it.
No redirectsThe profile endpoint MUST NOT answer with a 3xx.Discovery fails. This is the rule that catches most stores, so it has its own page.
Cache-ControlMUST include public and max-age of at least 60 seconds, and MUST NOT send private, no-store or no-cache.A profile behind a page cache that sets no-store is non-conformant even though it returns the right JSON.
A validatorSHOULD include an ETag or Last-Modified.Without one, every consumer re-downloads the whole profile instead of revalidating it.
Stable URLProfile URLs carry no per-transaction or per-session configuration.A profile that varies by visitor cannot be shared-cached, which the caching rule above exists to enforce.

All five are visible in the response headers, so you can check them yourself in one command:

shellheaders only
curl -sS -D - -o /dev/null https://yourstore.com/.well-known/ucp

# What you are looking for, in order:
#   HTTP/2 200                          <- not 301, 302, 307 or 308
#   content-type: application/json      <- not text/html
#   cache-control: public, max-age=300  <- public, and at least 60
#   etag: "..."                         <- or last-modified

What goes inside the profile

services says where an agent can reach you and over which transport — rest, mcp, a2a or embedded. Each entry needs a version, a spec URL and a transport, and REST, MCP and embedded entries also need a schema. capabilities is how an agent matches your store to a shopping intent; the intersection of your capabilities and the agent's is what gets negotiated. payment_handlers declares what your checkout accepts.

/.well-known/ucpapplication/json
{
  "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://yourstore.com/ucp/shopping",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/capabilities/checkout/",
          "schema": "https://ucp.dev/2026-08-25/capabilities/checkout/schema.json"
        }
      ]
    },
    "payment_handlers": {},
    "supported_versions": {
      "2026-04-08": "https://yourstore.com/.well-known/ucp/2026-04-08"
    }
  },
  "keys": []
}

Serving it, per platform

The hard part is rarely producing the JSON. It is getting your platform to answer at that exact path, with a 200 and your own headers. Most hosted platforms reserve the path, rewrite it, or hand you only a redirect rule.

Where the profile has to come from
PlatformApproachNotes
ShopifyAlready servedWe checked a live Shopify store and found a conformant profile at version 2026-08-25, served by the platform. There is nothing for you to publish.
WooCommerceNot in coreWordPress serves /.well-known/ through the rewrite layer, so a plugin or a template_redirect hook can answer the path directly. Community plugins exist; they are young and not endorsed by the working group, so read what they emit before trusting it.
Magento / Adobe CommerceWeb-server mappingThe usual pattern is a controller that renders the profile, mapped onto the public path at nginx or the CDN. Map it with a rewrite, never a redirect.
PrestaShopFront controller or fileEither a module front controller plus a web-server alias, or a static file written on each catalogue change. Check that your cache layer is not adding no-store.
BigCommerceEdge ruleThe storefront will not serve an arbitrary path, so the profile belongs at the edge: a worker or function in front of the domain, answering with 200.
Headless / customA routeThe easiest case. Add a route at /.well-known/ucp that returns the JSON with the two headers above. Avoid a static asset whose cache headers you do not control.

Whatever the platform, the mechanism is the same: answer at the requested URL. These three do that.

nginxrewrite, not redirect
# nginx: serve the file at the path itself. A rewrite or proxy_pass keeps
# the 200; "return 301" would fail discovery outright.
location = /.well-known/ucp {
    default_type application/json;
    add_header Cache-Control "public, max-age=300" always;
    alias /var/www/ucp/profile.json;
}
Apachehttpd.conf or vhost
# Apache: Alias maps the path without issuing a redirect.
Alias "/.well-known/ucp" "/var/www/ucp/profile.json"

<Files "profile.json">
    ForceType application/json
    Header set Cache-Control "public, max-age=300"
</Files>
JavaScriptedge worker
// Cloudflare Worker, for a platform that will not let you add a file.
// This answers at the requested URL, so it is not a redirect.
export default {
  async fetch(request, env) {
    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);
  },
};

Versions, and why they go stale quietly

UCP versions are dates, and matching is exact. The current release is 2026-08-25. An agent on a release your profile does not declare will not negotiate with you, and nothing anywhere logs an error you would see — the store simply stops being reachable to that agent.

quoted verbatim
“Businesses that support older protocol versions SHOULD declare a supported_versions object mapping each older version to a profile URI. Each URI points to a complete, self-contained profile for that version — whose ucp.version equals its map key… When supported_versions is omitted, only version is supported.”
UCP specification — Protocol Version

So a profile is not a file you publish once. It is a file with a maintenance schedule attached, and the cost of forgetting it is silent.

Check it on your own store

The scan fetches this path on your domain and reports what it found: whether the profile exists, parses, declares services and capabilities, and is served with the headers above. It checks the other three readiness axes at the same time. If the path is intercepted by a bot filter, it says so rather than guessing.

Run the free scan

No account, no email. It reports what it measured and says so when it could not measure something.