# AisleRun documentation > AisleRun is a shopper test an AI agent can order. The agent pays for one browser run through PayBox (an x402 payment in USDC on Base), and AisleRun opens an authorized product page in Chromium on a 390 x 844 phone and a 1440 x 900 desktop, adds the item to the cart, opens checkout without submitting anything, and returns a pass, fail or inconclusive verdict per viewport with the recording, screenshots, a Playwright trace and the first step that broke. --- # What is AisleRun > A shopper test an AI agent can order: one paid browser run that shops a product page on a phone and a desktop and stops at checkout. Source: https://aislerun.vercel.app/docs AisleRun is a shopper test your AI agent can order. Your agent pays for one run through [PayBox](https://docs.paybox.sh), and AisleRun opens your product page in a real browser on a phone and on a desktop, adds the item to the cart, opens checkout, and stops there. You get a verdict for each screen size, the recording of what the browser did, and the first step that broke. ## Who it's for AisleRun is for merchants and the agencies or AI agents that look after their stores. A store can load fine and still lose sales at the last click: a cart button that does nothing on phones, a checkout link that goes nowhere. Those bugs rarely show up in uptime checks, and spot-checking every release by hand doesn't scale. ## The problem it solves ### A page that loads isn't a page that sells Monitoring tells you the server answered. It doesn't tell you a shopper could put something in the cart. AisleRun runs the purchase path itself, in Chromium, the way a shopper would. ### Evidence instead of opinions Every result comes with the recording, screenshots, a [Playwright](https://playwright.dev) trace and the raw console output. A verdict is never written by a language model. It comes from a fixed script and fixed rules, so the same page gives the same answer. ### Proving a fix After a fix, the agent pays for a second run of the same page. The two runs sit [side by side](https://aislerun.vercel.app/docs/retests), each with its own recording and payment, so you can see the outcome changed. ## What you can do today | You want to | Read | | --- | --- | | Order a run from an agent | [Order your first run](https://aislerun.vercel.app/docs/quickstart) | | Understand a result | [Verdicts and blockers](https://aislerun.vercel.app/docs/verdicts) | | Check a fix | [Retests and comparisons](https://aislerun.vercel.app/docs/retests) | | Wire it into your own tools | [API](https://aislerun.vercel.app/docs/api) | | See it working | [Live proof](https://aislerun.vercel.app/docs/proof) | > During the MoonPay hackathon AisleRun only tests its own labelled test store. A public URL isn't treated as permission to test a store. See [Authorized stores](https://aislerun.vercel.app/docs/authorized-stores). ## Where to go next Start with [Order your first run](https://aislerun.vercel.app/docs/quickstart), or read [How a run works](https://aislerun.vercel.app/docs/how-it-works) for what happens between the payment and the report. --- # Order your first run > Get a quote, pay for the run through PayBox, and read the result, with the exact requests an agent sends. Source: https://aislerun.vercel.app/docs/quickstart This page walks through one paid run the way an agent does it: a free quote, one paid request through PayBox, then free reads until the run finishes. Every request and response below is the real shape returned by the API at `https://aislerun.158-220-85-120.sslip.io`. ## What you need - A [PayBox](https://docs.paybox.sh) account with a wallet credential granted to your agent or CLI. - About 0.10 USDC on Base in that wallet. One run costs 0.05 USDC. - A product page on an [authorized store](https://aislerun.vercel.app/docs/authorized-stores). Today that means one of AisleRun's [test store versions](https://aislerun.vercel.app/docs/test-store). ## 1. Get a quote The quote is free. It tells you the price, what's covered and when the offer expires, and it refuses a store that isn't authorized before anything is charged. ```sh curl -X POST https://aislerun.158-220-85-120.sslip.io/v1/quotes \ -H 'content-type: application/json' \ -d '{ "product_url": "https://aislerun-fixtures.vercel.app/v/fixture-mobile-cart-broken-v1/products/cup", "authorized_target_id": "fixture-mobile-cart-broken-v1", "profile": "shopify_checkout_entry_v1" }' ``` The parts of the response you use next: ```json { "quote_id": "9ac09ba8-1517-4589-af12-762cf700ad75", "expires_at": "2026-10-16T18:49:59.816Z", "price": { "amount": "0.05", "amount_atomic": "50000", "asset_symbol": "USDC", "network": "eip155:8453" }, "pay_with": { "method": "POST", "url": "https://aislerun.158-220-85-120.sslip.io/v1/inspections", "body_template": { "quote_id": "9ac09ba8-1517-4589-af12-762cf700ad75", "product_url": "https://aislerun-fixtures.vercel.app/v/fixture-mobile-cart-broken-v1/products/cup", "authorized_target_id": "fixture-mobile-cart-broken-v1", "profile": "shopify_checkout_entry_v1", "idempotency_key": "" } } } ``` ## 2. Pay for the run with PayBox Fill in `idempotency_key` with a UUID you keep for this run, then have PayBox call the paid URL. From an agent connected to PayBox's MCP server: ```text use_service({ credential_id: "", url: "https://aislerun.158-220-85-120.sslip.io/v1/inspections", method: "POST", body: { ...pay_with.body_template, "idempotency_key": "" } }) ``` From the PayBox CLI it's the same request: ```sh npx @paybox-sh/sdk use-service --wait \ --credential \ --method POST \ --url https://aislerun.158-220-85-120.sslip.io/v1/inspections \ --body @body.json ``` PayBox reads the payment request, signs a 0.05 USDC transfer from your wallet, and sends the request again with the payment attached. In our runs this took about 6 seconds. The agent gets the response body back: ```json { "ok": true, "inspection_id": "2c076b7f-3613-41c7-a5e3-6c46d7628f76", "status": "AWAITING_SETTLEMENT", "result_url": "https://aislerun.158-220-85-120.sslip.io/v1/inspections/2c076b7f-3613-41c7-a5e3-6c46d7628f76", "read_token": "rt_…", "report_url": "https://aislerun.vercel.app/runs/2c076b7f-3613-41c7-a5e3-6c46d7628f76#token=rt_…", "poll_after_ms": 2000, "payment": { "state": "VERIFIED", "reference": null } } ``` > The status says `AWAITING_SETTLEMENT` because this body is written before the payment lands on Base. The browser only starts after it does, about a second later. ## 3. Wait for the result Poll `result_url` with the read token every two seconds until `terminal` is true. ```sh curl -H "Authorization: Bearer rt_…" \ https://aislerun.158-220-85-120.sslip.io/v1/inspections/2c076b7f-3613-41c7-a5e3-6c46d7628f76 ``` ```json { "status": "COMPLETED", "terminal": true, "overall_verdict": "FAIL", "viewports": [ { "viewport": "mobile", "verdict": "FAIL", "blocker": "ADD_TO_CART_NO_EFFECT", "attempts": 2 }, { "viewport": "desktop", "verdict": "PASS", "blocker": null, "attempts": 1 } ] } ``` ## 4. Open the report Give a person the `report_url`. It opens the run in the browser with the recording, the steps and the payment. The token lives after the `#`, so it never reaches a server log. An agent can read the same data as JSON from `GET /v1/reports/{id}`. See [Reading results](https://aislerun.vercel.app/docs/reading-results). ## Do it in one command The repository has a script that does all four steps with the PayBox CLI and prints each status change: ```sh scripts/paybox-inspect.sh https://aislerun.158-220-85-120.sslip.io fixture-mobile-cart-broken-v1 ``` --- # How a run works > What happens between the quote and the report: the payment gate, the worker, the browser journey and where evidence is kept. Source: https://aislerun.vercel.app/docs/how-it-works A run has three parts: a free quote, a paid request that only queues work once the payment settles, and a browser job that writes everything it sees to the run's event log. Nothing on the report is written after the fact. Every status, step and verdict comes from a row the worker stored while it ran. ## The payment gate The paid route is an [x402](https://www.x402.org) resource. A request without payment gets HTTP 402 and a `PAYMENT-REQUIRED` header describing the price. PayBox signs a USDC transfer and retries with a `PAYMENT-SIGNATURE` header. | Stage | What AisleRun does | Status | | --- | --- | --- | | Before payment | Checks the body, the quote and the store. Refuses without asking for payment if any of them is wrong | none created | | Payment verified | The facilitator confirms the signature and balance. AisleRun records the payment and creates the run | `AWAITING_SETTLEMENT` | | Payment settled | The transfer lands on Base. Only now does the run join the queue | `QUEUED` | | Settlement failed | No browser work ever starts | `PAYMENT_DENIED` | The facilitator is [PayAI](https://facilitator.payai.network), and payments settle in USDC on Base mainnet (`eip155:8453`). ## The worker One worker takes queued runs from Postgres, one at a time, and holds a lease while it works. If a worker dies mid-run, the next one picks the run up once the lease expires and runs it again on the same payment. That happens at most once. A second failure ends the run as a service error, and you're never charged twice. See [Payments, retries and refunds](https://aislerun.vercel.app/docs/payments-and-refunds). ## The browser journey For each viewport, the worker opens a fresh Chromium context with no cookies or stored data and follows the same steps. 1. **Load** the product page and check it's still on the authorized store. 2. **Ready**: find the product title and price, and check it's in stock. 3. **Variant**: choose the first available option if the product has any. 4. **Add to cart**: find the control by test ID, then by the Shopify form, then by its accessible name, and press it. 5. **Confirm the cart**: wait up to 4 seconds for two separate signals, the cart count going up and a cart line appearing. 6. **Checkout entry**: press Check out and confirm the checkout page opens. Nothing is filled in or submitted. If the cart doesn't change, the worker repeats the whole journey once in a new, clean browser. A failure is only reported when it happens both times. The phone runs at 390 × 844 with touch and a 3× pixel ratio, and the desktop runs at 1440 × 900. Mobile runs first. ## Evidence Each attempt records a video, screenshots before and after the important steps, and a Playwright trace. When both viewports finish, the files are stored on a private volume with their size and SHA-256, and only then does the run become `COMPLETED` or `INCONCLUSIVE`. A run with no stored evidence never shows a verdict. ## The statuses you'll see | Status | Meaning | | --- | --- | | `AWAITING_SETTLEMENT` | Paid, waiting for the payment to settle | | `QUEUED` | Settled, waiting for the worker | | `STARTING` | The worker is launching Chromium | | `RUNNING_MOBILE`, `RUNNING_DESKTOP` | The journey is running on that viewport | | `UPLOADING_EVIDENCE` | Storing recordings, screenshots and traces | | `COMPLETED` | Finished with a pass or fail | | `INCONCLUSIVE` | Finished, but the journey couldn't be judged | | `FAILED_INTERNAL`, `CANCELLED` | AisleRun's own failure or time limit. Never a store verdict | | `PAYMENT_DENIED` | The payment didn't settle, so nothing ran | --- # Quotes > Ask what a run costs and what it covers before anything is charged, and how a quote binds the paid request to it. Source: https://aislerun.vercel.app/docs/quotes A quote is a free promise of what one run will cover and cost. It names the store, the product page, the journey and the price, and it expires after 15 minutes. The paid request has to match its quote exactly. ## Ask for a quote ```sh curl -X POST https://aislerun.158-220-85-120.sslip.io/v1/quotes \ -H 'content-type: application/json' \ -d '{ "product_url": "https://aislerun-fixtures.vercel.app/v/fixture-fixed-v2/products/cup", "authorized_target_id": "fixture-fixed-v2", "profile": "shopify_checkout_entry_v1", "viewports": ["mobile", "desktop"] }' ``` | Field | Required | Notes | | --- | --- | --- | | `product_url` | yes | An HTTPS product page on the authorized store. The query string and fragment are dropped | | `authorized_target_id` | yes | The store's ID from `GET /v1/meta` | | `profile` | yes | Always `shopify_checkout_entry_v1` for now | | `viewports` | no | `["mobile", "desktop"]` by default. One of them is allowed | ## What comes back | Field | Meaning | | --- | --- | | `quote_id` | Pass this in the paid request | | `expires_at` | 15 minutes after the quote was made | | `price` | Amount, atomic amount, asset and network | | `scope` | `product`, `cart`, `checkout_entry` | | `estimated_duration_text` | What we measured, not a promise | | `pay_with` | The paid URL and a body template with everything filled in except `idempotency_key` | ## When a quote is refused A refused quote never charges anything and never starts a browser. | Code | Why | | --- | --- | | `TARGET_NOT_AUTHORIZED` | The store isn't on the list, the URL is outside its product pages, the host is an IP address, or it doesn't resolve to a public address | | `INVALID_INPUT` | A missing or unknown field, or a URL that isn't HTTPS | | `RATE_LIMITED` | More than 30 quotes a minute from one address | ## How the paid request uses the quote Before asking for payment, AisleRun checks the paid body against the quote. If the product URL, store or profile differ, it answers `409 QUOTE_SCOPE_MISMATCH`. If the quote expired, it answers `410 QUOTE_EXPIRED`. Neither asks for payment. A quote can buy exactly one run. Using it again with a different idempotency key gets `409 IDEMPOTENCY_CONFLICT`. --- # Paying with PayBox > How PayBox pays the x402 request, what the agent gets back, and what happens when a payment fails or is retried. Source: https://aislerun.vercel.app/docs/paying-with-paybox AisleRun doesn't take cards or API keys. A run is paid for with one x402 request, and [PayBox](https://docs.paybox.sh) is how your agent makes that payment without ever holding a private key. PayBox enforces the approval mode and limits you set on your wallet, and AisleRun only ever sees a signed payment. ## The tool to use Use PayBox's `use_service` tool. It makes the request, reads the 402, pays, retries, and hands the paid response back to the agent. ```text use_service({ credential_id: "", url: "https://aislerun.158-220-85-120.sslip.io/v1/inspections", method: "POST", body: { "quote_id": "…", "product_url": "…", "authorized_target_id": "…", "profile": "shopify_checkout_entry_v1", "idempotency_key": "" } }) ``` PayBox sends `body` only on the paid retry, so the unpaid first request is just the price check. ## What PayBox returns to the agent PayBox returns the response body, not the headers. That's why everything the agent needs is in the body: the run ID, the read token, and both URLs. On success the PayBox result looks like this (trimmed): ```json { "status": "success", "output": { "value": { "payment": { "status": "succeeded", "network": "eip155:8453", "scheme": "exact" }, "response": { "status": 200, "content_type": "application/json; charset=utf-8", "body": "{\"ok\":true,\"inspection_id\":\"…\", …}" } } } } ``` ## What we observed | Trial | Result | | --- | --- | | Wallet unfunded, signing key not paired | `pending_signature`, `signer_unavailable`. Nothing paid, nothing ran | | Funded and paired | Paid and answered in about 6 seconds | | Five paid runs | Each settled on Base within about a second of being verified | All of these used PayBox's `autonomous` approval mode. The `iframe` and `always_approve` modes, where you confirm each payment, haven't been tried yet. ## If the payment doesn't go through | What happened | What AisleRun does | | --- | --- | | The wallet can't pay | The facilitator rejects the signature. No payment or run is recorded | | The payment is signed but the request is wrong | Refused before settlement, so the payment is cancelled and nothing is charged | | The payment fails to settle | The run becomes `PAYMENT_DENIED` and no browser starts | | The agent retries the same request | It gets the same run back without a new charge. See [Payments, retries and refunds](https://aislerun.vercel.app/docs/payments-and-refunds) | ## Discovery AisleRun works with `use_service` by URL. It isn't listed in the PayBox marketplace, and it won't appear in `discover_services`, which only lists services with at least 10 different payers in 30 days. --- # Reading results > Poll the run, read its event stream, and open the report with recordings, screenshots and traces. Source: https://aislerun.vercel.app/docs/reading-results Reading a run is free. Every read needs the run's read token, which comes back in the paid response. There are three views of the same run: its status, its event stream, and the full report. ## The read token The token is `read_token` in the paid response. Send it as a bearer token. ```text Authorization: Bearer rt_… ``` The same token is in `report_url` after the `#`, so the browser keeps it to itself and it never reaches a server log. Retrying the same paid request returns the same token, which is why a lost response isn't a lost run. ## Status ```sh curl -H "Authorization: Bearer rt_…" https://aislerun.158-220-85-120.sslip.io/v1/inspections/{id} ``` | Field | Meaning | | --- | --- | | `status` | Where the run is. See [How a run works](https://aislerun.vercel.app/docs/how-it-works#the-statuses-you-ll-see) | | `terminal` | `true` once the run won't change again | | `overall_verdict` | `PASS`, `FAIL` or `INCONCLUSIVE` once terminal, otherwise `null` | | `viewports` | Each viewport's verdict, blocker and attempt count | | `last_event` | The most recent thing the worker recorded | | `elapsed_ms` | Time since the worker started | | `poll_after_ms` | `2000` until terminal | Our paid runs finished 7 to 19 seconds after settlement. Two seconds between polls matches that. ## Events The event stream is what the worker wrote down as it went, in order. Use the cursor to read only what's new. ```sh curl -H "Authorization: Bearer rt_…" \ "https://aislerun.158-220-85-120.sslip.io/v1/inspections/{id}/events?after=14" ``` ```json { "events": [ { "seq": 15, "type": "step_started", "details": { "viewport": "mobile", "step": "confirm_cart" } }, { "seq": 16, "type": "step_finished", "details": { "viewport": "mobile", "step": "confirm_cart", "observed": "Add to cart was clicked but the cart did not change." } }, { "seq": 17, "type": "retrying", "details": { "viewport": "mobile", "cause_effect": "Click Add to cart → cart state unchanged → retrying once" } } ], "next_cursor": 17 } ``` ## The report `GET /v1/reports/{id}` returns the whole run once it's finished. | Part | What's in it | | --- | --- | | `viewports[].steps` | Each step's expected outcome, what was observed, the result, timings, and its offset into the recording | | `viewports[].recording` | The recording of the final attempt, with a poster image | | `viewports[].artifacts` | Every screenshot, recording and trace, each with its size and SHA-256 | | `viewports[].console_errors` | Raw console errors from the page. Shown as evidence, never as a diagnosis | | `payment` | Amount, network and the settlement transaction on Base | | `fixture_disclosure` | Present when the store is AisleRun's test store | | `limitations` | What this result can't tell you | Every evidence URL in a report is signed and stops working after 30 minutes. Fetch the report again for fresh links. Recordings support HTTP range requests, so video players can seek. ## In the browser Open `report_url` to see the same run as a page: the recording beside the first blocker, steps you can click to jump the recording, and the payment. While the run is still going, the page shows what's happening now, what it's waiting on, and what comes next, all from the event stream. --- # Verdicts and blockers > What pass, fail and inconclusive mean, every blocker code, and why a service error never counts against the store. Source: https://aislerun.vercel.app/docs/verdicts Each viewport gets its own verdict, and the run's overall verdict is the worst of them. A verdict comes from fixed rules over what the browser observed. No language model writes or adjusts it. ## The three verdicts | Verdict | When | | --- | --- | | Pass | The cart count and the cart contents both changed, and the checkout page opened | | Fail | Add to cart did nothing in two separate clean browsers, or the item reached the cart but checkout couldn't be opened | | Inconclusive | The journey couldn't be judged, for example a bot check or an unavailable product | The overall verdict is Fail if any viewport failed, Inconclusive if any viewport couldn't be judged, and Pass only when both passed. ## Blockers | Code | Verdict | What the browser saw | | --- | --- | --- | | `ADD_TO_CART_NO_EFFECT` | Fail | Add to cart was pressed and neither cart signal changed, twice | | `CHECKOUT_ENTRY_BLOCKED` | Fail | The item was in the cart but no checkout control was reachable, or pressing it didn't open checkout | | `BROWSER_BLOCKED` | Inconclusive | The page answered with a bot check or an access refusal | | `PRODUCT_UNAVAILABLE` | Inconclusive | The product or every variant was out of stock | | `UNSUPPORTED_MARKUP` | Inconclusive | No recognisable Add to cart control, or only one of the two cart signals changed | | `NAVIGATION_FAILED` | Inconclusive | The page didn't load or returned an error status | | `TARGET_LEFT_ALLOWLIST` | Inconclusive | The page redirected off the authorized store, so the run stopped | ## Why a cart failure needs two attempts A single failed click can be a slow network or a race in the page. When the cart doesn't change, AisleRun runs the whole journey again in a new browser with nothing carried over. It only reports Fail if the second attempt fails the same way. The report says how many attempts there were and keeps the recordings of both. ## Why a cart change needs two signals A click alone proves nothing. AisleRun waits up to 4 seconds for the cart count to go up and a cart line to appear. If only one of them changes, the result is Inconclusive, not Pass. ## Service errors are not verdicts If AisleRun itself breaks, the run ends as `FAILED_INTERNAL`, or `CANCELLED` if it hits the 150-second limit. These have no store verdict and never count against the store. ## Observed versus cause The report keeps what happened apart from why. "Add to cart was clicked but the cart did not change" is an observation. A console error like `Cannot set properties of null (setting 'hidden')` is shown as a possible cause, word for word, and AisleRun doesn't claim a root cause it hasn't proven. --- # Retests and comparisons > Pay for a second run after a fix and compare the two side by side, recording against recording. Source: https://aislerun.vercel.app/docs/retests A retest is a second, separately paid run of the same product page after you change something. The two runs stay separate, each with its own recording, steps and payment, and the comparison puts them side by side. ## Run a retest Ask for a new quote and pay for it the same way as the first run. A quote buys exactly one run, so the retest always has its own quote, its own idempotency key and its own settlement. ## Compare two runs Open the comparison in the browser: ```text https://aislerun.vercel.app/runs/{before}/compare/{after}#before=rt_…&after=rt_… ``` The page needs both runs' read tokens. An agent can read the same comparison as JSON: ```sh curl -H "X-Read-Tokens: rt_before…,rt_after…" \ https://aislerun.158-220-85-120.sslip.io/v1/comparisons/{before}/{after} ``` ## What can be compared | Rule | Why | | --- | --- | | Same store origin and the same product | Otherwise the difference could be the page, not the fix | | Different runs | A run compared with itself proves nothing | | The second run is newer | A retest comes after the change | | Both runs finished with stored evidence | There's nothing to compare otherwise | The store version can differ between the two runs. On the test store that difference is the fix itself. ## What the comparison shows - Both recordings, each with its own controls. "Play both from the start" starts them together, but they aren't synchronised frame by frame. - A table of what changed: each viewport's verdict, whether the cart was confirmed, whether checkout opened, attempts, run time and both settlements. - On the test store, the actual code change between the broken and fixed versions. ## The published example [Before and after](/demo/compare) compares run `2c076b7f` on the broken store, where mobile failed twice, with run `f3f13c4c` on the fixed store, where both viewports reached checkout. Each was paid for separately through PayBox. > A pass after a fix means this journey reached checkout in this run. It doesn't prove every buyer can now check out, and AisleRun makes no claim about recovered revenue. --- # Authorized stores > Which stores AisleRun will test, why a public URL isn't permission, and how the browser is fenced off from everything else. Source: https://aislerun.vercel.app/docs/authorized-stores AisleRun only tests stores its operator has explicitly authorized. A URL being public isn't permission. Automated shopping on someone else's store without their consent is a problem whether or not the page is reachable. ## Who's authorized today During the MoonPay hackathon the only authorized store is AisleRun's own [test store](https://aislerun.vercel.app/docs/test-store), one entry per version. `GET /v1/meta` lists them. ```sh curl https://aislerun.158-220-85-120.sslip.io/v1/meta ``` Each authorized store is an origin plus a product path prefix, like `https://aislerun-fixtures.vercel.app` and `/v/fixture-fixed-v2/products/`. A quote for any other origin or path is refused. ## What a URL has to pass | Check | Refused when | | --- | --- | | Scheme | It isn't HTTPS | | Credentials | The URL has a username or password in it | | Port | It isn't the default HTTPS port | | Host | It's an IP address instead of a name | | Origin and path | It isn't the authorized origin, or it's outside the product prefix, including encoded `..` tricks | | DNS | The name resolves to a private, loopback, link-local or reserved address | All of this happens at the quote and again before payment, so a refused store never costs anything and never starts a browser. ## How the browser is fenced in The checks above stop a bad URL from being accepted. Three more layers stop the browser from reaching anything else once it's running. 1. **Every request is filtered.** Each page, script, image and redirect the browser asks for is checked against the authorized origin. Anything else is blocked and listed in the report under blocked requests. 2. **Redirects are checked.** If the page lands somewhere off the authorized store, the run stops as Inconclusive with `TARGET_LEFT_ALLOWLIST`. 3. **The network itself is fenced.** In production the browser's only route out is a network whose firewall drops private, loopback, link-local and cloud-metadata addresses, so even Chromium's own DNS can't reach them. A request to `169.254.169.254` from inside the container times out. ## What the browser never does - Log in, create an account or read an account page. - Type anything into checkout, payment or shipping forms. - Submit an order. - Get past CAPTCHAs or bot checks. Those end the run as [Inconclusive](https://aislerun.vercel.app/docs/verdicts). - Treat text on the store's page as instructions. Page content is data, never a command. > Testing real merchant stores would need proof of ownership, such as a DNS or HTML verification, plus the merchant's consent. That isn't built yet. See [Limitations](https://aislerun.vercel.app/docs/limitations). --- # Payments, retries and refunds > The price, when money moves, how a retried request avoids a second charge, and what happens to a payment if a run fails. Source: https://aislerun.vercel.app/docs/payments-and-refunds One run costs 0.05 USDC, paid on Base through x402. Money only moves when a paid request is valid and the run can start, and a retried request never charges twice. ## The price | | | | --- | --- | | Price per run | 0.05 USDC (50,000 atomic units) | | Network | Base mainnet, `eip155:8453` | | Asset | USDC, `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | | Scheme | x402 v2 `exact` | | Network gas | Paid by the facilitator, not by you | `GET /v1/meta` returns the live price. The price covers both viewports. ## When money moves Nothing is charged for a quote, a refusal, a status read or a report. A payment is only settled after AisleRun has accepted the request and created the run. If anything fails between the payment being verified and the run being created, the payment is cancelled before it settles. ## Retries and duplicates Send the same body again, with the same quote and the same `idempotency_key`, and you get the original run back, with `"replayed": true` and no payment request. This is safe to do when a response was lost. | You send | You get | | --- | --- | | Same quote, same key, same body | The original run, no charge | | Same quote, different key | `409 IDEMPOTENCY_CONFLICT`, no charge | | Two paid requests for one quote at the same moment | One run. The second payment is cancelled before it settles | ## When a run fails after you paid | What happened | What you get | | --- | --- | | The store failed | A Fail verdict. That's the product working | | The page couldn't be judged | An Inconclusive verdict with the reason | | The worker crashed | The same run is retried once on the same payment, with no new charge | | It crashed again, or hit the 150-second limit | `FAILED_INTERNAL` or `CANCELLED`, with no store verdict | > Refunds aren't automatic. A payment for a run that ended as a service error is recorded with its settlement, and a refund is handled by hand. ## Abuse limits | Limit | Value | | --- | --- | | Quotes | 30 a minute per address | | Paid requests | 120 a minute per address | | Report reads | 240 a minute per address | | Queue | 10 waiting runs. Beyond that, new paid requests are refused with `RATE_LIMITED` before any payment is requested | --- # Privacy and evidence > Who can read a report, how long evidence links last, what is published, and what AisleRun never collects. Source: https://aislerun.vercel.app/docs/privacy-and-evidence A run's report is private to whoever holds its read token. Evidence is stored on a private volume and served through links that expire. The operator can publish chosen runs, and nothing else becomes public. ## Who can read a run Only someone with the run's read token. The token is returned once in the paid response, and again if the same request is retried. Without it, every read returns `401 REPORT_ACCESS_DENIED`. A comparison needs both runs' tokens. The token sits after the `#` in `report_url`. Browsers never send that part to a server, so it doesn't show up in access logs or in the `Referer` header. AisleRun's pages also set `Referrer-Policy: no-referrer`. ## Evidence links | Property | Value | | --- | --- | | Where files live | A private disk volume, never a public bucket | | How they're served | Through the API, one signed link per file | | Link lifetime | 30 minutes. Reload the report for fresh links | | Scope | A link only works for its own run and file. Changing either breaks the signature | | Integrity | Every file's size and SHA-256 are stored with it | ## Published runs The operator can publish selected runs as a demo. Published runs are served through separate `/v1/demo` routes that never return a read token. Today the published runs are the four paid runs on the [test store](https://aislerun.vercel.app/docs/test-store), listed on the [proof page](https://aislerun.vercel.app/docs/proof). ## What AisleRun never collects - Wallet keys, seed phrases or PayBox secrets. PayBox signs and AisleRun only sees the signed payment. - Card numbers or any payment details. - Shopper accounts, logins or personal data. The browser starts empty every time. ## What AisleRun does store The payer's wallet address and the settlement transaction, both public on Base. The product URL that was tested. The recordings, screenshots, traces, steps and console errors from the run. Request IDs in the server logs. Retention is manual for now, and files are deleted by hand. --- # Limitations > What a run can't tell you, and the things AisleRun doesn't do yet. Source: https://aislerun.vercel.app/docs/limitations AisleRun answers one narrow question well: could a shopper get this product into the cart and reach checkout, in this browser, right now? Here's what a run can't tell you, and what isn't built yet. ## What a result can't tell you - **Checkout completion.** The run stops at the checkout entrance. Payment forms, shipping, discounts and order placement are never touched. - **Every buyer's experience.** A pass is one run in Chromium on two screen sizes. Other browsers, devices, locations and logged-in shoppers can behave differently. - **Why something broke.** AisleRun reports what it observed and shows console errors as raw evidence. It doesn't claim a root cause. - **Revenue.** A failed run doesn't measure lost sales, and a passing retest doesn't measure recovered ones. ## What isn't built yet | Missing | Today | | --- | --- | | Real merchant stores | Only AisleRun's own [test store](https://aislerun.vercel.app/docs/test-store) is authorized | | Store ownership checks | No DNS or HTML verification yet | | Other journeys | One profile, `shopify_checkout_entry_v1` | | Other browsers | Chromium only | | Automatic refunds | Payments for service errors are refunded by hand | | PayBox marketplace listing | Reachable through `use_service` by URL, not listed in the marketplace | | Other approval modes | Only tested with PayBox's `autonomous` mode | | Evidence retention | Kept until deleted by hand | ## Hosting The web app runs on Vercel. The API, worker, Chromium and Postgres run as one Docker stack on a shared server, at an `sslip.io` hostname rather than a custom domain. --- # API > Base URL, every endpoint, request and response shapes, read tokens and the error codes. Source: https://aislerun.vercel.app/docs/api The AisleRun API takes quotes, sells runs over x402, and serves results. All responses are JSON and every response carries a `request_id`. ## Base URL ```text https://aislerun.158-220-85-120.sslip.io ``` There's no API key. The only paid route is `POST /v1/inspections`. Reads are authorized by each run's read token. ## Endpoints | Method and path | Cost | Auth | Purpose | | --- | --- | --- | --- | | `GET /v1/meta` | free | none | Price, network, authorized stores, profiles | | `POST /v1/quotes` | free | none | Get a quote | | `POST /v1/inspections` | 0.05 USDC | x402 | Buy and start a run | | `GET /v1/inspections/{id}` | free | read token | Status | | `GET /v1/inspections/{id}/events?after=` | free | read token | Event stream | | `GET /v1/reports/{id}` | free | read token | Full report | | `GET /v1/reports/{id}/assets/{assetId}` | free | signed link | One evidence file, with range support | | `GET /v1/comparisons/{before}/{after}` | free | both read tokens | Before and after | | `GET /v1/demo/runs`, `/v1/demo/reports/{id}`, `/v1/demo/comparison` | free | none | Published runs only | | `GET /healthz`, `GET /readyz` | free | none | Process health, and database plus worker readiness | ## Buy a run ```http POST /v1/inspections Content-Type: application/json PAYMENT-SIGNATURE: { "quote_id": "2d53bc4b-0464-4c6f-adba-8bffcbeab298", "product_url": "https://aislerun-fixtures.vercel.app/v/fixture-fixed-v2/products/cup", "authorized_target_id": "fixture-fixed-v2", "profile": "shopify_checkout_entry_v1", "idempotency_key": "6f1c2a9e-3c0b-4d51-9b8e-7a4f1e2d9c10", "client_reference": "optional, up to 128 characters" } ``` Without `PAYMENT-SIGNATURE` the answer is `402` with a `PAYMENT-REQUIRED` header. Decoded, it asks for: ```json { "x402Version": 2, "accepts": [{ "scheme": "exact", "network": "eip155:8453", "amount": "50000", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0x50b174FAF12a7c7D6618Ac65D71DF613fEa0F57f", "maxTimeoutSeconds": 120 }] } ``` With a valid payment the answer is `200` with the run. See [Order your first run](https://aislerun.vercel.app/docs/quickstart#2-pay-for-the-run-with-paybox) for the full body. ## Errors ```json { "ok": false, "code": "TARGET_NOT_AUTHORIZED", "message": "Target not authorized: origin is not authorized for this target.", "retryable": false, "request_id": "req_…" } ``` | Code | Status | Meaning | | --- | --- | --- | | `INVALID_INPUT` | 400 | The body is malformed or has unknown fields | | `TARGET_NOT_AUTHORIZED` | 403 | The store or URL isn't authorized | | `QUOTE_EXPIRED` | 410 | The quote is older than 15 minutes | | `QUOTE_SCOPE_MISMATCH` | 404, 409 | Unknown quote, or the body doesn't match it | | `PAYMENT_UNVERIFIED` | 402 | No valid payment, or the amount doesn't match the quote | | `IDEMPOTENCY_CONFLICT` | 409 | The quote was already used by a different request | | `JOB_NOT_FOUND` | 404 | No such run | | `REPORT_ACCESS_DENIED` | 401, 403 | Missing or wrong read token, or an expired evidence link | | `RATE_LIMITED` | 429 | Too many requests, or the queue is full. Includes `Retry-After` | | `INTERNAL_FAILURE` | 500 | AisleRun's own error. `retryable` is `true` | Everything that can be refused before payment is refused without a `402`. ## Browsers and CORS Read routes allow cross-origin requests only from `https://aislerun.vercel.app`. Agents and servers aren't affected by CORS. --- # The test store > AisleRun's own labelled fixture store, its four versions and the seeded defect. Source: https://aislerun.vercel.app/docs/test-store The test store is a small storefront AisleRun built for itself at `https://aislerun-fixtures.vercel.app`. It looks and behaves like a Shopify product page, and every page says in its footer that it's a test fixture. It's the only store AisleRun is authorized to test today. ## The four versions Each version lives at its own fixed path, so a result always names exactly what it tested. | Version | What it does | Expected result | | --- | --- | --- | | `fixture-healthy-v1` | Normal product, cart and checkout | Pass on both viewports | | `fixture-mobile-cart-broken-v1` | Add to cart throws an error below 750 px wide | Mobile fails, desktop passes | | `fixture-fixed-v2` | The one-line repair of that bug | Pass on both viewports | | `fixture-bot-check-v1` | A human-verification wall instead of the product | Inconclusive on both viewports | Product pages are at `/v//products/cup`, and the cart and checkout pages follow the same pattern. ## The seeded defect On the broken version, the Add to cart handler looks for a cart drawer that doesn't exist on narrow screens. It throws before the cart is saved, so nothing happens when a phone shopper presses the button. The fix makes both screen sizes use the same drawer. ```diff - var isMobile = window.matchMedia("(max-width: 749px)").matches; - return document.getElementById(isMobile ? "CartDrawer-mobile" : "CartDrawer"); + return document.getElementById("CartDrawer"); ``` The browser console shows the real error: `Cannot set properties of null (setting 'hidden')`. AisleRun reports it as a possible cause, not a diagnosis. ## How the store tracks the cart The cart lives in the browser's local storage, keyed by version. The header shows a cart count and the cart drawer lists each line, which gives AisleRun its two cart signals. The checkout page collects nothing and has no submit button. ## Results so far Every version has been run through a real paid inspection. See [Live proof](https://aislerun.vercel.app/docs/proof). --- # Live proof > Every paid run so far, with its settlement on Base and what the browser found. Source: https://aislerun.vercel.app/docs/proof Every claim in these docs is backed by a run you can open. These are all the paid runs so far, each bought through PayBox and settled in USDC on Base. ## Paid runs | Run | Store version | Mobile | Desktop | Settlement | | --- | --- | --- | --- | --- | | [2c076b7f](/demo/runs/2c076b7f-3613-41c7-a5e3-6c46d7628f76) | broken | Fail, `ADD_TO_CART_NO_EFFECT`, 2 attempts | Pass | [0xfb722e65…](https://basescan.org/tx/0xfb722e65302dc65afee313efbfa158a9af72e0ca51ea707e091d17bc3cc8d7e9) | | [f3f13c4c](/demo/runs/f3f13c4c-e02d-441d-b08e-718a52c7b2eb) | fixed | Pass | Pass | [0x0c9d639b…](https://basescan.org/tx/0x0c9d639b2864fae449f42520e26c8c9ff8cc4d9bdcc47e7992fa40c16bda0757) | | [a06faa99](/demo/runs/a06faa99-9443-41f2-8894-32140699ef4a) | healthy | Pass | Pass | [0x1423cda7…](https://basescan.org/tx/0x1423cda7cf560ec6cbad24bce13150e36ff503ec5b33ceeb0fbd34233b35b7fa) | | [aaebdec9](/demo/runs/aaebdec9-46b2-4bd3-8665-7ae67541b656) | bot check | Inconclusive, `BROWSER_BLOCKED` | Inconclusive, `BROWSER_BLOCKED` | [0x4229a68b…](https://basescan.org/tx/0x4229a68b5c2d82ff9e9c87c9cb96f92f63b8c39c16109ae1692dd72dfec30f2e) | | fad7f747 | broken, after moving hosts | Fail, `ADD_TO_CART_NO_EFFECT` | Pass | [0x78b3576c…](https://basescan.org/tx/0x78b3576ceddee01e061fc7474d7c790adbbe2ab1d84a55601b30bbfa18aa3623) | The last run is private. It was the regression check after production moved to its current server. ## The first PayBox payment Before any browser work existed, a 0.01 USDC test route was paid through PayBox to confirm the integration. It settled in [0xac05f7d1…](https://basescan.org/tx/0xac05f7d1597a7197495c86fa39d7a2abcd6afac62238c9b02b76025e30f9e8d8), Base block 52692877. ## Timings | Run | Verified to settled | Settled to first step | Settled to finished | | --- | --- | --- | --- | | 2c076b7f | 0.9 s | 3.2 s | 18.5 s | | f3f13c4c | 0.9 s | 1.8 s | 6.8 s | | a06faa99 | 1.1 s | 2.4 s | 7.4 s | The failing run takes longer because the mobile journey waits 4 seconds for the cart to change, twice. Three runs aren't enough for a p95, so none is given. ## Check it yourself - Open any run above to watch the recording and read each step. - Open a settlement on Basescan and check the transfer of 0.05 USDC to `0x50b174FAF12a7c7D6618Ac65D71DF613fEa0F57f`. - Compare the broken and fixed runs on [Before and after](/demo/compare). - Read the live price and authorized stores from [`/v1/meta`](https://aislerun.158-220-85-120.sslip.io/v1/meta). --- # FAQ > Direct answers to the questions people ask about AisleRun, PayBox and the results. Source: https://aislerun.vercel.app/docs/faq ## What does AisleRun actually do? It opens a product page in Chromium on a phone and a desktop, adds the item to the cart, checks the cart really changed, and opens checkout without submitting anything. Then it tells you, per screen size, whether that worked, with the recording to prove it. ## Does it buy anything? No. It never fills in checkout, payment or shipping details and never places an order. The run stops at the checkout entrance. ## Why does an AI agent need to pay for it? So the agent looking after a store can order a test when it needs one, without an account, an API key or a subscription. PayBox lets the agent pay from your wallet under the limits you set, and AisleRun only accepts a payment that a facilitator has verified. ## How much does a run cost? 0.05 USDC on Base, for both screen sizes. Network gas is covered by the facilitator. `GET /v1/meta` returns the live price. ## How long does it take? In our runs PayBox paid in about 6 seconds, the payment settled about a second later, and the run finished 7 to 19 seconds after that. A failing run takes longer because AisleRun repeats it to make sure. ## Can I test my own store? Not yet. During the hackathon only AisleRun's own [test store](https://aislerun.vercel.app/docs/test-store) is authorized. Testing real stores needs proof that you own them, which isn't built. ## Is a pass a guarantee my checkout works? No. A pass means this journey reached checkout once, in Chromium, on two screen sizes. See [Limitations](https://aislerun.vercel.app/docs/limitations). ## Why did a run come back inconclusive instead of failed? Because AisleRun couldn't judge it. Bot checks, out-of-stock products, unfamiliar page controls and pages that don't load are all inconclusive. AisleRun only says Fail when it saw the store break, twice. See [Verdicts and blockers](https://aislerun.vercel.app/docs/verdicts). ## What if AisleRun itself breaks during my run? The run is retried once on the same payment. If it breaks again, it ends as a service error with no store verdict. You aren't charged twice, and refunds for service errors are handled by hand. ## What if my agent sends the paid request twice? It gets the same run back without a second charge, as long as the quote and idempotency key are the same. See [Payments, retries and refunds](https://aislerun.vercel.app/docs/payments-and-refunds). ## Who can see my results? Only someone with the run's read token, which comes back once in the paid response. Evidence links expire after 30 minutes. See [Privacy and evidence](https://aislerun.vercel.app/docs/privacy-and-evidence). ## Is AisleRun in the PayBox marketplace? No. Any PayBox agent can use it through `use_service` by URL, but it isn't listed in the marketplace and doesn't show up in `discover_services`. ## Does a language model decide the verdict? No. The journey is a fixed script and the verdict comes from fixed rules. The only AI involved is the agent that orders the run. --- # Glossary > Plain-word definitions of the terms in the app and these docs. Source: https://aislerun.vercel.app/docs/glossary ## Authorized store A store origin and product path that the operator has allowed AisleRun to test. See [Authorized stores](https://aislerun.vercel.app/docs/authorized-stores). ## Blocker The first step that stopped the shopper, with a code such as `ADD_TO_CART_NO_EFFECT`. See [Verdicts and blockers](https://aislerun.vercel.app/docs/verdicts). ## Checkout entry The first page of checkout. A run reaches it and stops. Nothing is filled in or submitted. ## Clean browser A new Chromium context with no cookies, storage or history. Every attempt uses one. ## Event One thing the worker recorded while a run was going, such as a step starting or finishing. Events are what the live view and the report are built from. ## Facilitator The service that checks an x402 payment and submits it to the blockchain. AisleRun uses PayAI's facilitator. ## Fixture A version of AisleRun's own [test store](https://aislerun.vercel.app/docs/test-store), such as `fixture-mobile-cart-broken-v1`. ## Idempotency key A value you choose for one paid request. Sending the same request with the same key returns the original run instead of starting and charging for a new one. ## Inconclusive A result that means the journey couldn't be judged. It's never counted as a store failure. ## PayBox MoonPay's wallet for AI agents. It holds your keys and limits and pays x402 requests for your agent. See [Paying with PayBox](https://aislerun.vercel.app/docs/paying-with-paybox). ## Quote A free, 15-minute offer for one run of one product page at a fixed price. See [Quotes](https://aislerun.vercel.app/docs/quotes). ## Read token The secret that lets you read one run. It comes back in the paid response and starts with `rt_`. ## Retest A second, separately paid run of the same product page, usually after a fix. See [Retests and comparisons](https://aislerun.vercel.app/docs/retests). ## Service error A run that ended because AisleRun failed, shown as `FAILED_INTERNAL` or `CANCELLED`. It has no store verdict. ## Settlement The moment a payment lands on Base. AisleRun queues a run only after settlement. ## Use service PayBox's `use_service` tool. It calls a paid URL, pays the 402, and returns the paid response to the agent. ## Viewport The screen size a run uses: mobile at 390 × 844 or desktop at 1440 × 900. ## x402 An open protocol for paying for an HTTP request. The server answers `402 Payment Required` with a price, and the client retries with a signed payment.