<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peter</title>
    <description>The latest articles on DEV Community by Peter (@ucptools).</description>
    <link>https://dev.to/ucptools</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3737432%2F036b6d50-5faf-4939-ac76-c7f56e7a6382.jpeg</url>
      <title>DEV Community: Peter</title>
      <link>https://dev.to/ucptools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ucptools"/>
    <language>en</language>
    <item>
      <title>Which UCP version is your validator actually checking against?</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Thu, 24 Sep 2026 10:28:33 +0000</pubDate>
      <link>https://dev.to/ucptools/which-ucp-version-is-your-validator-actually-checking-against-3kd6</link>
      <guid>https://dev.to/ucptools/which-ucp-version-is-your-validator-actually-checking-against-3kd6</guid>
      <description>&lt;p&gt;If you have a UCP profile at &lt;code&gt;/.well-known/ucp&lt;/code&gt;, you have probably run it through a validator and got a score. The question almost nobody asks next is which version of the spec that score was measured against.&lt;/p&gt;

&lt;p&gt;It matters more than it sounds. UCP has four published releases: &lt;code&gt;2026-01-11&lt;/code&gt;, &lt;code&gt;2026-01-23&lt;/code&gt;, &lt;code&gt;2026-04-08&lt;/code&gt; and &lt;code&gt;2026-08-25&lt;/code&gt;. The profile shape changed completely in April, and the signing-keys field was renamed in August. A validator pinned to an older release does not just miss the new features. It can fail a correct profile and pass one that stopped being valid months ago, and both results look like any other report.&lt;/p&gt;

&lt;p&gt;We learned this the hard way. Earlier this month I wrote up how six profile builders inside our own product were still emitting the January shape in September, and how our tests agreed with them the whole time. This post is the outward-facing version of the same lesson: how to tell whether the tool you rely on is current, and how to check it without taking anyone's word for it, ours included.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each tool says it validates against
&lt;/h2&gt;

&lt;p&gt;I went through the public UCP validators and wrote down the spec version each one states on its own site. As of September 24, 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Spec version stated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UCPChecker&lt;/td&gt;
&lt;td&gt;every published version, including &lt;code&gt;2026-08-25&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UCPtools&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-08-25&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rankly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-01-23&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aicommerceaudit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-04-08&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spck.dev&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-04-08&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UCPhub&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-04-08&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nextwaves UCP Tester&lt;/td&gt;
&lt;td&gt;not stated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few caveats before anyone reads this as a leaderboard. These are claims from each tool's own pages, not results of a test I ran against each of them. Stating an older version is not the same as being wrong, since a tool can be deliberately pinned, and plenty of live profiles still declare &lt;code&gt;2026-04-08&lt;/code&gt;. And a stated version is not a guarantee either. We stated a version we did not fully implement for five months.&lt;/p&gt;

&lt;p&gt;What the table does tell you is where to look. If your profile declares &lt;code&gt;2026-08-25&lt;/code&gt; and your validator says &lt;code&gt;2026-04-08&lt;/code&gt;, some of what it reports is about a spec your profile is not using.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed between versions
&lt;/h2&gt;

&lt;p&gt;The changes that trip validators up are structural, so a validator on the wrong side of them gives confident wrong answers rather than vague ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;2026-04-08&lt;/code&gt; changed the shape of the profile.&lt;/strong&gt; Services went from an object with transports nested under their name to an array of entries with a flat &lt;code&gt;transport&lt;/code&gt; field. Capabilities went from an array of objects carrying their own &lt;code&gt;name&lt;/code&gt; to a map keyed by name. Payment handlers moved from root &lt;code&gt;$.payment&lt;/code&gt; into &lt;code&gt;$.ucp.payment_handlers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2026-01-11 / 2026-01-23&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev.ucp.shopping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://shop.example/ucp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;capabilities&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="c1"&gt;// 2026-04-08 and 2026-08-25&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev.ucp.shopping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transport&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://shop.example/ucp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;capabilities&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A validator built for January can read a current profile as malformed from the first key, and every later check that depends on finding the services is either skipped or fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;2026-08-25&lt;/code&gt; renamed &lt;code&gt;signing_keys&lt;/code&gt; to &lt;code&gt;keys&lt;/code&gt;&lt;/strong&gt;, and removed the old name entirely. A validator on &lt;code&gt;2026-04-08&lt;/code&gt; can look for &lt;code&gt;signing_keys&lt;/code&gt; in a current profile, not find it, and report that your signing keys are missing. It can also accept a profile that still uses &lt;code&gt;signing_keys&lt;/code&gt; while declaring &lt;code&gt;2026-08-25&lt;/code&gt;, which is exactly the profile it should be flagging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;2026-08-25&lt;/code&gt; also moved and renamed a lot of smaller things:&lt;/strong&gt; payment extensions from &lt;code&gt;dev.ucp.shopping.*&lt;/code&gt; to &lt;code&gt;dev.ucp.common.payment.*&lt;/code&gt;, the &lt;code&gt;allows_&lt;/code&gt; prefix dropped from fulfillment flags, buyer consent booleans replaced by a reverse-DNS map. It added new capabilities too, including location search and lookup, permalink and loyalty. An older validator will treat all of them as unknown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema URLs became version-scoped.&lt;/strong&gt; Unversioned &lt;code&gt;https://ucp.dev/schemas/...&lt;/code&gt; URLs return 404 today, and &lt;code&gt;https://ucp.dev/2026-08-25/...&lt;/code&gt; returns 200. A validator that does not fetch the schemas it is pointed at will never notice this. One that fetches the wrong ones will fail on a network error that has nothing to do with your profile.&lt;/p&gt;

&lt;p&gt;One warning if you plan to check these details yourself: read the schemas, not the release notes. The &lt;code&gt;2026-08-25&lt;/code&gt; notes get three field names wrong. They say &lt;code&gt;$requestConstraints&lt;/code&gt;, &lt;code&gt;ap2_mandates&lt;/code&gt; and &lt;code&gt;payment_terms&lt;/code&gt;, while the schemas define &lt;code&gt;request_constraints&lt;/code&gt;, &lt;code&gt;dev.ucp.common.payment.ap2_mandate&lt;/code&gt; and &lt;code&gt;dev.ucp.common.payment.terms&lt;/code&gt;. A validator written from the notes is wrong in exactly those places.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check your validator yourself
&lt;/h2&gt;

&lt;p&gt;You do not need to trust a comparison table, including the one above. Three tests take about twenty minutes and will tell you more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Build a fixture from the spec, not from anyone's sample.&lt;/strong&gt; Clone the spec repository at the release tag and take the business profile example from &lt;code&gt;docs/&lt;/code&gt;, cross-checked against &lt;code&gt;source/schemas/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--branch&lt;/span&gt; v2026-08-25 &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/Universal-Commerce-Protocol/ucp.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not use the &lt;code&gt;samples&lt;/code&gt; repository for this. It has no tags, and its reference server still serves a &lt;code&gt;2026-04-08&lt;/code&gt; profile. Run the fixture through your validator. A current validator should report zero errors on it. Anything it flags is either a real finding you can confirm in the schema, or a sign that the validator is modelling a different version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Run the &lt;code&gt;keys&lt;/code&gt; canary.&lt;/strong&gt; Take that same fixture and rename &lt;code&gt;keys&lt;/code&gt; to &lt;code&gt;signing_keys&lt;/code&gt;. Against &lt;code&gt;2026-08-25&lt;/code&gt;, this is now wrong, and a current validator should say so. Then put &lt;code&gt;keys&lt;/code&gt; back and remove it entirely. &lt;code&gt;keys&lt;/code&gt; is optional in the schema, so a validator that hard-errors on its absence is inventing a requirement. The two results together tell you whether the tool knows about the August rename and whether it knows what is actually required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Check what it calls required.&lt;/strong&gt; In a &lt;code&gt;/.well-known/ucp&lt;/code&gt; business profile, the required fields are &lt;code&gt;ucp&lt;/code&gt;, &lt;code&gt;ucp.version&lt;/code&gt;, &lt;code&gt;ucp.services&lt;/code&gt; and &lt;code&gt;ucp.payment_handlers&lt;/code&gt;. Capabilities are optional, and so are signing keys. If your validator errors on something outside that list, look the field up in the schema at the tag. We shipped a hard error for a field called &lt;code&gt;supported_mechanisms&lt;/code&gt; that does not exist in any published version. Grepping &lt;code&gt;source/&lt;/code&gt; and &lt;code&gt;docs/&lt;/code&gt; at every tag returns nothing. Nobody noticed until we tested against the real schemas.&lt;/p&gt;

&lt;p&gt;If a tool passes all three, it is current, whatever its site says. If it fails one, you know which of its findings to discount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the question to ask
&lt;/h2&gt;

&lt;p&gt;Scores are the part everyone looks at, and they are the part that hides this problem best. A 62 and a 25 look like a difference in your profile. In our case they were a difference in our validator: our own site went from F/25 to D/62 without the profile changing, purely by removing false negatives that came from modelling the wrong version.&lt;/p&gt;

&lt;p&gt;So before you act on a report, whoever it came from, find out which spec it measured you against. If the answer is not written down anywhere, the fixture test above will tell you in a few minutes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;UCP is an open standard co-developed by Google and Shopify. UCPtools is an independent community tool and is not affiliated with either company. Every schema detail above was read from the published specification in the &lt;a href="https://github.com/Universal-Commerce-Protocol/ucp" rel="noopener noreferrer"&gt;Universal-Commerce-Protocol/ucp&lt;/a&gt; repository at the release tags, not from the release notes. Tool versions were taken from each tool's public site on September 24, 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can check a profile against &lt;code&gt;v2026-08-25&lt;/code&gt; with the free validator at &lt;a href="https://ucptools.dev" rel="noopener noreferrer"&gt;ucptools.dev&lt;/a&gt;, or run it in CI with the &lt;a href="https://github.com/Nolpak14/ucp-validate-action" rel="noopener noreferrer"&gt;UCP validate GitHub Action&lt;/a&gt;. The story of how our own builders drifted is in &lt;a href="https://dev.to/ucptools/five-months-six-profile-builders-one-silent-spec-drift-nbl"&gt;Five months, six profile builders, one silent spec drift&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>testing</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Five months, six profile builders, one silent spec drift</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Wed, 09 Sep 2026 08:27:56 +0000</pubDate>
      <link>https://dev.to/ucptools/five-months-six-profile-builders-one-silent-spec-drift-nbl</link>
      <guid>https://dev.to/ucptools/five-months-six-profile-builders-one-silent-spec-drift-nbl</guid>
      <description>&lt;p&gt;We build tooling for UCP, the Universal Commerce Protocol: validators, a generator, a WordPress plugin, an npm package, a GitHub Action. Staying current with the spec is the whole product.&lt;/p&gt;

&lt;p&gt;On August 31 I ran our own AI Agent Test against &lt;code&gt;ucptools.dev&lt;/code&gt; and it came back an F, 25 out of 100. Transport 0/6. Schema Loaded 0/12. Endpoint 0/12. Signing Keys 0/7.&lt;/p&gt;

&lt;p&gt;None of it was broken. Every one of those zeros was a false negative, produced by our validator disagreeing with our generator while both modelled a version of UCP that had stopped existing in April.&lt;/p&gt;

&lt;p&gt;The audit that followed took the rest of the day. If you build anything that reads or writes a versioned spec, the mechanism that hid this is worth recognising.&lt;/p&gt;

&lt;h2&gt;
  
  
  The drift was older than the release that exposed it
&lt;/h2&gt;

&lt;p&gt;UCP shipped &lt;code&gt;v2026-08-25&lt;/code&gt; in late August, which is where the investigation started. The gap turned out to be five months wide rather than six days.&lt;/p&gt;

&lt;p&gt;The profile shape changed in &lt;code&gt;2026-04-08&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2026-01-11: services keyed by namespace, transports nested under their name,&lt;/span&gt;
&lt;span class="c1"&gt;// capabilities an array of objects carrying their own `name`.&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev.ucp.shopping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://shop.example/ucp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;capabilities&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="c1"&gt;// 2026-04-08 and later: services are arrays, transport is a field,&lt;/span&gt;
&lt;span class="c1"&gt;// endpoint is flat, capabilities are a map keyed by name.&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev.ucp.shopping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transport&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://shop.example/ucp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;capabilities&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Payment handlers moved from root &lt;code&gt;$.payment&lt;/code&gt; into &lt;code&gt;$.ucp.payment_handlers&lt;/code&gt; in the same release, and &lt;code&gt;signing_keys&lt;/code&gt; became &lt;code&gt;keys&lt;/code&gt; in &lt;code&gt;2026-08-25&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We were still emitting the January shape in September.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six builders, and the change had to be made in all of them
&lt;/h2&gt;

&lt;p&gt;The audit was scoped to one defect in the validator. It found the same stale shape in six independent places:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;What it was doing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /v1/profiles/generate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2026-01-11&lt;/code&gt; shape, declaring &lt;code&gt;"version": "2026-04-08"&lt;/code&gt;, schema URLs returning 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web &lt;code&gt;GeneratorTool&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;a second builder, &lt;code&gt;2026-01-11&lt;/code&gt; hardcoded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard hosting-setup flow&lt;/td&gt;
&lt;td&gt;a third builder, &lt;code&gt;2026-01-11&lt;/code&gt; hardcoded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce guide snippet&lt;/td&gt;
&lt;td&gt;a fourth, copy-pasted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Our own &lt;code&gt;/.well-known/ucp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;declared &lt;code&gt;2026-01-14&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /generate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;stamped &lt;code&gt;new Date()&lt;/code&gt; as the version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There was more outside the table: a WordPress plugin live on wp.org declaring &lt;code&gt;2026-01-15&lt;/code&gt; and failing our own validator with nine errors, an npm package five weeks behind its own repo, and a Shopify app depending on that package at a version from March.&lt;/p&gt;

&lt;p&gt;The table is the root cause. A spec change had to be made in six places, so it was made in zero. Each builder was written for a different surface at a different time, each looked reasonable on its own, and no single change ever felt like the migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the tests were green
&lt;/h2&gt;

&lt;p&gt;Three things kept this invisible, and all three are worth checking for in your own repo.&lt;/p&gt;

&lt;p&gt;The first was a fixture that agreed with the code. We had a file called &lt;code&gt;official-sample-profile-2026-04-08.json&lt;/code&gt;, with a header comment citing the upstream samples repository. It was not official. It was a &lt;code&gt;2026-01-11&lt;/code&gt;-shaped profile with the version strings swapped, and it passed because it agreed with the validator while both disagreed with the spec. A fixture built from your own output records your behaviour instead of testing it.&lt;/p&gt;

&lt;p&gt;The second was tolerance. Our normalize helpers already handled the current shape, under names like "Shopify style" - something met in the wild and accommodated as a vendor variant rather than recognised as the spec. Because the code could read correct profiles, nothing ever failed loudly enough to make anyone ask why two shapes existed.&lt;/p&gt;

&lt;p&gt;The third was short-circuiting. Our pipeline skips rules and network validation whenever structural validation reports an error. Two of the validator's three defects were errors, so they did more than add noise: they suppressed every real check behind them. One false error, and no analysis at all. If your validation pipeline has stages, find out which failures silence the later ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually caught it
&lt;/h2&gt;

&lt;p&gt;A fixture built from the published spec repository at the tag, cross-checked against &lt;code&gt;source/schemas/&lt;/code&gt;, rather than from our own samples or from the release notes.&lt;/p&gt;

&lt;p&gt;The method turned out to matter more than I expected. Across this work, suspicions taken from the changelog were overturned five times by reading the published schemas or exercising the running code, and four of the six items on my original checklist did not survive contact with the real fixture. Two of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We hard-errored when identity linking omitted &lt;code&gt;config.supported_mechanisms&lt;/code&gt;. Grepping &lt;code&gt;source/&lt;/code&gt; and &lt;code&gt;docs/&lt;/code&gt; at all three published tags returns zero hits for that field. We had invented it.&lt;/li&gt;
&lt;li&gt;We warned that &lt;code&gt;EdDSA&lt;/code&gt; was an unsupported signing algorithm. The spec lists it as well-known, recommends Ed25519 for Web Bot Auth interop, and says verifiers MUST tolerate algorithms they do not recognise. We replaced the closed-set check with a rule the spec does state as a MUST, that &lt;code&gt;alg&lt;/code&gt; must agree with a well-known &lt;code&gt;crv&lt;/code&gt;, so a real check took the place of the wrong one instead of leaving a gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grep could not have found the worst cases either. Three surfaces declared version strings that are not UCP releases at all: &lt;code&gt;2026-01-14&lt;/code&gt;, &lt;code&gt;2026-01-15&lt;/code&gt;, and a generator stamping today's date. The real releases are &lt;code&gt;2026-01-11&lt;/code&gt;, &lt;code&gt;2026-01-23&lt;/code&gt;, &lt;code&gt;2026-04-08&lt;/code&gt; and &lt;code&gt;2026-08-25&lt;/code&gt;, so a search for known versions matches none of those three. Reading the files and validating what they produced did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: one assembler, and versions as data
&lt;/h2&gt;

&lt;p&gt;All six builders now call one pure, isomorphic module, and it is the only thing that knows how a profile is laid out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assembleProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProfileParts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;UcpProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ucpVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ucpVersion&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;CURRENT_UCP_VERSION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ucpProfileDefaultsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ucpVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... shape services, capabilities and payment handlers per `defaults.shape`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The per-version facts sit in a data table rather than in branches scattered through the builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-01-11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;legacy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;keysField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signing_keys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;paymentHandlersAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root.payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* the unversioned URLs that tag really specified */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-04-08&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;keysField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signing_keys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;paymentHandlersAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ucp.payment_handlers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* version-scoped paths */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two decisions there are load-bearing. The generator is version-aware rather than version-bumped: ask for &lt;code&gt;2026-01-11&lt;/code&gt; and you get the legacy shape, because &lt;code&gt;profile_schema.json&lt;/code&gt; at that tag really does define root &lt;code&gt;payment&lt;/code&gt;. The old output was correct for its own version. And the next spec release becomes an entry in a table instead of a rewrite.&lt;/p&gt;

&lt;p&gt;Re-versioning six builders in place would have produced the same output and set up a seventh divergence later. Collapsing them was the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: guards that fail the next time
&lt;/h2&gt;

&lt;p&gt;Self-validation on its own would not have been enough. The WooCommerce guide's stale snippet nearly passed our validator, because the validator and the content shared the same wrong model. So the guards look for disagreement with an external source, and for disagreement between our own surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;own-surfaces-validate.test.ts&lt;/code&gt; requires that what we publish passes what we sell. Our &lt;code&gt;/.well-known/ucp&lt;/code&gt; has to declare a real, current version and validate with zero errors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools-shape-alignment.test.ts&lt;/code&gt; runs identical input through every builder and requires one answer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;plugin-zip.test.ts&lt;/code&gt; checks that the plugin declares nothing it cannot honour.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm-publish-drift.yml&lt;/code&gt; fails CI when &lt;code&gt;package.json&lt;/code&gt; is ahead of the registry. It caught its own case on day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The outcome
&lt;/h2&gt;

&lt;p&gt;Our own AI Agent Test went from F/25 to D/62, entirely by removing false negatives. Every generated profile now points at schema URLs that resolve. We published npm 2.1.0 after five weeks of unpublished fixes, moved the Shopify app off its March build, and shipped version 1.2.0 of the WordPress plugin.&lt;/p&gt;

&lt;p&gt;We checked legacy profiles explicitly and they still validate clean. Merchants on the old shape were not asked to do anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to take away
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Count your builders before you plan a migration. If the same artifact is constructed in more than one place, the spec bump is a refactor, and the duplication is what will make you miss it.&lt;/li&gt;
&lt;li&gt;A fixture from your own output tests nothing. Build it from the published spec, at the tag, and record where it came from. Ours ships with a &lt;code&gt;PROVENANCE.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Audit artifacts rather than identifiers. A per-surface audit finds only the surfaces you point it at, and a grep for version strings finds only the versions you already know. The two worst offenders here declared versions that were never released.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this was hard once it was visible. It stayed invisible for five months because every individual piece looked fine and nothing in the system was ever required to compare them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;UCP is an open standard co-developed by Google and Shopify. UCPtools is an independent community tool and is not affiliated with either company. Every schema detail above was read from the published specification in the &lt;a href="https://github.com/Universal-Commerce-Protocol/ucp" rel="noopener noreferrer"&gt;Universal-Commerce-Protocol/ucp&lt;/a&gt; repository, not from the release notes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can check a profile against &lt;code&gt;v2026-08-25&lt;/code&gt; with the free validator at &lt;a href="https://ucptools.dev" rel="noopener noreferrer"&gt;ucptools.dev&lt;/a&gt;, or run it in CI with the &lt;a href="https://github.com/Nolpak14/ucp-validate-action" rel="noopener noreferrer"&gt;UCP validate GitHub Action&lt;/a&gt;. If you are migrating from an older version, the &lt;a href="https://ucptools.dev/blog/ucp-2026-08-25-silent-breaking-changes" rel="noopener noreferrer"&gt;silent breaking changes in v2026-08-25&lt;/a&gt; is the companion piece to this one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>api</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>UCP v2026-08-25 Breaks Quietly: 4 Changes That Still Pass Validation</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Sat, 29 Aug 2026 12:58:57 +0000</pubDate>
      <link>https://dev.to/ucptools/ucp-v2026-08-25-breaks-quietly-4-changes-that-still-pass-validation-537p</link>
      <guid>https://dev.to/ucptools/ucp-v2026-08-25-breaks-quietly-4-changes-that-still-pass-validation-537p</guid>
      <description>&lt;p&gt;On August 25, 2026, the Universal Commerce Protocol shipped &lt;code&gt;v2026-08-25&lt;/code&gt;. It is the first&lt;br&gt;
new spec version in four and a half months, and it is a big one: a multi-vertical refactor&lt;br&gt;
laying groundwork for Food and Lodging, grocery and location capabilities, 3D Secure via a&lt;br&gt;
new Actions primitive, split payments and installment terms, a loyalty extension, and Web&lt;br&gt;
Bot Auth interop.&lt;/p&gt;

&lt;p&gt;The release notes carry a clearly labelled "Breaking Changes" section. Good.&lt;/p&gt;

&lt;p&gt;The problem is that the most dangerous changes in it are not the ones that break loudly.&lt;br&gt;
They are the ones where your profile still parses, still validates against a naive checker,&lt;br&gt;
and agents quietly stop working.&lt;/p&gt;

&lt;p&gt;I know, because our own validator got this exactly wrong. More on that below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this release breaks quietly
&lt;/h2&gt;

&lt;p&gt;Look at &lt;code&gt;source/schemas/profile.json&lt;/code&gt; at tag &lt;code&gt;v2026-08-25&lt;/code&gt;. The base definition ends like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ucp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ucp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ucp.json#/$defs/base"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/$defs/jwk_public_key"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things matter here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;required&lt;/code&gt; is &lt;code&gt;["ucp"]&lt;/code&gt; and nothing else. And &lt;code&gt;additionalProperties&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That combination means a profile carrying removed fields is &lt;strong&gt;not invalid&lt;/strong&gt;. It parses. It&lt;br&gt;
validates. It just contains members that nothing reads any more. When a spec renames a field&lt;br&gt;
under &lt;code&gt;additionalProperties: true&lt;/code&gt;, the old name does not become an error. It becomes&lt;br&gt;
decoration.&lt;/p&gt;

&lt;p&gt;That is the failure mode to hunt for in this release. Not "my profile is rejected" but "my&lt;br&gt;
profile is accepted and ignored".&lt;/p&gt;
&lt;h2&gt;
  
  
  Silent breaker 1: &lt;code&gt;signing_keys&lt;/code&gt; became &lt;code&gt;keys&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the big one, and it is the cleanest example of the pattern.&lt;/p&gt;

&lt;p&gt;Before &lt;code&gt;v2026-08-25&lt;/code&gt;, a profile published its signing keys in a root-level &lt;code&gt;signing_keys&lt;/code&gt;&lt;br&gt;
array. As of this release, &lt;code&gt;signing_keys&lt;/code&gt; is &lt;strong&gt;gone from &lt;code&gt;profile.json&lt;/code&gt; entirely&lt;/strong&gt; and &lt;code&gt;keys&lt;/code&gt;&lt;br&gt;
is the sole canonical field. The schema is blunt about it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a profile publishes signing keys, they MUST appear here; this is where every UCP&lt;br&gt;
verifier reads them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before - v2026-04-08&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ucp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-08"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signing_keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"key-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"crv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P-256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// After - v2026-08-25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ucp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"key-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"crv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P-256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The array contents do not change. It is the same JWK Set under a different name.&lt;/p&gt;

&lt;p&gt;Now put that next to &lt;code&gt;additionalProperties: true&lt;/code&gt;. If you bump your &lt;code&gt;version&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;2026-08-25&lt;/code&gt; and leave &lt;code&gt;signing_keys&lt;/code&gt; where it is, your profile is still schema-valid.&lt;br&gt;
Nothing rejects it. But no verifier looks at &lt;code&gt;signing_keys&lt;/code&gt; any more, so signature&lt;br&gt;
verification fails against a profile in which nothing looks wrong. You will be debugging&lt;br&gt;
your signing code, not your field names.&lt;/p&gt;

&lt;p&gt;The fix is a rename. The hard part is knowing you need it.&lt;/p&gt;
&lt;h2&gt;
  
  
  We got this wrong in the opposite direction
&lt;/h2&gt;

&lt;p&gt;Worth being straight about this, because it is the same trap from the other side.&lt;/p&gt;

&lt;p&gt;Our validator hard-required &lt;code&gt;signing_keys&lt;/code&gt; and had no concept of &lt;code&gt;keys&lt;/code&gt; at all. So when the&lt;br&gt;
spec moved, we started emitting a hard error on profiles that were &lt;em&gt;correctly&lt;/em&gt; migrated. A&lt;br&gt;
merchant who did the right thing got told their profile was broken.&lt;/p&gt;

&lt;p&gt;It got worse. Our pipeline skips rules and network validation whenever structural validation&lt;br&gt;
reports an error. So the bogus error did not just add noise, it suppressed every real check&lt;br&gt;
behind it. One false error, and no actual analysis.&lt;/p&gt;

&lt;p&gt;We shipped the fix on August 29. Both spellings are now accepted, &lt;code&gt;keys&lt;/code&gt; is treated as&lt;br&gt;
canonical from &lt;code&gt;2026-08-25&lt;/code&gt;, and a leftover &lt;code&gt;signing_keys&lt;/code&gt; on a new-version profile raises a&lt;br&gt;
warning that tells you it will be ignored rather than an error claiming it is invalid.&lt;/p&gt;

&lt;p&gt;The lesson generalises past our bug: &lt;strong&gt;a validator that hard-codes a field name is a&lt;br&gt;
validator that will eventually fail correct input.&lt;/strong&gt; If you have written any internal UCP&lt;br&gt;
checking, go look at how it resolves keys today.&lt;/p&gt;
&lt;h2&gt;
  
  
  Silent breaker 2: fulfillment config lost its &lt;code&gt;allows_&lt;/code&gt; prefix and changed shape
&lt;/h2&gt;

&lt;p&gt;The file was renamed from &lt;code&gt;merchant_fulfillment_config.json&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;business_fulfillment_config.json&lt;/code&gt;, and two properties were renamed. But this is not just a&lt;br&gt;
rename, and that is what makes it dangerous. &lt;code&gt;multi_destination&lt;/code&gt; also changed type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before - v2026-04-08: a map of method -&amp;gt; boolean&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"allows_multi_destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shipping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pickup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// After - v2026-08-25: an array of objects, listing = permitting&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"multi_destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shipping"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now trace what happens if you skip this migration. The old schema set&lt;br&gt;
&lt;code&gt;additionalProperties: false&lt;/code&gt;, so a stray key there used to be a hard error. The new&lt;br&gt;
&lt;code&gt;business_fulfillment_config&lt;/code&gt; does not, so &lt;code&gt;allows_multi_destination&lt;/code&gt; is now simply an&lt;br&gt;
unrecognised member that nothing reads. And the new field's own rule is explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Listing a method permits it; an omitted method does not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So your untouched config does not fail. It resolves to an empty permission set. You silently&lt;br&gt;
stop allowing split shipping across addresses, and nothing anywhere says so. That is a&lt;br&gt;
capability you believe you advertise quietly turning off.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;method_combinations&lt;/code&gt; kept its array-of-arrays shape, but its &lt;code&gt;enum&lt;/code&gt; restriction to&lt;br&gt;
&lt;code&gt;["shipping", "pickup"]&lt;/code&gt; was removed in favour of an open vocabulary with those as&lt;br&gt;
well-known values. That one is a loosening, so it will not break you.&lt;/p&gt;

&lt;p&gt;While you are in these files: &lt;code&gt;fulfillment_option.description&lt;/code&gt; was upgraded from a flat&lt;br&gt;
string to a structured object (it now &lt;code&gt;$ref&lt;/code&gt;s &lt;code&gt;common/types/description.json&lt;/code&gt;). That one&lt;br&gt;
does change type on a required-ish field, so it is more likely to fail loudly, which&lt;br&gt;
honestly makes it the safer of the two.&lt;/p&gt;
&lt;h2&gt;
  
  
  Silent breaker 3: buyer consent went from booleans to a reverse-DNS map
&lt;/h2&gt;

&lt;p&gt;This one changes shape rather than just names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before - v2026-04-08: four fixed booleans&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"analytics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"marketing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sale_of_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;v2026-08-25&lt;/code&gt;, &lt;code&gt;consent&lt;/code&gt; is a dynamic map keyed by reverse-DNS identifiers, where each&lt;br&gt;
purpose is an object requiring &lt;code&gt;granted&lt;/code&gt;, &lt;code&gt;source&lt;/code&gt;, and &lt;code&gt;description&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After - v2026-08-25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev.ucp.consent.marketing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"granted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Promotional communications across all channels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"segments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dev.ucp.consent.marketing.email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"granted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email marketing only"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;source&lt;/code&gt; field is the interesting addition: it distinguishes a business default&lt;br&gt;
(&lt;code&gt;"business"&lt;/code&gt;) from an explicit buyer decision captured by the platform (&lt;code&gt;"platform"&lt;/code&gt;). If&lt;br&gt;
you are handling consent for compliance reasons, that distinction is probably one you have&lt;br&gt;
been reconstructing by hand. Now it is in the protocol. Segments let you scope a decision to&lt;br&gt;
a channel without inventing your own convention, and UCP predefines&lt;br&gt;
&lt;code&gt;dev.ucp.consent.marketing.email&lt;/code&gt; and &lt;code&gt;.sms&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Silent breaker 4: payment extensions moved namespace
&lt;/h2&gt;

&lt;p&gt;Payment extensions migrated out of the shopping vertical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dev.ucp.shopping.split_payments&lt;/code&gt; becomes &lt;code&gt;dev.ucp.common.payment.split_payments&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the same move applies to &lt;code&gt;payment_terms&lt;/code&gt; and &lt;code&gt;ap2_mandates&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Related, and easy to miss: shared primitive schemas (&lt;code&gt;amount.json&lt;/code&gt;, &lt;code&gt;price.json&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;line_item.json&lt;/code&gt;) moved under &lt;code&gt;common/types/&lt;/code&gt;, which &lt;strong&gt;changes their &lt;code&gt;$id&lt;/code&gt; URLs&lt;/strong&gt;. If you&lt;br&gt;
pin, cache, or fetch those schemas by URL anywhere, those references are now stale.&lt;/p&gt;

&lt;p&gt;This is the change most likely to bite tooling rather than merchants. Anything that walks&lt;br&gt;
capability extension chains by prefix will read migrated extensions as orphaned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Rename &lt;code&gt;signing_keys&lt;/code&gt; to &lt;code&gt;keys&lt;/code&gt; at the profile root. Delete the old key. Do not keep both -
two sources of truth for signing keys drifting apart is precisely the failure this change
was made to eliminate.&lt;/li&gt;
&lt;li&gt;Move to &lt;code&gt;business_fulfillment_config.json&lt;/code&gt;, drop the &lt;code&gt;allows_&lt;/code&gt; prefix, and convert
&lt;code&gt;multi_destination&lt;/code&gt; from a method-keyed boolean map to an array of &lt;code&gt;{ "method": "..." }&lt;/code&gt;
objects. Do not just rename it - an unconverted map reads as "nothing permitted".&lt;/li&gt;
&lt;li&gt;Convert &lt;code&gt;fulfillment_option.description&lt;/code&gt; from a flat string to the structured
&lt;code&gt;common/types/description.json&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;Restructure buyer consent into the reverse-DNS map with &lt;code&gt;granted&lt;/code&gt; / &lt;code&gt;source&lt;/code&gt; /
&lt;code&gt;description&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Repoint payment extensions to &lt;code&gt;dev.ucp.common.payment.*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update any pinned &lt;code&gt;$id&lt;/code&gt; URLs for &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, and &lt;code&gt;line_item&lt;/code&gt; to &lt;code&gt;common/types/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bump &lt;code&gt;version&lt;/code&gt; to &lt;code&gt;2026-08-25&lt;/code&gt; &lt;strong&gt;last&lt;/strong&gt;, after the above. The version field is the part
agents read to decide how to interpret everything else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then re-validate. And validate with something that actually knows about &lt;code&gt;2026-08-25&lt;/code&gt; - a&lt;br&gt;
checker still targeting an older version will happily tell you a half-migrated profile is&lt;br&gt;
fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader point
&lt;/h2&gt;

&lt;p&gt;Date-based protocol versions make it easy to think of a spec bump as a number you increment.&lt;br&gt;
This release is a good argument against that. The changes that will cost you time are not in&lt;br&gt;
the "Breaking Changes" heading you read once. They are in the gap between "my JSON is valid"&lt;br&gt;
and "an agent can actually transact with me", and &lt;code&gt;additionalProperties: true&lt;/code&gt; is exactly&lt;br&gt;
where that gap lives.&lt;/p&gt;

&lt;p&gt;If you maintain UCP tooling, the practical takeaway is narrower: stop hard-coding field&lt;br&gt;
names, and make your validator report &lt;em&gt;which&lt;/em&gt; layout it detected rather than silently&lt;br&gt;
assuming one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;UCP is an open standard co-developed by Google and Shopify. UCPtools is an independent&lt;br&gt;
community tool and is not affiliated with either company. Every schema detail above was read&lt;br&gt;
from the published specification at tag &lt;code&gt;v2026-08-25&lt;/code&gt; in the&lt;br&gt;
&lt;a href="https://github.com/Universal-Commerce-Protocol/ucp" rel="noopener noreferrer"&gt;Universal-Commerce-Protocol/ucp&lt;/a&gt;&lt;br&gt;
repository, not from the release notes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can check a profile against &lt;code&gt;v2026-08-25&lt;/code&gt; with the free validator at&lt;br&gt;
&lt;a href="https://ucptools.dev" rel="noopener noreferrer"&gt;ucptools.dev&lt;/a&gt;, or run it in CI with the&lt;br&gt;
&lt;a href="https://github.com/Nolpak14/ucp-validate-action" rel="noopener noreferrer"&gt;UCP validate GitHub Action&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ecommerce</category>
      <category>ai</category>
      <category>api</category>
    </item>
    <item>
      <title>Is Your UCP Validator Lying to You About Cart and Catalog?</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:06:16 +0000</pubDate>
      <link>https://dev.to/ucptools/is-your-ucp-validator-lying-to-you-about-cart-and-catalog-35i7</link>
      <guid>https://dev.to/ucptools/is-your-ucp-validator-lying-to-you-about-cart-and-catalog-35i7</guid>
      <description>&lt;h1&gt;
  
  
  Is Your UCP Validator Lying to You About Cart and Catalog?
&lt;/h1&gt;

&lt;p&gt;You ran validation. Your store got a "FAIL" on Cart and Catalog capabilities. The report says your catalog endpoint returns an invalid response shape. You spend an afternoon debugging endpoints that look fine when you test them manually.&lt;/p&gt;

&lt;p&gt;The problem isn't your store. It's your validator. Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The draft-spec trap nobody talks about
&lt;/h2&gt;

&lt;p&gt;The Universal Commerce Protocol isn't static. As of the April 2026 release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Identity Linking&lt;/strong&gt; is stable. The spec is finalized. Agents expect a specific response shape, and that shape won't change without a new protocol version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cart and Catalog&lt;/strong&gt; are draft. The endpoints exist and work. Agents interact with them in production. But the schema, response shapes, and exact field names are still evolving. The spec explicitly says they "MAY version independently when breaking changes are required."&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a validator doesn't distinguish between stable and draft capabilities, here's what happens:&lt;/p&gt;

&lt;p&gt;Your Catalog endpoint returns products correctly. An AI agent asks "show me hiking boots under $200" and your store responds with actual products. But the response includes an optional metadata field that the draft spec didn't mention six months ago, or it nests variant data differently than the current draft expects.&lt;/p&gt;

&lt;p&gt;A spec-unaware validator flags this mismatch as a failure. Hard stop. Red X. "Catalog validation failed."&lt;/p&gt;

&lt;p&gt;The agent, meanwhile, browses your catalog just fine. It reads the products that matter, ignores the extra fields, and builds a cart. Your store works in practice. But your validator says it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a hard-fail actually costs you
&lt;/h2&gt;

&lt;p&gt;False negatives aren't just annoying - they steer you toward the wrong work.&lt;/p&gt;

&lt;p&gt;When your validator says Cart validation failed, you investigate. You compare your endpoint response to the draft schema. You spend hours figuring out whether the mismatch is on your side or the spec's. You might even modify your endpoint to match a draft schema that changes next month.&lt;/p&gt;

&lt;p&gt;Meanwhile, the actual problems - signing keys that rotated, a CDN that changed how it serves &lt;code&gt;.well-known/ucp&lt;/code&gt;, payment handlers referencing an old gateway endpoint - go unnoticed. These are genuine, production-breaking failures that get buried under draft-spec noise.&lt;/p&gt;

&lt;p&gt;The validator that flags everything with equal severity trains you to ignore all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: draft-aware validation
&lt;/h2&gt;

&lt;p&gt;As of July 2026, UCPtools handles draft capabilities differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stable spec requirements&lt;/strong&gt;: checked as before. If your Cart endpoint returns a 4xx, if your Catalog doesn't serve JSON, if a required stable-spec field is missing - those are real failures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Draft-spec mismatches&lt;/strong&gt;: produce warnings, not failures. If your response shape doesn't match the current draft schema, you get a clear warning explaining what's different - not a red X that blocks everything else.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You still see the mismatch. You still know something changed. But you're not punished for a moving target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to check in your current validation workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Does your tool report spec status for each capability?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If every failure looks the same - same severity, same color, same urgency - you can't tell a broken endpoint from a draft-spec evolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Does a Cart/Catalog failure block your entire validation run?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A genuine &lt;code&gt;signing_keys&lt;/code&gt; failure and a draft-spec Catalog warning are not the same problem. If your tool treats them identically, you're spending time on the wrong one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. When was your tool last updated against the current UCP spec?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The April 2026 update was the largest spec release since January. If your validator hasn't been updated since then, it's validating against a version of the protocol that no longer reflects what agents expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spec is moving. Your validation should move with it.
&lt;/h2&gt;

&lt;p&gt;Google shipped the April 2026 update with Cart, Catalog, and Signals. Shopify Editions Spring '26 made UCP the default on every store. The protocol is in active development - and that's a good thing. It means agentic commerce is real enough that the spec keeps improving.&lt;/p&gt;

&lt;p&gt;But point-in-time validation tools that don't distinguish between draft and stable create more noise than signal. You end up debugging spec evolution instead of fixing what actually breaks agent interactions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Check if your validation handles draft specs correctly&lt;/strong&gt;: &lt;a href="https://ucptools.dev?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=202607" rel="noopener noreferrer"&gt;Validate your UCP profile&lt;/a&gt; - free, no account needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running validation across multiple domains?&lt;/strong&gt; &lt;a href="https://ucptools.dev/signup?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=202607" rel="noopener noreferrer"&gt;Start a free 7-day trial&lt;/a&gt; for monitoring and multi-domain dashboards. No credit card required.&lt;/p&gt;

</description>
      <category>ucp</category>
      <category>validation</category>
      <category>agenticcommerce</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ChatGPT Can Shop Now. Is Your Store Ready to Sell to It?</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Mon, 20 Jul 2026 10:05:06 +0000</pubDate>
      <link>https://dev.to/ucptools/chatgpt-can-shop-now-is-your-store-ready-to-sell-to-it-17o4</link>
      <guid>https://dev.to/ucptools/chatgpt-can-shop-now-is-your-store-ready-to-sell-to-it-17o4</guid>
      <description>&lt;h1&gt;
  
  
  ChatGPT Can Shop Now. Is Your Store Ready to Sell to It?
&lt;/h1&gt;

&lt;p&gt;OpenAI shipped the Agentic Commerce Protocol (ACP) and ChatGPT's shopping agent is live. Target, Sephora, Nordstrom, Best Buy, and Home Depot are already integrated. When a user asks ChatGPT to find them a product, those stores show up.&lt;/p&gt;

&lt;p&gt;If your store isn't ACP-visible, ChatGPT users can't find you. Period.&lt;/p&gt;

&lt;p&gt;Here's the thing most e-commerce developers don't realize: your store might already be ACP-compatible and you just haven't checked. If you're on Shopify with Stripe, HTTPS, and proper policy pages, you could be one product feed away from ChatGPT discoverability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ACP Actually Checks
&lt;/h2&gt;

&lt;p&gt;ACP is the open standard (Apache 2.0) that OpenAI and Stripe built for AI commerce. It's not proprietary and it's not Stripe-only - Adyen shipped "Adyen Agentic" and PayPal announced ACP support for 2026.&lt;/p&gt;

&lt;p&gt;The protocol checks four things on your store:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Platform&lt;/strong&gt; (25 pts) - Shopify/Etsy auto-eligible path, or custom integration needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment infrastructure&lt;/strong&gt; (30 pts) - Stripe is the easiest path (Shared Payment Token), but seller-backed instruments (gift cards, store credit) also work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy pages&lt;/strong&gt; (25 pts) - Terms, privacy, and return policy are required by spec. Agents need these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; (20 pts) - HTTPS with modern TLS. Hard requirement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You get a 0-100 score with letter grades (A through F) and finding codes for every failure - not "you failed," but exactly what to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACP vs UCP: Don't Confuse Them
&lt;/h2&gt;

&lt;p&gt;Two protocols, two AI agent ecosystems:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;ACP (OpenAI + Stripe)&lt;/th&gt;
&lt;th&gt;UCP (Google + Shopify)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ChatGPT&lt;/td&gt;
&lt;td&gt;Google AI Mode, Gemini, Copilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/.well-known/acp.json&lt;/code&gt; + product feed&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/.well-known/ucp&lt;/code&gt; manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stripe delegated tokens or merchant app&lt;/td&gt;
&lt;td&gt;Merchant-hosted endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Focus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Payment + discovery&lt;/td&gt;
&lt;td&gt;Full commerce (browse, cart, checkout)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Microsoft Copilot already consumes both. checkout.com data shows dual-protocol stores capture 40% more agentic traffic. The recommendation for 2026: implement both if you can. But start by checking what you already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The March 2026 Pivot (And Why ACP Matters More, Not Less)
&lt;/h2&gt;

&lt;p&gt;OpenAI deprioritized in-ChatGPT checkout in March 2026. Users browsed but didn't buy in-chat. Purchases moved to merchant storefronts.&lt;/p&gt;

&lt;p&gt;But here's what most people missed: the ACP spec had its &lt;em&gt;biggest release&lt;/em&gt; after that pivot. April 17, 2026 brought public product feeds, Cart capability, delegate authentication with 3DS2, and mandatory idempotency keys.&lt;/p&gt;

&lt;p&gt;The takeaway: ACP now governs &lt;em&gt;discovery&lt;/em&gt; inside ChatGPT. If your products aren't ACP-surfaced, ChatGPT users never see them. The purchase completes on your site, but the discovery happens inside the agent. And discovery requires ACP readiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;McKinsey: up to $1T in US agentic commerce by 2030&lt;/li&gt;
&lt;li&gt;Morgan Stanley: 126M AI shopping-agent users by 2030&lt;/li&gt;
&lt;li&gt;Shopify: 13x YoY growth in AI search orders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The retailers showing up today didn't wait. They integrated early. Every week your store isn't checked is a week ChatGPT users buy from someone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test It in 30 Seconds
&lt;/h2&gt;

&lt;p&gt;I built a free ACP Readiness Checker that scans your domain and gives you a 0-100 score across all four categories. No account required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ucptools.dev/acp-checker?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=acp-checker-promo-202607" rel="noopener noreferrer"&gt;Run the ACP check on your store →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you score A, you're ChatGPT-ready. If not, you get specific finding codes and fix recommendations for every failing category.&lt;/p&gt;

&lt;p&gt;Your products should show up wherever customers shop. In 2026, that includes ChatGPT.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Also published on &lt;a href="https://ucptools.dev/blog/acp-checker-chatgpt-readiness?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=202607" rel="noopener noreferrer"&gt;ucptools.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>acp</category>
      <category>agenticcommerce</category>
      <category>chatgpt</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Run UCP + ACP Validation in Your CI/CD Pipeline</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Fri, 10 Jul 2026 05:26:39 +0000</pubDate>
      <link>https://dev.to/ucptools/run-ucp-acp-validation-in-your-cicd-pipeline-590d</link>
      <guid>https://dev.to/ucptools/run-ucp-acp-validation-in-your-cicd-pipeline-590d</guid>
      <description>&lt;p&gt;You deploy a change to your storefront. The JSON still parses. The linter is happy. Tests pass.&lt;/p&gt;

&lt;p&gt;Two weeks later you notice AI agents have been silently skipping your store, because a capability schema endpoint started returning a 404 and nothing anywhere told you.&lt;/p&gt;

&lt;p&gt;This is the failure mode of agentic commerce readiness: it degrades quietly. There is no 500 page, no error rate spike, no angry customer email. Agents just move on to a merchant whose profile resolves.&lt;/p&gt;

&lt;p&gt;The fix is the same as for every other class of quality problem in software. Stop checking it by hand. Put it in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually breaks
&lt;/h2&gt;

&lt;p&gt;UCP profiles are not simple documents. They carry namespace bindings, HTTPS requirements, signing keys, and extension chains that reference capabilities elsewhere in the file. A profile can be perfectly valid JSON and still be broken in ways that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UCP_NS_ORIGIN_MISMATCH&lt;/code&gt; - your declared namespace does not match the origin serving it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UCP_ENDPOINT_NOT_HTTPS&lt;/code&gt; - agents require HTTPS, without exception&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UCP_MISSING_SIGNING_KEYS&lt;/code&gt; - required before order and fulfillment capabilities will be trusted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UCP_SCHEMA_FETCH_FAILED&lt;/code&gt; - a capability schema URL returns an error&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UCP_ORPHANED_EXTENSION&lt;/code&gt; - an extension points at a capability that no longer exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these survives &lt;code&gt;JSON.parse()&lt;/code&gt;. Every one of these makes you invisible to some agent. And the last two are exactly the kind of thing a refactor breaks without touching the profile file at all, because the breakage lives at the other end of a URL.&lt;/p&gt;

&lt;p&gt;That is the argument for CI. Not "validate once at launch" - validate on every push, against the environment you are about to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitHub Action
&lt;/h2&gt;

&lt;p&gt;If you are on GitHub, this is four lines of YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nolpak14/ucp-validate-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yourstore.com'&lt;/span&gt;
    &lt;span class="na"&gt;fail-on-grade&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;C'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action runs four-level validation (structural, rules, network, SDK compliance) against the live domain and scores AI readiness. &lt;code&gt;fail-on-grade&lt;/code&gt; fails the build at that grade or worse; &lt;code&gt;fail-on-score&lt;/code&gt; does the same against a 0-100 number. Use whichever matches how your team talks about quality.&lt;/p&gt;

&lt;p&gt;It exposes &lt;code&gt;score&lt;/code&gt;, &lt;code&gt;grade&lt;/code&gt;, &lt;code&gt;ucp-found&lt;/code&gt;, &lt;code&gt;passed&lt;/code&gt;, and &lt;code&gt;result-json&lt;/code&gt; as outputs, so you can branch on the result rather than just pass or fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nolpak14/ucp-validate-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ucp&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yourstore.com'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "UCP score&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.ucp.outputs.score }}"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.ucp.outputs.ucp-found == 'false'&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "::warning::No UCP profile at /.well-known/ucp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On pull requests it posts a comment with the score, a category breakdown, and any validation issues with their error codes, updating in place rather than piling up duplicates. Set &lt;code&gt;comment: 'false'&lt;/code&gt; if you would rather have step summaries only.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern that actually pays: gate the deploy
&lt;/h2&gt;

&lt;p&gt;Validating on push is useful. Gating the deploy on it is what stops a broken profile reaching production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate-ucp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nolpak14/ucp-validate-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;staging.yourstore.com'&lt;/span&gt;
          &lt;span class="na"&gt;fail-on-grade&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;C'&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate-ucp&lt;/span&gt;   &lt;span class="c1"&gt;# deploy only runs if validation passed&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Deploying to production..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at staging. If staging's profile does not clear your bar, the &lt;code&gt;deploy&lt;/code&gt; job never runs. You catch the regression before customers see it, and before agents quietly reroute around you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not on GitHub? Use the CLI
&lt;/h2&gt;

&lt;p&gt;The validator ships as an npm package, so it drops into any runner with Node.js 20+. It exits non-zero when validation fails, which is all a CI system needs.&lt;/p&gt;

&lt;p&gt;GitLab CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ucp-validation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:20-alpine&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npx -p @ucptools/validator ucp-validate validate --remote yourstore.com&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == "main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CircleCI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ucp-validation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cimg/node:20.11&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate UCP profile&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx -p @ucptools/validator ucp-validate validate --remote yourstore.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or wire it into your test suite so it runs wherever your tests already run - Jenkins, Bitbucket Pipelines, a Makefile, a pre-commit hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @ucptools/validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:ucp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ucp-validate validate --remote yourstore.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also validate the file before it ever ships, which is faster and catches structural errors without a network round trip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-p&lt;/span&gt; @ucptools/validator ucp-validate validate &lt;span class="nt"&gt;--file&lt;/span&gt; ./public/.well-known/ucp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The other protocol: ACP readiness
&lt;/h2&gt;

&lt;p&gt;UCP is not the only spec agents care about. The Agentic Commerce Protocol - OpenAI and Stripe's standard, the one behind checkout inside ChatGPT - has its own readiness surface: platform detection, payment integration, policy endpoints, security posture.&lt;/p&gt;

&lt;p&gt;Worth being precise about how this runs, because it is different: &lt;strong&gt;the GitHub Action and the CLI check UCP.&lt;/strong&gt; ACP readiness is a public REST endpoint. No key, no install, so it works in any CI that has &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sX&lt;/span&gt; POST https://ucptools.dev/api/acp-check &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"domain":"yourstore.com"}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; acp.json

&lt;span class="nv"&gt;SCORE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.score'&lt;/span&gt; acp.json&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GRADE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.grade'&lt;/span&gt; acp.json&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ACP readiness: &lt;/span&gt;&lt;span class="nv"&gt;$GRADE&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$SCORE&lt;/span&gt;&lt;span class="s2"&gt;/100)"&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCORE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 70 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.recommendations[]? | "[\(.priority)] \(.title) - \(.action)"'&lt;/span&gt; acp.json
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One trap worth knowing: the endpoint answers &lt;code&gt;200 OK&lt;/code&gt; even when a domain fails, with &lt;code&gt;"ok": false&lt;/code&gt; in the body. A reflexive &lt;code&gt;curl --fail&lt;/code&gt; gate would pass every grade, including an F. Read &lt;code&gt;.score&lt;/code&gt; or &lt;code&gt;.grade&lt;/code&gt; from the response, not the HTTP status.&lt;/p&gt;

&lt;p&gt;The response also carries &lt;code&gt;score_breakdown&lt;/code&gt; across platform, payment, policies, and security, plus &lt;code&gt;recommendations&lt;/code&gt; as structured objects with &lt;code&gt;priority&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;action&lt;/code&gt; - which is why the snippet above prints something a developer can act on rather than a wall of JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to set the bar
&lt;/h2&gt;

&lt;p&gt;Resist the urge to start at &lt;code&gt;fail-on-grade: 'A'&lt;/code&gt;. You will spend a week fighting your own pipeline and then disable the check, which is worse than never adding it.&lt;/p&gt;

&lt;p&gt;Start where you are. Run the validator once, take the grade it gives you, and set the threshold one notch below. Now the build fails only on regression, which is the thing you actually care about. Ratchet the bar upward as you fix the backlog. A gate that fires on real regressions and stays quiet otherwise is a gate people keep.&lt;/p&gt;

&lt;p&gt;For ACP, 70 is a reasonable opening threshold. For UCP, &lt;code&gt;fail-on-grade: 'C'&lt;/code&gt; catches the profile-level breakage without blocking on Schema.org polish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add the action, or the &lt;code&gt;npx&lt;/code&gt; one-liner, to a workflow you already run&lt;/li&gt;
&lt;li&gt;Point it at staging, not production&lt;/li&gt;
&lt;li&gt;Set a threshold one notch below your current grade&lt;/li&gt;
&lt;li&gt;Add the ACP curl step if you care about ChatGPT checkout&lt;/li&gt;
&lt;li&gt;Make the deploy job &lt;code&gt;needs:&lt;/code&gt; the validation job&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Copy-paste recipes for GitHub Actions, GitLab CI, CircleCI, npx, and the ACP endpoint live at &lt;a href="https://ucptools.dev/ci" rel="noopener noreferrer"&gt;ucptools.dev/ci&lt;/a&gt;. If you would rather call validation from your own code than from a CI step, the &lt;a href="https://ucptools.dev/api-docs" rel="noopener noreferrer"&gt;API and npm reference&lt;/a&gt; covers the public REST endpoints and the typed package.&lt;/p&gt;

&lt;p&gt;The point is not the score. The point is that a profile which silently stopped resolving should break your build, not your revenue.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;UCPtools is an independent community tool. UCP is an open standard by Google and Shopify; ACP is an open standard by OpenAI and Stripe. We are not affiliated with any of them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>ecommerce</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Agentic Commerce Stack Is Complete - Here's Your July 2026 Implementation Checklist</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Mon, 06 Jul 2026 10:07:25 +0000</pubDate>
      <link>https://dev.to/ucptools/the-agentic-commerce-stack-is-complete-heres-your-july-2026-implementation-checklist-1f3l</link>
      <guid>https://dev.to/ucptools/the-agentic-commerce-stack-is-complete-heres-your-july-2026-implementation-checklist-1f3l</guid>
      <description>&lt;h1&gt;
  
  
  The Agentic Commerce Stack Is Complete - Here's Your July 2026 Implementation Checklist
&lt;/h1&gt;

&lt;p&gt;Three infrastructure changes in the last 30 days turned agentic commerce from a spec to a live channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mastercard backed UCP.&lt;/strong&gt; Their Agent Pay network now runs on Google's Universal Commerce Protocol. The payment layer converged on the discovery layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify shipped Agentic Storefronts in Summer '26 Editions.&lt;/strong&gt; Every Shopify merchant now has an AI shopping channel, active by default, manageable from the admin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google AI Mode is in production.&lt;/strong&gt; Gemini browses stores via UCP. ChatGPT shops through Shopify's agentic storefronts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stack is live: &lt;strong&gt;Discovery&lt;/strong&gt; (Google AI Mode, Gemini, ChatGPT) + &lt;strong&gt;Storefront&lt;/strong&gt; (Shopify, BigCommerce) + &lt;strong&gt;Payment&lt;/strong&gt; (Mastercard Agent Pay, Visa Intelligent Commerce, Adyen Agentic).&lt;/p&gt;

&lt;p&gt;If your store doesn't have a valid UCP profile, you are invisible to every layer. Not "less visible." Invisible. The agents read protocols, not websites.&lt;/p&gt;

&lt;p&gt;Here's the 5-item checklist to fix that. Each step takes 10-30 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Validate at All 4 Levels
&lt;/h2&gt;

&lt;p&gt;A structural JSON check tells you the manifest parses. It doesn't tell you whether an AI agent can actually shop.&lt;/p&gt;

&lt;p&gt;You need all four:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structural&lt;/strong&gt; - valid JSON, required fields, schema compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules&lt;/strong&gt; - capability consistency, namespace/origin match, business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt; - HTTPS on all endpoints, live response codes, reachability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK/Simulation&lt;/strong&gt; - end-to-end agent interaction (browse, cart, checkout, pay)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most free checkers stop at Level 1. The failures that make your store invisible live at Levels 3 and 4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ucptools.dev/validator?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=stack-checklist-202607" rel="noopener noreferrer"&gt;Run a free 4-level validation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Run the AI Agent Simulator
&lt;/h2&gt;

&lt;p&gt;Validation says your manifest is correct. Simulation says whether an agent can actually use it.&lt;/p&gt;

&lt;p&gt;The simulator sends real agent-style requests: browse catalog, add to cart, initiate checkout. Failures it catches that validators miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment handlers declared but unreachable (302 redirect, CORS block)&lt;/li&gt;
&lt;li&gt;Identity Linking endpoints that return 200 but fail cross-domain&lt;/li&gt;
&lt;li&gt;Cart where add works but remove doesn't&lt;/li&gt;
&lt;li&gt;Signing keys present but cryptographically invalid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://ucptools.dev/tools/simulator?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=stack-checklist-202607" rel="noopener noreferrer"&gt;Run the AI agent simulator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fix the Top 3 Failures
&lt;/h2&gt;

&lt;p&gt;Production scan data shows three issues cause most agent-visible failures:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing &lt;code&gt;signing_keys&lt;/code&gt;&lt;/strong&gt; - Without them, agents can't verify your manifest. They walk. This is the single most common failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Namespace/origin mismatch&lt;/strong&gt; - Your manifest says &lt;code&gt;origin: "store.example.com"&lt;/code&gt; but the agent requested from &lt;code&gt;www.store.example.com&lt;/code&gt;. The agent sees a contradiction and aborts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broken &lt;code&gt;payment_handlers&lt;/code&gt;&lt;/strong&gt; - The manifest declares checkout but doesn't tell agents what payment methods exist, or lists handlers that don't work. Cart fills, purchase fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Set Up Monitoring
&lt;/h2&gt;

&lt;p&gt;Your UCP profile will break. Not because you made a mistake - because infrastructure changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS certs rotate and the new CA isn't trusted&lt;/li&gt;
&lt;li&gt;Your platform ships an update that changes the manifest shape&lt;/li&gt;
&lt;li&gt;Payment gateway config drifts from what the manifest declares&lt;/li&gt;
&lt;li&gt;A caching plugin starts serving &lt;code&gt;/.well-known/ucp&lt;/code&gt; as &lt;code&gt;text/html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When this happens, you won't get an error. You'll just stop appearing in AI shopping results. Silent, invisible failure.&lt;/p&gt;

&lt;p&gt;Monitoring re-validates on a schedule, compares to baseline, and alerts on regression. Same principle as uptime monitoring - but for AI agent discoverability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ucptools.dev/tools/monitoring?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=stack-checklist-202607" rel="noopener noreferrer"&gt;Set up monitoring&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Test All Three Layers
&lt;/h2&gt;

&lt;p&gt;The stack works as a chain. Break any link and it fails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt; - Can Google AI Mode find your store? Search your products in AI Mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storefront&lt;/strong&gt; - Is your Shopify Agentic Storefront active? Check admin under Sales Channels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment&lt;/strong&gt; - Can Mastercard Agent Pay process a payment through your store? Test the handler endpoints.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Shopify reported 13x year-over-year growth in AI search orders &lt;em&gt;before&lt;/em&gt; Agentic Storefronts shipped. Cloudflare reports bots have surpassed human traffic. McKinsey projects up to $1T in US agentic commerce by 2030.&lt;/p&gt;

&lt;p&gt;The protocol war is effectively settled. UCP has the discovery layer, the storefront layer, and now the payment layer. Implementing it isn't a competitive advantage - it's table stakes.&lt;/p&gt;

&lt;p&gt;Five steps. One afternoon. The difference between being visible to the agentic commerce stack and being invisible to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ucptools.dev?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=stack-checklist-202607" rel="noopener noreferrer"&gt;Start your free validation&lt;/a&gt; - no account required.&lt;/p&gt;

</description>
      <category>ucp</category>
      <category>ecommerce</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building a Dual-Protocol Checkout with Agorio SDK: Support UCP and ACP in Your AI Commerce Agent</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:53:12 +0000</pubDate>
      <link>https://dev.to/ucptools/building-a-dual-protocol-checkout-with-agorio-sdk-support-ucp-and-acp-in-your-ai-commerce-agent-3ob7</link>
      <guid>https://dev.to/ucptools/building-a-dual-protocol-checkout-with-agorio-sdk-support-ucp-and-acp-in-your-ai-commerce-agent-3ob7</guid>
      <description>&lt;h1&gt;
  
  
  Building a Dual-Protocol Checkout with Agorio SDK: Support UCP and ACP in Your AI Commerce Agent
&lt;/h1&gt;

&lt;p&gt;The AI commerce landscape is rapidly standardizing around two open protocols: &lt;strong&gt;Google's Universal Commerce Protocol (UCP)&lt;/strong&gt; and &lt;strong&gt;OpenAI's Agentic Commerce Protocol (ACP)&lt;/strong&gt;. By holiday 2026, Visa and Mastercard will enable all US cardholders for agent transactions, while Shopify's 4.8M merchants are discoverable via UCP and Stripe's 1.5M merchants can accept ACP payments with one line of code.&lt;/p&gt;

&lt;p&gt;For developers building AI shopping agents, this creates a critical challenge: &lt;strong&gt;should you implement UCP, ACP, or both?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this step-by-step tutorial, you'll learn how to build a dual-protocol checkout system using the &lt;a href="https://agorio.dev?utm_source=devto&amp;amp;utm_medium=tutorial&amp;amp;utm_campaign=202606" rel="noopener noreferrer"&gt;Agorio SDK&lt;/a&gt; — the only open-source toolkit that supports both UCP and ACP with auto-detection. We'll walk through setting up a TypeScript project, discovering merchants via UCP, managing checkout sessions via ACP, and assembling a full-featured shopping agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Dual-Protocol Support Matters
&lt;/h2&gt;

&lt;p&gt;UCP and ACP solve different problems:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Lead Developers&lt;/th&gt;
&lt;th&gt;Primary Use Case&lt;/th&gt;
&lt;th&gt;Discovery Method&lt;/th&gt;
&lt;th&gt;Payment Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google, Shopify, 25+ partners&lt;/td&gt;
&lt;td&gt;Full commerce lifecycle (discovery, cart, checkout, order tracking)&lt;/td&gt;
&lt;td&gt;Decentralized (&lt;code&gt;/.well-known/ucp&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Multiple handlers (Google Pay, Shop Pay, tokenization)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ACP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenAI, Stripe&lt;/td&gt;
&lt;td&gt;Checkout-focused, delegated payment&lt;/td&gt;
&lt;td&gt;Centralized (merchant application)&lt;/td&gt;
&lt;td&gt;Any PSP via delegated payment (Stripe first)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Merchants are adopting both. If your agent only supports one protocol, you'll miss transactions on the other. The smart solution: &lt;strong&gt;support both, but avoid writing twice the code&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Agorio Provides
&lt;/h2&gt;

&lt;p&gt;Agorio abstracts the protocol differences behind a unified API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;UcpClient&lt;/code&gt;&lt;/strong&gt; – Discovers merchants via &lt;code&gt;/.well-known/ucp&lt;/code&gt;, normalizes capability formats, handles REST/MCP/A2A transports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AcpClient&lt;/code&gt;&lt;/strong&gt; – Manages ACP checkout sessions (create, get, update, complete, cancel) with Bearer auth and request tracing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ShoppingAgent&lt;/code&gt;&lt;/strong&gt; – 17 built‑in tools that work across both protocols via automatic detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MockMerchant&lt;/code&gt;&lt;/strong&gt; – A full UCP‑compliant test server, plus ACP‑only and MCP‑only variants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM adapters&lt;/strong&gt; – Gemini, Claude, OpenAI, Ollama — swap without changing your agent logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Agorio, you write commerce logic once; the SDK adapts to the merchant's supported protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Project Setup
&lt;/h2&gt;

&lt;p&gt;Start a new TypeScript project and install Agorio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;typescript ts-node @types/node &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @agorio/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Discover a Merchant via UCP
&lt;/h2&gt;

&lt;p&gt;First, let's discover a merchant's capabilities using the &lt;code&gt;UcpClient&lt;/code&gt;. The client automatically fetches the &lt;code&gt;/.well-known/ucp&lt;/code&gt; manifest and normalizes the capability format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UcpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UcpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;discoverMerchant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Merchant &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; supports:`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Discovery failed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example: discover a UCP-enabled merchant&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;discoverMerchant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shop.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;result&lt;/code&gt; object contains normalized services, payment handlers, and transport bindings. You can now invoke any UCP capability (catalog, cart, checkout) using the client's REST or MCP transport.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Create an ACP Checkout Session
&lt;/h2&gt;

&lt;p&gt;For merchants that support ACP (like Stripe‑enabled stores), you'll manage checkout sessions via the &lt;code&gt;AcpClient&lt;/code&gt;. ACP uses delegated payment: the user's payment method stays with their PSP (Stripe, PayPal), and the agent acts as an operator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AcpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AcpClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.stripe.com/acp/v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Replace with merchant's ACP endpoint&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAcpCheckout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;acp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCheckout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// $29.99 in minor units (cents)&lt;/span&gt;
        &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;merchantDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shop.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ACP checkout session &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; created`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "not_ready_for_payment"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAcpCheckout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SB_1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AcpClient&lt;/code&gt; handles all required headers (Authorization, API‑Version, Idempotency‑Key, Request‑Id) and validates responses against the ACP schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Build a Dual-Protocol Shopping Agent
&lt;/h2&gt;

&lt;p&gt;Now combine both clients into a single &lt;code&gt;ShoppingAgent&lt;/code&gt;. The agent automatically detects whether a merchant speaks UCP or ACP and uses the appropriate client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ShoppingAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GeminiAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShoppingAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GeminiAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ShoppingAgent automatically instantiates UcpClient and AcpClient&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runShoppingTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// Natural language summary&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Order ID if purchase completed&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;totalTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// LLM token usage&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// This single command works for both UCP and ACP merchants:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runShoppingTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Go to shop.example.com and buy wireless headphones&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent's 17 built‑in tools handle the entire flow: merchant discovery, product search, adding to cart, submitting shipping and payment, and order tracking. You can extend with custom tools via plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Test with Mock Merchants
&lt;/h2&gt;

&lt;p&gt;Before hitting real merchants, test with Agorio's mock servers. &lt;code&gt;MockMerchant&lt;/code&gt; provides a full UCP‑compliant server; &lt;code&gt;MockAcpMerchant&lt;/code&gt; provides an ACP‑only server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MockMerchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MockAcpMerchant&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Start a UCP merchant&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ucpMerchant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockMerchant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ucpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`UCP mock merchant running at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ucpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Start an ACP merchant&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acpMerchant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockAcpMerchant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;acpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ACP mock merchant running at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;acpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test your agent against the mock&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Go to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ucpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and buy a laptop`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Order placed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Clean up&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ucpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;acpMerchant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both mocks simulate realistic latency, support configurable error rates for chaos testing, and provide structured product catalogs (10 products by default).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Deployment Considerations
&lt;/h2&gt;

&lt;p&gt;When moving to production, keep these points in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LLM cost control&lt;/strong&gt; – Set &lt;code&gt;maxIterations&lt;/code&gt; in &lt;code&gt;AgentOptions&lt;/code&gt; to limit agent loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; – Use the &lt;code&gt;onLog&lt;/code&gt; callback to stream logs to your monitoring system, or integrate with Agorio Cloud for full tracing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session storage&lt;/strong&gt; – Swap &lt;code&gt;MemorySessionStorage&lt;/code&gt; for &lt;code&gt;FileSessionStorage&lt;/code&gt; or &lt;code&gt;@agorio/session-redis&lt;/code&gt; in multi‑process deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; – Always pass &lt;code&gt;idempotencyKey&lt;/code&gt; on &lt;code&gt;completeCheckout&lt;/code&gt; to prevent duplicate charges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt; – If you handle EU users, enable the built‑in EU AI Act compliance export (&lt;code&gt;GET /api/compliance/export&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: Why Agorio is the Right Choice
&lt;/h2&gt;

&lt;p&gt;Building dual‑protocol AI commerce agents from scratch requires deep knowledge of both UCP and ACP specs, plus months of implementation work. Agorio gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol agnosticism&lt;/strong&gt; – Write once, run on UCP, ACP, or both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production‑ready tooling&lt;/strong&gt; – Mock merchants, LLM adapters, observability, CLI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise hardening&lt;/strong&gt; – RBAC, audit logs, compliance exports, security posture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SDK is open‑source (MIT), has zero runtime fees, and already powers agents handling real purchases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get started today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;code&gt;npm install @agorio/sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/Nolpak14/agorio?utm_source=devto&amp;amp;utm_medium=tutorial&amp;amp;utm_campaign=202606" rel="noopener noreferrer"&gt;github.com/Nolpak14/agorio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://agorio.dev/docs?utm_source=devto&amp;amp;utm_medium=tutorial&amp;amp;utm_campaign=202606" rel="noopener noreferrer"&gt;agorio.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playground&lt;/strong&gt;: &lt;a href="https://agorio.dev/playground?utm_source=devto&amp;amp;utm_medium=tutorial&amp;amp;utm_campaign=202606" rel="noopener noreferrer"&gt;agorio.dev/playground&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building AI commerce agents, join the 10+ GitHub stars and 46+ weekly npm downloaders who've chosen Agorio as their foundation. Ship your agent in 20 lines, not 20 weeks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>commerce</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>We Tracked Every AI Agent That Visited Our Site. 5 Different Agents Found Us This Month.</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Mon, 22 Jun 2026 10:04:50 +0000</pubDate>
      <link>https://dev.to/ucptools/we-tracked-every-ai-agent-that-visited-our-site-5-different-agents-found-us-this-month-544d</link>
      <guid>https://dev.to/ucptools/we-tracked-every-ai-agent-that-visited-our-site-5-different-agents-found-us-this-month-544d</guid>
      <description>&lt;h1&gt;
  
  
  We Tracked Every AI Agent That Visited Our Site. 5 Different Agents Found Us This Month.
&lt;/h1&gt;

&lt;p&gt;Three weeks ago, I noticed something strange in our referrer logs.&lt;/p&gt;

&lt;p&gt;A user from &lt;code&gt;chatgpt.com&lt;/code&gt; had landed on our validator page, browsed our documentation, and — crucially — triggered tool events. Not a human clicking around. An AI agent. ChatGPT had crawled our site, loaded our tool, and used it.&lt;/p&gt;

&lt;p&gt;That was one agent. One week. A curiosity.&lt;/p&gt;

&lt;p&gt;This week: &lt;strong&gt;five.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What showed up (and what they did)
&lt;/h2&gt;

&lt;p&gt;The full lineup of AI agents that visited ucptools.dev in the last month:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT (chatgpt.com)&lt;/strong&gt; — 4 visits this week alone. Most active of the bunch. Browsed documentation pages and the free UCP validator. Triggered actual tool events — someone asked ChatGPT about UCP validation, and it came to us to answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini (gemini.google.com)&lt;/strong&gt; — 1 visit this week. First time we've seen Gemini in our logs. Shorter session than ChatGPT, but it hit the same pages: validator, compare page, docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perplexity (perplexity.ai)&lt;/strong&gt; — First spotted in May. Here's the wild part: every Perplexity-referred session in May resulted in a tool user. 100% conversion rate. Someone asked Perplexity "how do I validate my UCP profile?" and Perplexity sent them straight to our tool, already knowing what to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude (claude.ai)&lt;/strong&gt; — Returned June 14 after a multi-week gap. Browsed our UCP protocol documentation and the agent simulator page. Not using tools yet — but it's building a map.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NotebookLM (notebooklm.google.com)&lt;/strong&gt; — Newcomer. Discovered us June 7. Google's research assistant found our site and ingested content. We don't know what question triggered it, but something in our content matched a user's research query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trend line: 1 → 1 → 5.&lt;/strong&gt; Not a spike. A ramp.&lt;/p&gt;




&lt;h2&gt;
  
  
  This isn't about SEO as you know it
&lt;/h2&gt;

&lt;p&gt;If you work in developer tools or e-commerce infrastructure, you've probably spent the last 6 months hearing about UCP, ACP, AI shopping agents, and "agentic commerce readiness." The conversation has been about protocols — can an AI agent parse your UCP manifest? Can it call your payment handlers? Can it add items to a cart?&lt;/p&gt;

&lt;p&gt;That's the protocol layer. And it matters.&lt;/p&gt;

&lt;p&gt;But here's what the referral data shows: &lt;strong&gt;AI agents are indexing your entire web presence, not just your UCP endpoint.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When ChatGPT crawled our validator tool, it wasn't reading a &lt;code&gt;.well-known/ucp&lt;/code&gt; manifest. It was reading our documentation, our compare page, our blog posts. It was building a knowledge graph of "what tool exists for checking UCP profiles" that it can retrieve when a user asks.&lt;/p&gt;

&lt;p&gt;When Perplexity sent us a user with a 100% tool completion rate, it had already decided — before the human ever saw our site — that UCPtools was the right answer to their question.&lt;/p&gt;

&lt;p&gt;This is a new kind of discoverability. Not search ranking. &lt;strong&gt;Knowledge base ranking.&lt;/strong&gt; The question isn't "do I rank #1 for 'UCP validator'?" — it's "when an AI agent needs to answer a UCP question, does it know I exist?"&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for developer tools and e-commerce platforms
&lt;/h2&gt;

&lt;p&gt;Five different agents found us without us doing anything to attract them. No agent-specific SEO. No structured data markup for AI crawlers. Just a website with clear, specific content about a niche protocol.&lt;/p&gt;

&lt;p&gt;The implication: &lt;strong&gt;AI agents are actively hunting for authoritative sources in specialized domains.&lt;/strong&gt; If you build something in the UCP/ACP/agentic commerce space, the agents will find you — but only if your content is in their corpus.&lt;/p&gt;

&lt;p&gt;Three patterns we can already see:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agents prefer documentation over marketing.&lt;/strong&gt; Every agent that visited us hit our docs and tools. None touched our pricing page. None visited our blog homepage. They went straight for substance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tool usage is the strongest signal.&lt;/strong&gt; Perplexity's 100% tool-user rate tells us the agent pre-qualified the user — it already knew our tool was the answer. The human didn't browse; they executed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-agent coverage compounds.&lt;/strong&gt; One agent finding you is luck. Five agents finding you means your content is propagating across knowledge bases. ChatGPT, Gemini, Perplexity, Claude, and NotebookLM use different indexes and different retrieval strategies. If you're in all five, you're discoverable regardless of which agent a user chooses.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The protocol layer matters too — here's how they connect
&lt;/h2&gt;

&lt;p&gt;We built UCPtools to validate UCP profiles. The core insight was: an AI shopping agent needs a valid UCP manifest to buy from your store. But the protocol layer and the knowledge layer are connected in ways that aren't obvious yet.&lt;/p&gt;

&lt;p&gt;Consider this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A developer asks ChatGPT: "How do I set up UCP for my Shopify store?"&lt;/li&gt;
&lt;li&gt;ChatGPT searches its knowledge base, finds our documentation (because we're in its corpus), and recommends our validator.&lt;/li&gt;
&lt;li&gt;The developer uses the free validator, finds 3 broken endpoints, and fixes them.&lt;/li&gt;
&lt;li&gt;Now their store is both &lt;strong&gt;UCP-valid&lt;/strong&gt; (protocol layer) AND &lt;strong&gt;indexed by the agents that will send them customers&lt;/strong&gt; (knowledge layer).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without step 2, the developer might never find the validator. Without step 3, their UCP profile stays broken and AI shopping agents can't transact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need both.&lt;/strong&gt; A valid UCP profile that no AI agent knows about is useless. And an AI agent that knows about your store but can't transact because your payment handlers are broken is equally useless.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to check if AI agents can find you (and transact with you)
&lt;/h2&gt;

&lt;p&gt;There's no all-in-one dashboard for this yet. But here's what you can do right now:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your referrer logs&lt;/strong&gt; for these domains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chatgpt.com&lt;/code&gt; / &lt;code&gt;chat.openai.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gemini.google.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perplexity.ai&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;claude.ai&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notebooklm.google.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you see any of them, AI agents are already building knowledge about your site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your UCP profile&lt;/strong&gt; — because being discoverable is pointless if agents can't transact. Our free validator runs 4 levels of checks: structural, rules, network, and an AI agent simulator that actually tests whether an agent can complete a purchase flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ucptools.dev/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=ai-agent-traffic-202606" rel="noopener noreferrer"&gt;Run the free UCP validator →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your robots.txt.&lt;/strong&gt; Most AI crawlers respect &lt;code&gt;robots.txt&lt;/code&gt;. If you're blocking &lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, or &lt;code&gt;Google-Extended&lt;/code&gt;, you're opting out of AI discoverability. That might be intentional — but it's worth knowing what you're blocking.&lt;/p&gt;




&lt;h2&gt;
  
  
  One thing I'm still figuring out
&lt;/h2&gt;

&lt;p&gt;The data is real but the sample is small. Five agents, a handful of visits, one site. This isn't a research paper — it's an observation from our referrer logs that I think matters for anyone building in this space.&lt;/p&gt;

&lt;p&gt;What I don't know yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How frequently agents re-crawl and refresh their knowledge&lt;/li&gt;
&lt;li&gt;Whether agent referral traffic converts differently than organic search&lt;/li&gt;
&lt;li&gt;Whether NotebookLM ingestion leads to downstream discoverability in other Google products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're seeing AI agents in your own referrer logs, I'd love to compare notes. Drop a comment — especially if you're seeing patterns I haven't mentioned.&lt;/p&gt;

&lt;p&gt;The one thing I'm confident about: &lt;strong&gt;the agents are here, they're indexing, and they're already making decisions about which tools and services to recommend.&lt;/strong&gt; The question isn't whether to care about AI discoverability. It's whether you're already invisible.&lt;/p&gt;

</description>
      <category>ucp</category>
      <category>ai</category>
      <category>webdev</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Google's Universal Cart Is Live. Here's the Checklist to Make Sure Agents Can Actually Check Out on Your Store</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Sat, 13 Jun 2026 13:35:37 +0000</pubDate>
      <link>https://dev.to/ucptools/googles-universal-cart-is-live-heres-the-checklist-to-make-sure-agents-can-actually-check-out-on-2n80</link>
      <guid>https://dev.to/ucptools/googles-universal-cart-is-live-heres-the-checklist-to-make-sure-agents-can-actually-check-out-on-2n80</guid>
      <description>&lt;p&gt;At Google Marketing Live on May 20, 2026, Universal Cart stopped being a demo. Google announced that shoppers can now save products across retailers and check out - with Google Pay or the retailer's own flow - directly inside Search, AI Mode, the Gemini app, and YouTube shopping ads. The launch list is not a pilot: Nike, Sephora, Target, Walmart, Wayfair, and Shopify merchants like Fenty and Steve Madden, with Affirm and Klarna wired in for financing and hotels and food delivery slated next.&lt;/p&gt;

&lt;p&gt;The plumbing under all of it is UCP (Universal Commerce Protocol) - the open standard that gives an AI agent a machine-readable entry point to a store at &lt;code&gt;/.well-known/ucp&lt;/code&gt;. &lt;em&gt;(Quick disclaimer: UCP is owned and maintained by Google and Shopify. UCPtools, which I work on, is an independent community tool - not affiliated with either.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is the part that matters for everyone not named in that launch list: the agent decides in milliseconds whether your store can transact. There is no "almost." Either your UCP profile answers the questions the agent asks, or the agent moves on to a merchant whose profile does. No bounce shows up in your analytics. You just quietly stop existing in the one shopping surface that is about to matter most.&lt;/p&gt;

&lt;p&gt;So this is the readiness checklist. It maps to the four things a validator actually checks, in the order an agent hits them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 1: Structure - does the profile parse at all?
&lt;/h2&gt;

&lt;p&gt;Before anything semantic, the profile has to be a well-formed UCP document served correctly. This is where most "it's there but it doesn't work" failures live.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;/.well-known/ucp&lt;/code&gt; returns &lt;code&gt;200&lt;/code&gt; with &lt;code&gt;Content-Type: application/json&lt;/code&gt; - not &lt;code&gt;text/html&lt;/code&gt;, not a redirect to a marketing page.&lt;/li&gt;
&lt;li&gt;[ ] The root &lt;code&gt;ucp&lt;/code&gt; object exists. Missing it is &lt;code&gt;UCP_MISSING_ROOT&lt;/code&gt; and the agent has nothing to read.&lt;/li&gt;
&lt;li&gt;[ ] A &lt;code&gt;version&lt;/code&gt; field is present and formatted &lt;code&gt;YYYY-MM-DD&lt;/code&gt;. A stray semver string trips &lt;code&gt;UCP_INVALID_VERSION_FORMAT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] The JSON is valid - no trailing commas, no CDN error page wrapped around it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds trivial. It is the single most common break, because the file is served by a platform or a CDN that nobody is watching, and a caching plugin or a security challenge can turn a clean profile into an HTML error page overnight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 2: Rules - is it actually UCP-compliant?
&lt;/h2&gt;

&lt;p&gt;A parseable profile can still be semantically wrong in ways that make an agent distrust it. Level 2 is the UCP compliance layer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Namespace and origin match.&lt;/strong&gt; The namespace your capabilities declare has to bind to the origin serving the profile. A mismatch is &lt;code&gt;UCP_NS_ORIGIN_MISMATCH&lt;/code&gt; - a classic copy-paste-from-staging bug.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Every endpoint is HTTPS, no trailing slash.&lt;/strong&gt; &lt;code&gt;UCP_ENDPOINT_NOT_HTTPS&lt;/code&gt; and &lt;code&gt;UCP_ENDPOINT_TRAILING_SLASH&lt;/code&gt; are both hard stops; agents will not POST a payment intent over &lt;code&gt;http://&lt;/code&gt; or to a sloppy URL.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;No orphaned extensions.&lt;/strong&gt; Every extension has to chain back to a capability that declares it. A dangling one is &lt;code&gt;UCP_ORPHANED_EXTENSION&lt;/code&gt; and signals a profile that was hand-edited and never re-validated.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Signing keys are present.&lt;/strong&gt; &lt;code&gt;UCP_MISSING_SIGNING_KEYS&lt;/code&gt; is the one that quietly kills checkout: without published keys, the agent cannot verify that the offer and the payment handler are really yours. It will browse and then refuse to transact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the difference between showing up in Universal Cart's discovery layer and actually completing a purchase in it. Discovery without signing keys gets you window-shopped, not bought.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 3: Network - do your references actually resolve?
&lt;/h2&gt;

&lt;p&gt;Your profile points outward - to capability schemas and handler endpoints. Level 3 fetches them, the way an agent would, from outside your network.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every referenced capability schema is reachable and self-describing. A schema host that 404s or times out is &lt;code&gt;UCP_SCHEMA_FETCH_FAILED&lt;/code&gt;, and it is &lt;em&gt;someone else's outage becoming your broken profile&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Handler endpoints respond from the public internet, not just from inside your VPC. A check that runs on your own infra can hit a warm cache and report healthy while real agents get a connection error.&lt;/li&gt;
&lt;li&gt;[ ] TLS is valid across every edge, not just your origin. Certs renew and propagate unevenly; one stale CDN edge is enough to fail the agent that lands on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You cannot fully test Level 3 from a unit test, because the failures are environmental and external. This is the level that needs to be checked against your &lt;em&gt;live&lt;/em&gt; domain, from outside.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 4: SDK - does it pass the official compliance pass?
&lt;/h2&gt;

&lt;p&gt;The top level runs your profile through &lt;code&gt;@ucp-js/sdk&lt;/code&gt;, the official UCP compliance kit. This is the closest a check gets to "would Google's own agent accept this?" - the same library the ecosystem builds against, applied to your live profile. Passing Levels 1 through 3 and failing here usually means a subtle shape mismatch the schema rules did not catch. If you only automate one gate, make it this one, because it is the one that tracks the spec as it evolves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run the whole checklist in one call
&lt;/h2&gt;

&lt;p&gt;You do not have to eyeball any of this. UCPtools exposes a remote endpoint that fetches your live profile and runs all four levels server-side, the way an agent sees it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://ucptools.dev/v1/profiles/validate-remote &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"domain":"mystore.com"}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mystore.com/.well-known/ucp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"grade"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UCP_MISSING_SIGNING_KEYS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.ucp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No signing keys published"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Publish your Ed25519/ES256 public keys so agents can verify offers."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"validated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-13T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire that same check into CI with the &lt;a href="https://github.com/Nolpak14/ucp-validate-action" rel="noopener noreferrer"&gt;&lt;code&gt;ucp-validate-action&lt;/code&gt;&lt;/a&gt; so a regression fails the build before it ships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nolpak14/ucp-validate-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mystore.com'&lt;/span&gt;
    &lt;span class="na"&gt;min-score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Where each stack tends to break
&lt;/h2&gt;

&lt;p&gt;The checklist is platform-agnostic - it reads the open standard, not platform internals - but the failure that actually bites you tends to cluster by stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shopify:&lt;/strong&gt; you are largely along for the ride on what the platform serves at the well-known path, which is good until a platform change shifts the manifest shape under you. Validate after every theme or app change, because you do not control that surface directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WooCommerce:&lt;/strong&gt; the usual culprit is a caching or security plugin that starts serving &lt;code&gt;/.well-known/ucp&lt;/code&gt; from cache, behind a challenge, or as &lt;code&gt;text/html&lt;/code&gt;. That is a Level 1 break (&lt;code&gt;Content-Type&lt;/code&gt;) hiding behind a green admin dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BigCommerce / headless:&lt;/strong&gt; a frontend deploy moves a handler endpoint the profile still advertises, and Level 3 starts failing while the storefront looks perfect to a human. Storefront-scope mismatches show up here too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom / self-hosted:&lt;/strong&gt; signing-key drift. The key rotates in your infra but not in the published profile, and &lt;code&gt;UCP_MISSING_SIGNING_KEYS&lt;/code&gt; or a verification failure silently downgrades you from "can transact" to "can only be browsed."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every case the break is invisible to a human visitor and only shows up when something fetches the live profile from outside and runs the checks. That is the entire argument for validating continuously instead of once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing the launch list quietly proves
&lt;/h2&gt;

&lt;p&gt;The brands Google named did not get into Universal Cart by accident. Someone on each of those teams treated the UCP profile as production infrastructure - versioned, validated, and monitored - not as a one-time SEO chore. That is the whole gap. The standard is open and the endpoint is a static file; nothing stops a mid-sized Shopify or WooCommerce store from being exactly as agent-ready as Nike. What stops most of them is that nobody ever validated past "the file exists."&lt;/p&gt;

&lt;p&gt;Universal Cart going live is the forcing function. Agents are transacting now, in surfaces real shoppers already use every day. Run the checklist against your store - &lt;a href="https://ucptools.dev" rel="noopener noreferrer"&gt;validate it free at ucptools.dev&lt;/a&gt; - and if it passes, set up monitoring so it &lt;em&gt;stays&lt;/em&gt; passing at 3am when a cert renews and no one shipped a thing. The merchants who win this transition treat their UCP profile like uptime, because for agentic checkout, that is exactly what it is.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UCP is an open standard by Google and Shopify. UCPtools is an independent community tool.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Built by Peter at &lt;a href="https://ucptools.dev" rel="noopener noreferrer"&gt;UCPtools&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>ai</category>
      <category>webdev</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>UCP vs ACP: The Battle of AI Commerce Protocols and How to Build for Both</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Thu, 11 Jun 2026 18:49:04 +0000</pubDate>
      <link>https://dev.to/ucptools/ucp-vs-acp-the-battle-of-ai-commerce-protocols-and-how-to-build-for-both-18ao</link>
      <guid>https://dev.to/ucptools/ucp-vs-acp-the-battle-of-ai-commerce-protocols-and-how-to-build-for-both-18ao</guid>
      <description>&lt;p&gt;The AI commerce agent landscape exploded in early 2026 when both Google's &lt;strong&gt;Universal Commerce Protocol (UCP)&lt;/strong&gt; and OpenAI's &lt;strong&gt;Agentic Commerce Protocol (ACP)&lt;/strong&gt; launched within weeks of each other. If you're building an AI shopping agent, you now face a critical choice: which protocol should you implement? Or should you support both?&lt;/p&gt;

&lt;p&gt;In this deep-dive, we'll compare UCP and ACP from an implementer's perspective, examine their architectural trade-offs, and show you how to get started with &lt;strong&gt;Agorio&lt;/strong&gt; - the only open-source SDK that implements both with automatic detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rise of Agentic Commerce
&lt;/h2&gt;

&lt;p&gt;AI agents that can shop on your behalf are no longer science fiction. With 4.8M Shopify merchants discoverable via UCP and 1.5M Stripe merchants ready for ACP, the infrastructure for autonomous commerce is already in place. Visa and Mastercard are enabling all US cardholders for agent transactions by holiday 2026.&lt;/p&gt;

&lt;p&gt;But to tap into this ecosystem, your agent needs to speak the right protocol. Let's look at the two contenders.&lt;/p&gt;

&lt;h2&gt;
  
  
  UCP: Google &amp;amp; Shopify's Decentralized Approach
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Universal Commerce Protocol&lt;/strong&gt; is an open standard co-developed by Google, Shopify, Etsy, Wayfair, Target, Walmart and endorsed by Stripe, PayPal, Visa, Mastercard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt;: Merchants publish a JSON manifest at &lt;code&gt;/.well-known/ucp&lt;/code&gt; that tells agents what they support - capabilities, endpoints, payment handlers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capabilities&lt;/strong&gt;: Modular capabilities like &lt;code&gt;dev.ucp.shopping.checkout&lt;/code&gt; (cart, tax, session handling), &lt;code&gt;dev.ucp.shopping.order&lt;/code&gt; (post-purchase), and extensions for fulfillment, discounts, buyer consent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Architecture&lt;/strong&gt;: The "trust triangle" separates business, payment credential provider, and platform. Multiple handler types: Google Pay, Shop Pay, Direct Tokenization, AP2 (autonomous agent mandates).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport Bindings&lt;/strong&gt;: REST (OpenAPI), MCP (OpenRPC for LLM tools), and Agent Card (A2A).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.well-known/ucp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;manifest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ucpVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev.ucp.shopping.checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"restEndpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/ucp/checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"paymentHandlers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"google_pay"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to Choose UCP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;decentralized discovery&lt;/strong&gt; (any merchant can self-host)&lt;/li&gt;
&lt;li&gt;Your agents need &lt;strong&gt;rich shopping workflows&lt;/strong&gt; (cart, catalog, order tracking)&lt;/li&gt;
&lt;li&gt;You're building for &lt;strong&gt;Google's AI Mode&lt;/strong&gt; or &lt;strong&gt;Gemini&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;multiple payment handler types&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ACP: OpenAI &amp;amp; Stripe's Delegated Payment Model
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Agentic Commerce Protocol&lt;/strong&gt; is an open standard (Apache 2.0) by OpenAI and Stripe, primarily powering ChatGPT Instant Checkout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt;: Centralized - merchants apply at chatgpt.com/merchants and provide product feeds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Model&lt;/strong&gt;: Delegated payment - any PSP can implement the spec (Stripe first, PayPal second)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoints&lt;/strong&gt;: 5 REST endpoints for the full checkout lifecycle: create, get, update, complete, cancel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;States&lt;/strong&gt;: &lt;code&gt;not_ready_for_payment&lt;/code&gt; → &lt;code&gt;ready_for_payment&lt;/code&gt; → &lt;code&gt;completed&lt;/code&gt; (with optional authentication steps)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example ACP checkout session flow&lt;/span&gt;
&lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;checkout_sessions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line_items&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// $29.99 in cents&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return_url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/return&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success_url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to Choose ACP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You're building for &lt;strong&gt;ChatGPT&lt;/strong&gt; or other OpenAI-agent ecosystems&lt;/li&gt;
&lt;li&gt;You prefer a &lt;strong&gt;centralized, vetted merchant pool&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your transactions are &lt;strong&gt;single-item purchases&lt;/strong&gt; (current limitation)&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;delegated payment&lt;/strong&gt; through any PSP&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Head-to-Head Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;UCP&lt;/th&gt;
&lt;th&gt;ACP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google, Shopify, 25+ partners&lt;/td&gt;
&lt;td&gt;OpenAI, Stripe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Decentralized (&lt;code&gt;/.well-known/ucp&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Centralized (merchant application)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple handlers (Google Pay, Shop Pay, etc.)&lt;/td&gt;
&lt;td&gt;Delegated via any PSP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Namespace-based extensions&lt;/td&gt;
&lt;td&gt;RFC-based extensions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;REST, MCP, A2A&lt;/td&gt;
&lt;td&gt;REST only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merchant Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.8M Shopify merchants + any self-hosted&lt;/td&gt;
&lt;td&gt;1.5M Stripe merchants + 35M PayPal (coming)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open spec&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Current Limitations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex capability negotiation&lt;/td&gt;
&lt;td&gt;US-only, single-item only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Choose? Build Both with Agorio
&lt;/h2&gt;

&lt;p&gt;The reality is that &lt;strong&gt;most serious commerce agents will need to support both protocols&lt;/strong&gt; to reach the maximum number of merchants. That's where &lt;a href="https://agorio.dev?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=202606" rel="noopener noreferrer"&gt;Agorio&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;Agorio is an open-source TypeScript SDK that implements both UCP and ACP with automatic protocol detection. Here's how you can use it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: UCP Client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UcpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Discover merchant capabilities&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ucp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UcpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ucp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Check if they support checkout&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supportsCheckout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ucp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;cartId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google_pay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example: ACP Client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AcpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AcpClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your_stripe_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;merchantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merch_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;acp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The ShoppingAgent: Auto-Detection Magic
&lt;/h3&gt;

&lt;p&gt;Agorio's &lt;code&gt;ShoppingAgent&lt;/code&gt; automatically detects which protocol a merchant supports and uses the appropriate client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ShoppingAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GeminiAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agorio/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShoppingAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GeminiAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEMINI_API_KEY&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Agent automatically handles protocol detection&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Go to store.example.com and buy me wireless headphones&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Works with both UCP and ACP merchants&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Order placed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation Complexity Analysis
&lt;/h2&gt;

&lt;p&gt;Implementing UCP from scratch requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON-LD manifest parsing&lt;/li&gt;
&lt;li&gt;Capability intersection algorithm&lt;/li&gt;
&lt;li&gt;Payment handler abstraction&lt;/li&gt;
&lt;li&gt;Multiple transport bindings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing ACP from scratch requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bearer auth with API versioning&lt;/li&gt;
&lt;li&gt;Payment token delegation&lt;/li&gt;
&lt;li&gt;Product feed management&lt;/li&gt;
&lt;li&gt;Strict state machine adherence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agorio handles all of this for you&lt;/strong&gt;, providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UcpClient&lt;/code&gt; with discovery + REST/MCP auto-transport&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AcpClient&lt;/code&gt; with full session lifecycle management&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShoppingAgent&lt;/code&gt; with 12 built-in tools for commerce workflows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MockMerchant&lt;/code&gt; for testing (UCP, ACP, and MCP variants)&lt;/li&gt;
&lt;li&gt;LLM adapters for Gemini, Claude, OpenAI, Ollama&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with ACP if&lt;/strong&gt; you're building for ChatGPT or want the simplest integration with Stripe/PayPal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with UCP if&lt;/strong&gt; you need decentralized discovery, multi-item carts, or Google AI Mode integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Agorio if&lt;/strong&gt; you want to support both protocols from day one (recommended).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember: &lt;strong&gt;merchants should support both protocols&lt;/strong&gt; for maximum AI agent coverage, and so should your agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The fastest way to experiment is Agorio's interactive playground:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agorio.dev/playground?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=202606" rel="noopener noreferrer"&gt;Agorio Playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or install the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @agorio/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://github.com/Nolpak14/agorio" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; (MIT licensed) and star it if you find it useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Agentic Commerce
&lt;/h2&gt;

&lt;p&gt;Both UCP and ACP are evolving rapidly. UCP's AP2 mandates will enable cryptographic payment authorization for autonomous agents, while ACP is expanding to multi-item purchases and international markets.&lt;/p&gt;

&lt;p&gt;The key takeaway: &lt;strong&gt;protocol literacy is your moat&lt;/strong&gt;. Understanding these standards will make you a more effective agent builder, whether you're working on a personal shopping assistant or an enterprise procurement agent.&lt;/p&gt;

&lt;p&gt;Have you started building with UCP or ACP? Share your experiences in the comments below!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>commerce</category>
      <category>ucp</category>
    </item>
    <item>
      <title>Build a UCP Watchdog: Catch the Production Breaks Your CI Never Will</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Fri, 05 Jun 2026 14:50:32 +0000</pubDate>
      <link>https://dev.to/ucptools/build-a-ucp-watchdog-catch-the-production-breaks-your-ci-never-will-18jh</link>
      <guid>https://dev.to/ucptools/build-a-ucp-watchdog-catch-the-production-breaks-your-ci-never-will-18jh</guid>
      <description>&lt;p&gt;You wired UCP validation into CI. Every push runs the checks, every PR gets a score, and a bad profile fails the build before it merges. Good - that is the right baseline.&lt;/p&gt;

&lt;p&gt;Here is what it does not catch: the break that happens when nobody touches the code.&lt;/p&gt;

&lt;p&gt;The standard here is UCP (Universal Commerce Protocol) - an open standard that gives AI shopping agents a machine-readable entry point to a store at &lt;code&gt;/.well-known/ucp&lt;/code&gt;. &lt;em&gt;(Quick disclaimer: UCP is owned and maintained by Google and Shopify. UCPtools, which I work on, is an independent community tool - not affiliated with either.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A CI gate is triggered by &lt;em&gt;your&lt;/em&gt; commits. But a UCP profile is a live production surface, and most of the things that break it are not commits at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;TLS certificate renews&lt;/strong&gt; and propagates to your origin but not to every CDN edge.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;capability schema host&lt;/strong&gt; your profile references goes down - someone else's outage, your broken profile.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CDN or DNS change&lt;/strong&gt; starts serving a cache page or a redirect at &lt;code&gt;/.well-known/ucp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Your &lt;strong&gt;platform&lt;/strong&gt; (Shopify, BigCommerce, a WooCommerce plugin update) quietly changes the served manifest or strips the &lt;code&gt;Content-Type: application/json&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;signing key rotates&lt;/strong&gt; in your infra but not in the published profile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these trips a build, because there is no build. Your CI is green. Your store works fine for human browsers. The only thing that regressed is the machine-readable layer that no human ever visits - and the AI agent that hits it does not file a bug. It just leaves for the next merchant whose profile answers.&lt;/p&gt;

&lt;p&gt;CI catches what you break on merge. A watchdog catches what breaks itself. You need both.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Watchdog Actually Watches
&lt;/h2&gt;

&lt;p&gt;A pre-merge gate asks "is the profile I'm about to ship valid?" A watchdog asks a different question on a schedule: "is the profile that is live &lt;em&gt;right now&lt;/em&gt; still valid, from outside, the way an agent sees it?"&lt;/p&gt;

&lt;p&gt;Two design rules make the difference:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check from outside your network.&lt;/strong&gt; A check that runs inside your own infra can hit a warm cache or an internal route and report healthy while external agents get errors. Fetch your public URL over the public internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare against a baseline, not just against pass/fail.&lt;/strong&gt; A profile can stay technically valid while its score quietly slides from A to C. Alert on regression from a known-good baseline, not only on hard failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's build it two ways: a dependency-free cron version, and a GitHub Action with Slack alerts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 10-Line Version: cron + curl
&lt;/h2&gt;

&lt;p&gt;UCPtools exposes a public remote-validation endpoint that fetches a live domain's profile and runs the checks server-side. You can hit it from anything that runs &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ucp-watch.sh - alert if the live UCP profile is broken&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mystore.com"&lt;/span&gt;

&lt;span class="nv"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://ucptools.dev/v1/profiles/validate-remote &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;domain&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$resp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;     | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.ok'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$resp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'[.issues[] | select(.severity=="error")] | length'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ok&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$errors&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;codes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$resp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'[.issues[] | select(.severity=="error") | .code] | join(", ")'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:rotating_light: UCP profile for &lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt; is broken: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;codes&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint returns the live result, shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mystore.com/.well-known/ucp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UCP_SCHEMA_FETCH_FAILED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.ucp.capabilities[0]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"validated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-05T14:33:57Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schedule it and you have a watchdog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/15 * * * * SLACK_WEBHOOK_URL=https://hooks.slack.com/... /opt/ucp-watch.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;UCP_SCHEMA_FETCH_FAILED&lt;/code&gt; or &lt;code&gt;UCP_ENDPOINT_NOT_HTTPS&lt;/code&gt; showing up at 3am - hours after a cert renewal, with no deploy in sight - pages you instead of silently costing you agent traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GitHub Action Version: scheduled, with a baseline
&lt;/h2&gt;

&lt;p&gt;If your store already lives in GitHub, you can run the same idea on a &lt;code&gt;schedule:&lt;/code&gt; trigger and reuse the existing &lt;a href="https://github.com/Nolpak14/ucp-validate-action" rel="noopener noreferrer"&gt;&lt;code&gt;ucp-validate-action&lt;/code&gt;&lt;/a&gt; - the same action people put in CI - but pointed at your &lt;strong&gt;live production domain&lt;/strong&gt; and run on a clock instead of on push. The difference is entirely in the trigger and what you do with the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UCP Watchdog&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;   &lt;span class="c1"&gt;# every 30 minutes&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# let me run it by hand too&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ucp&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nolpak14/ucp-validate-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mystore.com'&lt;/span&gt;   &lt;span class="c1"&gt;# your LIVE domain, not staging&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Alert on regression&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_WEBHOOK_URL }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;score="${{ steps.ucp.outputs.score }}"&lt;/span&gt;
          &lt;span class="s"&gt;grade="${{ steps.ucp.outputs.grade }}"&lt;/span&gt;
          &lt;span class="s"&gt;found="${{ steps.ucp.outputs.ucp-found }}"&lt;/span&gt;
          &lt;span class="s"&gt;baseline=$(cat .ucp-baseline 2&amp;gt;/dev/null || echo 0)&lt;/span&gt;

          &lt;span class="s"&gt;echo "Live: score=$score grade=$grade found=$found | baseline=$baseline"&lt;/span&gt;

          &lt;span class="s"&gt;if [ "$found" = "false" ] || [ "$score" -lt "$baseline" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;curl -sS -X POST "$SLACK_WEBHOOK_URL" -H 'Content-type: application/json' \&lt;/span&gt;
              &lt;span class="s"&gt;-d "{\"text\":\":rotating_light: UCP regression on mystore.com - score ${score} (grade ${grade}), baseline ${baseline}\"}"&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit a one-line baseline file the first time you go green:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;90 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .ucp-baseline   &lt;span class="c"&gt;# your known-good score&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action exposes &lt;code&gt;score&lt;/code&gt;, &lt;code&gt;grade&lt;/code&gt;, &lt;code&gt;ucp-found&lt;/code&gt;, &lt;code&gt;passed&lt;/code&gt;, and &lt;code&gt;result-json&lt;/code&gt;, so you can build whatever alerting logic you want on top. The point is that the trigger is a clock, the target is production, and the comparison is against your last known-good state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Alert Hygiene (so you don't train yourself to ignore it)
&lt;/h2&gt;

&lt;p&gt;A watchdog that cries wolf gets muted, and a muted watchdog is worse than none. Three things keep it honest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline, don't just pass/fail.&lt;/strong&gt; A slow slide from grade A to grade C is the regression you most want to know about, and a binary "still valid?" check will miss it entirely. Diff the score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debounce flaps.&lt;/strong&gt; A single failed fetch can be a transient network blip. Require two consecutive bad checks before paging, or alert on a sustained drop rather than one data point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bump the baseline when you improve.&lt;/strong&gt; When you legitimately raise your score, update &lt;code&gt;.ucp-baseline&lt;/code&gt; in the same PR. The baseline is a ratchet - it should only move up on purpose.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Platform Notes
&lt;/h2&gt;

&lt;p&gt;The watchdog is platform-agnostic - it reads the open &lt;code&gt;/.well-known/ucp&lt;/code&gt; standard, not platform internals - but the regression that pages you tends to differ by stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WooCommerce:&lt;/strong&gt; a caching or security plugin update that starts serving &lt;code&gt;/.well-known/ucp&lt;/code&gt; from cache, behind a challenge, or as &lt;code&gt;text/html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BigCommerce / headless:&lt;/strong&gt; a frontend deploy or app change that moves an endpoint the profile still advertises, or a storefront-scope mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify:&lt;/strong&gt; the platform changing what it serves at the well-known path out from under you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every case the failure is invisible until something fetches the live profile from outside and compares it to what you expect. That is the whole job of the watchdog.&lt;/p&gt;




&lt;p&gt;CI proves the profile you wrote is correct. A watchdog proves the profile your customers' agents actually hit is still correct - at 3am, after a cert renewal, when no one shipped a thing. Both are a few lines of YAML. The merchants who win the agentic-commerce transition will treat the second one like uptime, because that is exactly what it is.&lt;/p&gt;

&lt;p&gt;If you would rather not run your own, UCPtools does hosted monitoring with break-alerts across all four validation levels - &lt;a href="https://ucptools.dev/signup" rel="noopener noreferrer"&gt;start here&lt;/a&gt;. Either way: watch the live profile, not just the build.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UCP is an open standard by Google and Shopify. UCPtools is an independent community tool.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Built by Peter at &lt;a href="https://ucptools.dev" rel="noopener noreferrer"&gt;UCPtools&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>devops</category>
      <category>ecommerce</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
