# cart.fun for agents cart.fun is on-chain commerce: stores sell for stablecoins through a CartCheckout contract, and every sale prints a receipt NFT (proof of purchase, warranty, refunds) plus a cart NFT holding any on-chain goods. Buyer agents discover stores, get signed quotes and pay on-chain. Merchant agents run a store's catalog, invites, sales log and webhooks with API keys. Base URL: `https://go.cart.fun`. Everything is JSON over HTTPS. | Network | Chain id | Slug for URLs | | --- | --- | --- | | Base Sepolia | 84532 | `base-sepolia` | | Robinhood Chain Testnet | 46630 | `robinhood-testnet` | | Arc Testnet | 5042002 | `arc-testnet` | - [OpenAPI 3.1 spec](https://go.cart.fun/openapi.json): every endpoint, auth, request and response schema - [Agent skill](https://go.cart.fun/skill.md): drop-in SKILL.md for Claude Code and other skill-aware agents - [Full guide](https://go.cart.fun/llms-full.txt): this whole document set as one file - [Example agent card](https://go.cart.fun/api/v1/stores/base-sepolia/3/agent): a live store's discovery document ## Workflows ### Buy something 1. **Discover the store.** `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/agent` returns its agent card. Read `cartfun.commerce`: `orderable`, `payment` (token, symbol, decimals), `checkout` (contract, version), and for public stores `catalog.products` with `price` (decimal) and `priceUnits` (base units). Invite-only stores list no products; ask the merchant for an API key with the `orders` scope. 2. **Quote.** `POST https://go.cart.fun/api/v1/orders` with `{ chainId, storeId, buyer, lines: [{ id: sku, quantity }] }`. The server prices the lines from the catalog and returns an order signed by the store, valid for 10 minutes and fillable only by `buyer`. 3. **Approve.** From the buyer wallet, approve the checkout contract (`quote.checkout`) to spend `order.amount + order.serviceFee` of `order.paymentToken`. Tokens with EIP-2612 permit can use `checkoutWithPermit` instead. 4. **Pay.** Call `checkout(order, items, cartRecipient, signature)` on `quote.checkout` with the quote's `order`, `items` and `signature` exactly as returned (numbers as uint256). `cartRecipient` receives the cart NFT, usually the buyer. On a version 1 checkout, drop `serviceFee` from the order struct. 5. **Confirm.** The `CheckoutCompleted` event carries the `receiptId`. `GET https://go.cart.fun/api/v1/receipts/{chain}/{receiptId}` shows the receipt; `GET …/order` with header `x-order-token: ` shows its line items. ```ts import { createWalletClient, http, parseAbi, erc20Abi } from "viem"; // wallet: a viem WalletClient for the buyer's account on the store's chain const API = "https://go.cart.fun"; const CHECKOUT_ABI = parseAbi([ "struct Order { uint16 storeId; address buyer; address paymentToken; uint256 amount; uint256 serviceFee; bytes32 itemsHash; uint256 nonce; uint256 deadline; }", "struct Item { uint8 kind; address token; uint256 id; uint256 amount; }", "function checkout(Order order, Item[] items, address cartRecipient, bytes signature) returns (uint256 receiptId, uint256 cartId)", ]); // version 1 checkouts: the same without serviceFee const quote = await fetch(`${API}/api/v1/orders`, { method: "POST", headers: { "Content-Type": "application/json" }, // + Authorization: Bearer cf_sk_… for invite-only stores body: JSON.stringify({ chainId: 84532, storeId: 3, buyer: account.address, lines: [{ id: 1, quantity: 1 }] }), }).then((r) => r.json()); const o = quote.order; const order = { ...o, amount: BigInt(o.amount), serviceFee: BigInt(o.serviceFee), nonce: BigInt(o.nonce), deadline: BigInt(o.deadline) }; const items = quote.items.map((i) => ({ ...i, id: BigInt(i.id), amount: BigInt(i.amount) })); await wallet.writeContract({ address: o.paymentToken, abi: erc20Abi, functionName: "approve", args: [quote.checkout, order.amount + order.serviceFee] }); await wallet.writeContract({ address: quote.checkout, abi: CHECKOUT_ABI, functionName: "checkout", args: [order, items, account.address, quote.signature] }); ``` ### Run a store 1. **Get a key.** The store owner creates one in the cart.fun dashboard (Developers tab), choosing its scopes. Send it as `Authorization: Bearer cf_sk_…` from a server, never a browser. Keys are per store and stop working when revoked or when the store changes owner. 2. **Catalog.** `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products` with `{ products: [{ name, type, price, active, onchain? }] }`; `PATCH …/products/{sku}`; `DELETE …/products/{sku}`. Prices are decimal strings in the store's payment token. 3. **Webhooks.** `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks` with `{ url, events? }` returns a signing secret once. Verify every delivery (below), dedupe on the event `id`, answer 2xx quickly. 4. **Sales.** `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/sales` lists receipts. After sending a refund, void or redeem on-chain, log it with `POST …/actions` so the reason shows in the dashboard. 5. **Access.** `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites` makes the store invite-only (6-digit codes); deleting every code opens it again. Agents with an `orders` key get through regardless. ```bash curl https://go.cart.fun/api/v1/stores/base-sepolia/3/products \ -H "Authorization: Bearer cf_sk_…" ``` ## Auth Public reads need nothing. Store APIs take an API key (`Authorization: Bearer cf_sk_…`) with the right scope. Creating and revoking keys takes the store owner's wallet session (`Authorization: CartSig `), which agents rarely need. | Scope | Grants | | --- | --- | | `orders` | Get quotes from, and read the catalog of, this store even while it's invite-only (agents, checkout servers) | | `catalog` | Read every product (hidden ones too) and create, edit and delete products | | `sales` | Read the refund/void/redeem log, log new actions, and see receipts' line items | | `invites` | List, create, edit and delete invite codes | | `webhooks` | Manage webhook endpoints, send test events and see deliveries | Errors are JSON `{ "error": "…" }` with a meaningful status: 400 bad input, 401 no or bad credential, 402 store not activated, 403 wrong store or missing scope, 404 not found, 409 conflict, 429 rate limited (honour `Retry-After`). Each key gets 120 requests a minute. ## Webhooks cart.fun POSTs a JSON envelope `{ id, type, created, chainId, storeId, data }` to your endpoint. On-chain events fire once their block has a few confirmations; failed deliveries retry with backoff for about two days. The `cartfun-signature` header is `t=,v1=.")>`. | Event | When | | --- | --- | | `order.quoted` | cart.fun signed a quote from your catalog (before payment) | | `order.paid` | A receipt was printed: the order is paid. Includes the order's items when cart.fun quoted it | | `receipt.refunded` | A receipt was marked refunded | | `receipt.voided` | A receipt was voided | | `receipt.redeemed` | A receipt was redeemed (picked up or used) | | `receipt.transferred` | A receipt's warranty moved to a new holder | | `receipt.serviced` | A service record was added to a receipt | | `review.created` | A buyer reviewed your store (ERC-8004), with whether it checked out against their receipt | | `review.replied` | Your store replied to a review | ```js // Node: verify a cart.fun webhook before trusting it import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyCartfun(rawBody, header, secret, toleranceSec = 300) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // stale: possible replay const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const given = Buffer.from(parts.v1 ?? "", "hex"); return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, "hex")); } // verifyCartfun(await req.text(), req.headers.get("cartfun-signature"), process.env.CARTFUN_WEBHOOK_SECRET) // Events can arrive more than once: dedupe on the body's "id" (also in the cartfun-event-id header). ``` ## Endpoints ### Discover - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/agent` (No auth): The store's ERC-8004 agent card. cartfun.commerce says whether it takes orders, what it's paid in, how to quote and pay, and (public stores) every orderable product with prices in base units. - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products` (Optional API key (orders)): The store's catalog. Invite-only stores need an API key with the orders scope (or a browser pass). A key with the catalog scope also sees hidden products. - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/reviews` (No auth): ERC-8004 reviews, each checked against the receipt it names, and the verified score. - `GET https://go.cart.fun/api/v1/signer` (No auth): The platform order signer. A store must register this address as its signer to get quotes. - `GET https://go.cart.fun/api/v1/service-fee` (No auth): The buyer service-fee policy on a network, to preview fees before quoting. Null where the checkout can't charge one. - `GET https://go.cart.fun/api/v1/stores/{chain}/summary` (No auth): Sales totals per store on a network, from the public receipt index. ### Buy - `POST https://go.cart.fun/api/v1/orders` (Optional API key (orders)): Prices the lines from the catalog and returns an order signed by the store. The buyer then pays it on-chain (see the checkout flow). Quotes expire after 10 minutes and only `buyer` can fill them. ### Receipts & reviews - `GET https://go.cart.fun/api/v1/receipts/{chain}/{id}` (No auth): A receipt NFT's on-chain record: store, purchaser, holder, amount paid and lifecycle state. - `GET https://go.cart.fun/api/v1/receipts/{chain}/{id}/order` (Optional API key (sales)): The order behind a receipt, proven by its on-chain commitment. Everyone gets the money breakdown; line items need the quote's x-order-token, the buyer's or holder's session, or an API key with the sales scope. - `GET https://go.cart.fun/api/v1/receipts/{chain}/{id}/svg` (No auth): The receipt's rendered image (SVG). - `GET https://go.cart.fun/api/v1/reviews/comment` (No auth): How long a review comment may be, and whether long ones can be pinned to IPFS here. - `POST https://go.cart.fun/api/v1/reviews/comment` (No auth): Pins a long review comment to IPFS and returns its ipfs:// URI and keccak256 hash for the review's feedback file. ### Catalog - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products` (API key or owner session (catalog), activated store): Creates products. Rejects the whole batch if any sku is taken. - `PATCH https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products/{sku}` (API key or owner session (catalog), activated store): Replaces a product's fields; the sku stays. - `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products/{sku}` (API key or owner session (catalog), activated store): Deletes a product. ### Sales - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/sales` (No auth): Every receipt the store has printed, newest first. Public: receipts are on-chain. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/sales/refresh` (No auth): Re-reads up to 50 of the store's receipts from chain now, after a redeem, refund or void lands. - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/actions` (API key or owner session (sales)): The store's refund, void and redeem log, with reasons and notes. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/actions` (API key or owner session (sales), activated store): Logs refunds, voids or redemptions you just sent on-chain, attributed to your key. ### Invites - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites` (API key or owner session (invites)): The store's invite codes. While a store has any, it's invite-only. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites` (API key or owner session (invites), activated store): Creates an invite code (generated when `code` is omitted). - `PATCH https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites/{code}` (API key or owner session (invites), activated store): Changes an invite's label, use limit, expiry or active flag. - `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites/{code}` (API key or owner session (invites), activated store): Deletes an invite code. Deleting every code makes the store public. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/access` (No auth): Checks an invite code for a browser and sets a 30-day pass cookie. Agents should use an API key with the orders scope instead. ### Webhooks - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks` (API key or owner session (webhooks)): The store's webhook endpoints and its 50 latest deliveries. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks` (API key or owner session (webhooks), activated store): Adds an endpoint. The response carries its signing secret, shown this once. - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}` (API key or owner session (webhooks)): One endpoint's 50 latest deliveries. - `PATCH https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}` (API key or owner session (webhooks)): Changes an endpoint's URL, description, events or active flag, or rotates its secret (the new one comes back once). - `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}` (API key or owner session (webhooks)): Deletes an endpoint and its delivery history. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}/test` (API key or owner session (webhooks)): Sends a `ping` event to the endpoint now. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/deliveries/{deliveryId}/retry` (API key or owner session (webhooks)): Sends a delivery again now, whatever happened to it before. ### Keys & activation - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/keys` (Owner wallet session): The store's API keys (never their secrets). Key management always takes the owner's wallet session. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/keys` (Owner wallet session, activated store): Creates a key. The response carries the secret, the only time it's returned. - `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/keys/{id}` (Owner wallet session): Revokes a key at once. - `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/activation` (No auth): Whether the store is activated for cart.fun orders, and the fee to activate it. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/activation` (No auth): Activates the store from its fee payment. The payment must come from the store's owner or treasury. - `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/agent` (No auth): Links a registered ERC-8004 agent to the store. Checked on-chain: the agent's cartfun.store metadata must name the store, and the store's owner must own the agent.