Agentic Commerce Readiness Index - UCP, ACP & AI-Shopping Audit
Pricing
from $0.00005 / actor start
Agentic Commerce Readiness Index - UCP, ACP & AI-Shopping Audit
Score any storefront 0-100 on how ready it is for AI shopping agents — Universal Commerce Protocol manifest, ACP product-feed conformance, structured data, AI-crawler policy — and track adoption over time. Cohort mode turns 500 merchants into an adoption curve you cannot buy retroactively.
Pricing
from $0.00005 / actor start
Rating
0.0
(0)
Developer
Eonix Pvt Ltd
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
19 days ago
Last modified
Categories
Share
Agentic Commerce Readiness Index — UCP, ACP & AI-Shopping Audit
The adoption curve you cannot buy retroactively.
The Universal Commerce Protocol went self-serve on Shopify in June 2026. Right now, every week, merchants are switching it on. Nobody is writing that down.
This actor scores any storefront 0–100 on how ready it is for AI shopping agents — and, because it snapshots every scan, it turns a merchant list into a longitudinal dataset of protocol adoption. Run it once and you get an audit. Run it weekly against 500 merchants and you get something that cannot be reconstructed after the fact at any price: who adopted, when, in what order, and what they turned on first.
A competitor can clone the audit in an afternoon. Nobody can clone six months of history.
The lead example: audit 500 merchants, show me who switched UCP on this month
{"domains": ["merchant1.com", "merchant2.com", "…498 more"],"productSampleSize": 25,"generateReports": false}
Put that on a weekly Schedule. Every run writes COHORT.json and emits a typed change record for every merchant whose posture moved:
ucp_adopted gymshark.com switched UCP on at version 2026-04-08.capability_added kith.com added the UCP capability dev.ucp.shopping.checkout.payment_handler_changed mejuri.com changed its UCP payment handlers.ucp_version_changed skims.com moved from UCP 2026-01-23 to 2026-04-08.
Read CHANGES.json and filter changeType to ucp_adopted. That is your answer, and it gets more valuable every week you keep the schedule running.
Change events are in the dataset too, as
kind: "change"rows. Reach forCHANGES.jsonwhen you want the changes on their own: an Apify dataset view selects columns without filtering rows, so the dataset's "Adoption changes" view still returns every audit row with the change columns blank.
Auditing your own single store is the demo, not the product. A per-store UCP validator already exists and does that job well. What this actor sells is the cohort view and the adoption deltas.
What you get
| Output | Where | What it is |
|---|---|---|
| Per-domain audit | dataset, kind: "record" | Full readiness profile: score, six sub-scores, raw UCP manifest, ACP feed gaps, crawler matrix |
| Adoption changes | dataset, kind: "change" | Typed events since the previous scan of that domain |
| Product feed gaps | dataset, kind: "record" with productId | Per-product missing-field list (opt in with includeProductDetail) |
| Run summary | dataset, kind: "summary" | Totals, artifact URLs |
CHANGES.json | key-value store | The change feed alone — the only output that is just changes, with counts by type |
COHORT.json | key-value store | Adoption rates by capability, payment handler, platform and version; median score; leaderboard |
INDEX.json | key-value store | Every domain, sortable |
REPORT-<domain>.md | key-value store | Human-readable audit (opt in — see pricing) |
SUMMARY.json | key-value store | Same as the summary record |
COST-ESTIMATE.json | key-value store | What this run will cost you, written before work begins |
Real sample output
Copy-pasted from a real local run against allbirds.com on 16 August 2026. Trimmed only where marked.
{"kind": "record","target": "allbirds.com","status": "ok","collectedAt": "2026-08-16T18:21:15.116Z","source": "agentic-commerce-radar","score": 89.8,"grade": "A","ucpPresent": true,"ucpVersion": "2026-04-08","platform": "shopify","acpReadyPct": 100,"subScores": {"ucpManifest": 30,"ucpCapabilityDepth": 10,"acpFeedConformance": 23.8,"structuredData": 16,"agentCrawlerPolicy": 10,"agentCard": 0},"maxScores": {"ucpManifest": 30,"ucpCapabilityDepth": 10,"acpFeedConformance": 25,"structuredData": 20,"agentCrawlerPolicy": 10,"agentCard": 5},"topFixes": [{"area": "agentCard","pointsAvailable": 5,"action": "Publish an agent card at /.well-known/agent-card.json describing your agent-facing endpoints. Optional today, and a cheap leading signal."},{"area": "structuredData","pointsAvailable": 4,"action": "Add JSON-LD Organization markup to product pages — this is how agents read a store that has no UCP manifest."}],"ucp": {"present": true,"httpStatus": 200,"version": "2026-04-08","supportedVersions": ["2026-04-08", "2026-01-23"],"capabilities": ["dev.ucp.shopping.checkout","dev.ucp.shopping.fulfillment","dev.ucp.shopping.discount","dev.ucp.shopping.cart","dev.ucp.shopping.order","dev.ucp.shopping.catalog.search","dev.ucp.shopping.catalog.lookup","dev.shopify.catalog"],"transports": ["mcp", "embedded"],"paymentHandlers": ["com.google.pay", "dev.shopify.card", "dev.shopify.shop_pay"],"services": [{"serviceId": "dev.ucp.shopping","transport": "mcp","endpoint": "https://weareallbirds.myshopify.com/api/ucp/mcp"},{ "serviceId": "dev.ucp.shopping", "transport": "embedded", "endpoint": null }],"drift": false,"rawManifest": "<full manifest stored verbatim — trimmed here>"},"crawlerPolicy": {"robotsPresent": true,"allowedCount": 11,"blockedCount": 0,"payPerCrawl": false,"policyHash": "882049824c94f373"},"feed": {"surface": "shopify-products-json","productsSampled": 25,"acpReadyCount": 25,"acpReadyPct": 100,"fieldGapCounts": {},"seller": {"missingFields": ["seller_name"],"truncationWarnings": [],"sellerName": null,"sellerUrl": "https://allbirds.com"}},"structuredData": {"homepageBlocks": 0,"productBlocks": 5,"types": ["ProductGroup"],"hasProduct": true,"hasOffer": true,"hasAggregateRating": true,"productCompleteness": 1},"warnings": [],"durationMs": 2118}
Scoring — published weights, always with sub-scores
The total is never given without the parts. If you disagree with the weighting, recompute your own total from subScores without re-running the audit — that is why they are first-class output.
| Area | Weight | What earns it |
|---|---|---|
ucpManifest | 30 | A live, parseable manifest at /.well-known/ucp. Most of the weight is presence; the rest rewards a declared version, multiple supported versions, and a service with a reachable endpoint. |
ucpCapabilityDepth | 10 | How much of a purchase can actually complete — coverage of catalog search/lookup, cart, checkout, fulfillment, discount, order — plus declared payment handlers. |
acpFeedConformance | 25 | 20 for the share of sampled products carrying every required ACP field, 5 for seller-level fields. |
structuredData | 20 | JSON-LD Product/Offer/Organization/AggregateRating presence, weighted toward how complete the richest Product/Offer actually is. |
agentCrawlerPolicy | 10 | Share of tracked AI crawlers permitted by robots.txt. |
agentCard | 5 | A published agent-card.json or ai-plugin.json. |
Grades: A ≥ 85 · B ≥ 70 · C ≥ 55 · D ≥ 35 · F < 35.
Two deliberate calls worth knowing about:
- A pay-per-crawl posture (HTTP 402) is not scored as a block. Charging agents for access is a legitimate commercial stance, so it costs a small deduction for friction rather than the full ten points.
productSampleSize: 0redistributes its 25 points across the other areas instead of capping every store at 75.maxScoresalways tells you what was actually attainable. A store whose catalog genuinely cannot be read scores 0 there — an agent cannot read it either.
Robots.txt is a scoring input, not a product
The AI-crawler matrix lives inside the score object and nowhere else. This actor deliberately does not emit a standalone crawler-policy feed — that overlaps a site-health auditor and a dedicated crawler panel.
Free plan limits
Unmonetized runs are capped to 1 target and 5 records. Paid plans run uncapped. Report generation is a paid feature. The run still produces a complete, real audit for that one domain, and the log says plainly what was capped.
"Unmonetized" means pay-per-event pricing is not active on the run — a local apify run, or an Actor with no pricing configured. Once pay-per-event pricing is live, runs are uncapped and bounded by the platform's own total-charge ceiling instead. Verify either side locally:
$ACTOR_TEST_PAY_PER_EVENT=true npx tsx scripts/check-gating.ts
What a run costs you
Prices are per event. The actor writes COST-ESTIMATE.json and logs a table before any work begins, so you never discover the bill afterwards.
| Event | Price | Fires |
|---|---|---|
domain-audited | $0.02 | Once per domain, after its record is pushed |
manifest-parsed | $0.03 | Only when a UCP manifest was found and parsed |
product-validated | $0.02 | Per 100 products validated, rounded up, once per run |
store-report | $2.00 | Per REPORT.md written — off by default |
adoption-change | $0.01 | Per change event |
request-served | $0.001 | Standby requests only |
Worked example — the shipped default (3 domains, 25 products each, no reports):
domain-audited 3 × $0.02 = $0.06manifest-parsed 2 × $0.03 = $0.06 (store.google.com has no manifest, so it is not charged)product-validated 1 × $0.02 = $0.02 (50 products → 1 charge unit)adoption-change 0 × $0.01 = $0.00 (first scan of each domain is a baseline)------$0.14
A 500-merchant cohort sweep costs roughly 500 × $0.02 + (adopters × $0.03) + ~$2.50 of product validation ≈ $13–$28 per run depending on adoption rate. Weekly, that is a dataset for the price of lunch.
Reports are the one expensive event. At $2.00 each, a 500-domain sweep with
generateReportson bills over $1,000. It ships off for exactly that reason. Turn it on for the handful of merchants you want a document for, not for a sweep.
Things you are never charged for: unreachable domains, domains with no UCP manifest (no manifest-parsed), the first scan of a domain (no adoption-change), failed report writes, or failed standby requests.
Pricing calibration — measured, not assumed
The prices above started as category-median hypotheses. They were validated against real platform cost before publishing, because profit = (0.8 × revenue) − platform cost and a mispriced actor simply runs at a loss.
Every run writes UNIT-ECONOMICS.json and logs a verdict. On the platform the harness reads usageUsd back off the run object (mode: "platform-actual"); locally it estimates from your configured rates.
Platform-measured on 16 August 2026 — the authoritative figures. Both are real runs in mode: "platform-actual", differing only in allocated memory:
| Memory | Compute units | Platform cost, 1 domain | Floor (3×) | Headroom on $0.02 |
|---|---|---|---|---|
| 4096 MB (as created) | 0.002944 | $0.001712 | $0.00642 | 3.1x |
| 1024 MB (correct) | 0.000658 | $0.000373 | $0.00140 | 14.3x |
The first platform run overturned a conclusion. Local estimates had bytes dominating cost. On the platform the actor was allocated the full 4096 MB and compute swamped everything — 99.8% of platform cost. A plain-HTTP actor has no use for 4 GB: dropping to 1024 MB cuts cost 4.6x, measured, with no change in output.
.actor/actor.json declares defaultRunOptions.memoryMbytes: 1024, but Apify applies that only when the actor is first created — apify push will not change it on an actor that already exists. On an already-deployed actor this is a one-time Console change; see "Set the memory default" in RUNBOOK.md. Until it is done, every run costs 4.6x more than it needs to.
Pricing was never at risk either way: even at the wasteful 4096 MB, domain-audited at $0.02 clears its cost-covering floor by 3.1x.
Local scale comparison, with COST_CU_USD=0.25 and COST_TRANSFER_GB_USD=0.20:
| 1× (3 domains) | 10× (30 domains) | ||
|---|---|---|---|
| Platform cost | $0.002688 | $0.017527 | |
| Cost per domain | $0.000896 | $0.000584 | falls 35% with volume ✓ |
| Margin | 97.60% | 97.91% | HEALTHY |
The cost driver was corrected by measurement. The initial hypothesis was product-validated — catalog paging looked like the thing that grows. Running at both scales said otherwise: cost per domain falls with volume while cost per product rises. Every domain costs the same ~12 probe requests regardless of productSampleSize, while a 250-product sample is one extra catalog page. The domain is the unit of work; the product is not. costDriverEvent is domain-audited.
| Event | Hypothesis | Measured floor (3× cost) | Verdict |
|---|---|---|---|
domain-audited | $0.02 | $0.0022 – $0.0034 | 6–9× headroom. Correctly priced, arguably generous. |
product-validated | $0.02 | $0.0027 – $0.0111 | 1.8–7.4× headroom. Correct, but the thinnest line — it tightens as cohorts skew toward reachable Shopify stores, because catalog paging then rises from 29% to 55% of bytes. |
manifest-parsed | $0.03 | negligible marginal cost | Priced on value, not cost: it fires only on the ~43% of domains that carry the signal buyers want. |
store-report | $2.00 | negligible marginal cost | Priced on value. See the warning above about sweeps. |
adoption-change | $0.01 | negligible marginal cost | Priced on value; the diff is pure computation over stored state. |
No hypothesis price was found to be wrong. All five sit above their cost-covering floor. The one to watch is product-validated.
The pay-per-event + usage toggle is deliberately NOT enabled. Apify's own docs warn it reduces pricing transparency and hurts quality score. At 97% margin there is no case for it.
Calibrating on your own account
The compute and transfer rates are not hardcoded — they vary by plan and change over time. Read yours off your Apify billing page and set them in .env:
COST_CU_USD=0.25COST_TRANSFER_GB_USD=0.20COST_PROXY_GB_USD=8.0
Leave them unset and the harness reports CALIBRATION NOT PERFORMED — rates unset rather than a fake healthy margin.
Reliability
Measured across a 30-domain cohort of real storefronts on 16 August 2026:
- 30/30 domains produced a usable audit — 100%. 26 clean, 4
partial(a probe degraded, the audit still completed). - 13/30 served a live UCP manifest (43.3%). The remaining 17 returned 404 — recorded as signal, not as failure.
- 1 endpoint-drift detection.
marucci.comanswered200with an HTML body at/.well-known/ucp; it was logged asSOURCE DRIFTand emitted withdrift: truerather than being silently counted as an adopter. - Default run: 3 domains in ~4 seconds. 30-domain cohort: ~30 seconds.
That last point is the one that matters for the failure mode this market is full of. A well-known competitor reports SUCCESS on 57,620 of 57,636 runs while returning unusable data. A 2xx with the wrong shape is reported here, not swallowed.
Maintenance
UCP moved from 2026-01-23 to 2026-04-08 inside a single quarter. This actor is built for that: the manifest parser never matches against a fixed schema, stores rawManifest verbatim so a later re-parse can recover fields today's parser ignores, and logs UCP schema drift: unknown key <k> instead of throwing.
Commitment: broken-source issues are triaged within 2 business days. Open an issue on the actor's Apify page with the domain and the run ID. Structural upstream breakage (a well-known path moves, a platform changes its catalog API) is prioritised over feature requests.
What this actor does NOT do
Being straight about the edges, because a surprised buyer leaves a one-star review.
- It does not transact. No orders, no carts, no checkout. It reads manifests; it never exercises them.
- It requires no merchant credentials, and cannot use them. Every probe is a public, unauthenticated GET against a CORS-open endpoint. It audits stores you do not own — that is the point.
- It does not render JavaScript. Plain HTTP only, deliberately: that is what makes 500 domains a week affordable. A store whose product data exists only in client-rendered JS will under-score on structured data.
- It cannot read a private or authenticated catalog. Merchants who gate
products.jsonscore 0 on ACP feed conformance, because an AI agent hits the same wall. - Seller-level ACP fields are inferred, not authoritative.
seller_namecomes from homepage JSON-LDOrganizationand the policy URLs are probed at conventional paths (/policies/privacy-policyon Shopify). A merchant using non-standard paths will show a false gap. - It samples, it does not exhaust. ACP conformance comes from up to 250 products and structured data from 3 product pages. Sub-5-point score movements are treated as sampling noise and do not emit a change event.
- It does not verify that a declared capability works. A manifest claiming
checkoutis scored as claiming it. Nobody can verify that without transacting, and this actor does not transact. robots.txtposture is a scoring input only. There is no standalone crawler-policy output.- Change detection needs a prior scan. The first run against any domain is a baseline and emits nothing. The dataset starts compounding on run two.
Use from an AI agent
Apify MCP
The actor is exposed through the Apify MCP server. Point your MCP client at it and the actor becomes a callable tool:
{"mcpServers": {"apify": {"command": "npx","args": ["-y", "@apify/actors-mcp-server", "--actors", "your-username/agentic-commerce-radar"],"env": { "APIFY_TOKEN": "<your token>" }}}}
Then ask in plain language: "Audit these twelve merchants for UCP support and tell me which ones can complete a checkout."
Standby HTTP
With Standby enabled the actor stays warm and answers GETs directly — same pipeline, same output envelope, same charges, plus request-served at $0.001 so idle compute never runs at a loss.
GET https://<your-actor>.apify.actor/?domains=allbirds.com,gymshark.com&productSampleSize=25GET https://<your-actor>.apify.actor/health
Query parameters mirror the input schema. Standby caps a request at 25 domains and 50 products, and ignores secret fields entirely — an API key or webhook URL in a query string ends up in access logs, proxies and browser history. Use a normal run for those.
Weekly Schedule recipe
The compounding dataset is the whole point, and it only compounds if the schedule runs.
- Actor → Schedules → Create new schedule
- Cron:
0 6 * * 1(Mondays, 06:00 UTC) - Input: your merchant list,
productSampleSize: 25,generateReports: false - Leave
storeNameatagentic-commerce-snapshotsso every run diffs against the same history - Optional: set
slackWebhookUrlto get pinged only when something actually moves
Week one is a baseline with no change events. From week two onward, every ucp_adopted record is a data point nobody else has.
Input reference
| Field | Type | Default | Notes |
|---|---|---|---|
domains | string[] | 3 verified domains | Bare domains or URLs; normalised to an origin |
productSampleSize | integer | 25 | 0–250. 0 skips product validation and reweights the score |
includeProductDetail | boolean | false | One dataset row per sampled product |
generateReports | boolean | false | $2.00 per report — see pricing |
storeName | string | agentic-commerce-snapshots | Named KV store holding scan history |
proxyConfiguration | proxy | none | Optional; helps when sweeping many domains behind one CDN |
slackWebhookUrl | secret | — | Posts only when changes are detected |
alertWebhookUrl | secret | — | POSTs the full JSON change payload |
openaiApiKey | secret | — | Adds a plain-English narrative to reports. Nothing else uses it |
The shipped defaults — allbirds.com, barnesandnoble.com, store.google.com — were verified live during the build. The first two serve real UCP manifests; the third returns 404. Google co-authored UCP and its own store does not serve one, which is a genuinely useful thing to be able to prove.
Run it locally
npm installnpm run buildnpm start
Test, lint, typecheck:
$npm test && npm run lint && npm run typecheck
Exercise the charging path and the economics harness without a live account:
$npm run acceptance
Compare unit economics at 10× scale:
$npm run acceptance:scaled
Configuration lives in .env — copy .env.example and read the comments. Nothing is required to run; the rate variables only affect calibration accuracy.
Operational docs
- SECURITY.md — security model, SSRF posture, reporting
- RUNBOOK.md — deploy, roll back, rotate secrets, investigate failures
- CHANGELOG.md
License
MIT.


