OpenPermit Docs
Guides

Agent Commerce Storefronts

Let agents quote and place storefront orders through OpenPermit without requiring MCP.

OpenPermit storefronts are cooperative merchant integrations. The merchant exposes a discovery manifest plus catalog, quote, checkout, and order APIs. Agents can then use direct HTTPS, the SDK, or MCP wrappers to buy under an active smart-account x402 mandate.

Merchant surface

The storefront publishes the same contract at the root or under a business page such as /shoes, /grocery, or /clothing:

  • GET /.well-known/openpermit-storefront.json
  • GET /agent-payments/start
  • GET /api/products
  • POST /api/checkout/quote
  • POST /api/checkout/orders
  • GET /api/orders/:orderId

For a business page, the URLs are scoped to that page, for example GET /grocery/.well-known/openpermit-storefront.json and POST /grocery/api/checkout/quote.

The checkout endpoint is protected with createSellerMiddleware. Missing payment returns 402 Payment Required; a paid retry creates the order. Browser-only agents can use the seller-hosted checkout-start route to prepare the same concrete checkout session without submitting JSON themselves.

Storefront manifests should include actions.checkoutStart, not a generic actions.buyerSetup. The checkout-start URL is seller-hosted and SDK-generated; it accepts q or query, quantity, optional items, and optional idempotencyKey. It quotes the basket, creates a seller checkout session, then returns the concrete setup link with seller/resource context, payment mode, resolved chain, asset, payee, token metadata, and merchantContinuationUrl:

{
	"actions": {
		"checkoutStart": {
			"type": "openpermit.checkoutStart",
			"method": "GET",
			"url": "https://storefront.example/agent-payments/start",
			"label": "Start checkout in browser",
			"acceptedParams": ["q", "query", "quantity", "items", "idempotencyKey"]
		}
	}
}

Seller SDK integrations should expose both pieces:

  • actions.checkoutStart from the manifest for browser-only agents.
  • buyerSetup from the concrete 402 checkout response for HTTP-capable agents.
import {
	createOpenPermitBuyerSetupAction,
	createSellerCheckoutStartAction,
	createSellerCheckoutStartResponse,
	parseSellerCheckoutStartRequest,
	createSellerMiddleware,
} from '@openpermit/sdk/seller';

const checkoutStart = createSellerCheckoutStartAction({
	url: 'https://storefront.example/agent-payments/start',
	label: 'Start checkout in browser',
});

createSellerMiddleware({
	client,
	sellerId,
	resourceId,
	paymentChallengeType: 'x402',
	paymentRequired: {
		buyerSetup: ({ request, challenge }) => createOpenPermitBuyerSetupAction({
			setupBaseUrl: 'https://openpermit.ai/buyer/setup',
			storefrontUrl: request.url,
			challenge,
			paymentMode: 'metamaskErc7715',
			merchantContinuationUrl: new URL('/agent-payments/continue/session_123?checkoutSession=checkout_session_123', request.url).toString(),
		}),
		agentInstructions: ({ buyerSetup }) =>
			`Open this OpenPermit setup link. After approval, OpenPermit returns the buyer to this store's continuation page: ${buyerSetup?.nextAction.url}`,
	},
});

The checkout-start route uses the same quote/session/setup code as checkout:

export async function GET(request: Request) {
	const start = parseSellerCheckoutStartRequest(request);
	const quote = await quoteBasket(start.items);
	const session = await createCheckoutSession(start.items, quote);
	const buyerSetup = createOpenPermitBuyerSetupAction({
		setupBaseUrl: 'https://openpermit.ai/buyer/setup',
		storefrontUrl: new URL('/', request.url).toString(),
		sellerId,
		resourceId,
		chain,
		asset: quote.asset,
		amount: quote.amount,
		paymentMode: 'metamaskErc7715',
		merchantContinuationUrl: session.continuationUrl,
		payTo,
		tokenAddress,
		tokenDecimals,
		tokenName: 'USDC',
		tokenVersion: '2',
	});

	return createSellerCheckoutStartResponse({
		request,
		sellerName: 'Example Store',
		items: start.items,
		idempotencyKey: start.idempotencyKey ?? session.idempotencyKey,
		quote,
		buyerSetup,
		merchantContinuationUrl: session.continuationUrl,
	});
}

The SDK keeps the x402 payment headers intact and adds a Link: <setupUrl>; rel="openpermit-buyer-setup" header plus a JSON body with buyerSetup and agentInstructions, so HTTP agents and LLM agents can handle the same response. Point setup URLs at /buyer/setup, not generic onboarding, so OpenPermit can preserve seller/resource/amount context through authentication and prefill the buyer mandate. Instructions should tell agents to include the raw setup URL in plain text, not only as a hyperlink label.

Autonomous browser checkout also requires a seller-hosted merchantContinuationUrl. Session-backed continuation URLs should include the seller's prepared checkout session, for example /agent-payments/continue/:sessionId?checkoutSession=.... After the buyer approves the OpenPermit mandate, the dashboard appends a scoped continuation token to that seller URL and opens it. The seller page then calls OpenPermit server-side and completes checkout without asking the generic agent to submit raw JSON to OpenPermit.

Setup links without resolved chain, payTo, tokenAddress, tokenDecimals, tokenName, tokenVersion, or merchantContinuationUrl can still help a buyer find OpenPermit, but they cannot become an autonomous ready state for that exact x402 checkout. Store those values on the seller resource or provider profile so the seller challenge carries them automatically.

Use paymentMode: 'metamaskErc7715' for the current autonomous browser-wallet path. OpenPermit stores a real MetaMask Advanced Permissions/ERC-7715 permission context for the payer, payee, token, and mandate, then issues x402 ERC-7710 credentials during continuation checkout. paymentMode: 'openpermit7702' remains an explicit future/advanced mode and should not be the default seller setup action.

Browser-first continuation

The default generic-agent path is browser-first:

  1. The agent reads actions.checkoutStart from the merchant manifest, or calls the merchant checkout if it can POST JSON.
  2. The merchant prepares a concrete basket/session and returns a seller-hosted OpenPermit setup link.
  3. The agent gives the setup link to the buyer.
  4. The buyer approves the mandate and MetaMask ERC-7715 permission in OpenPermit.
  5. OpenPermit creates a scoped continuation token and opens the seller merchantContinuationUrl.
  6. The seller continuation page completes checkout server-side and records the order/receipt.

Generic agents should not need to call OpenPermit's continuation API directly. The continuation API remains available for SDK-capable clients and seller servers:

POST /api/v1/agent/commerce/quote
POST /api/v1/agent/commerce/readiness
POST /api/v1/agent/commerce/continuations
POST /api/v1/agent/commerce/continue

Example request:

{
	"storefrontUrl": "https://storefront.example",
	"mandateId": "mandate_...",
	"idempotencyKey": "order-run-001",
	"items": [{ "query": "shoes", "color": "white", "size": "42", "quantity": 2 }]
}

Quoting remains free and does not require a wallet or payment credential. Before creating a continuation, call readiness with the mandate and seller scope. If readiness is ready, create a continuation token and append it to the seller-hosted merchantContinuationUrl. The seller server can then call continue with the original cart and idempotency key. OpenPermit authorizes the order under the active mandate and produces the x402-compatible credential for the seller retry.

POST /api/v1/agent/commerce/place-order remains available for direct callers that can supply a real wallet-signed PAYMENT-SIGNATURE. It is the manual mode, not the default generic-agent checkout path.

Continuation errors

Seller continuation pages should use the OpenPermit problem contract from the continuation response:

  • Branch on problem.code; do not parse MetaMask, facilitator, or RPC strings.
  • Use getOpenPermitProblemDisplay from @openpermit/sdk/errors to render title, description, details, and the primary next action.
  • Keep the raw OpenPermit response collapsed for support diagnostics.

Important codes for storefront checkout:

  • payment_payer_insufficient_balance: buyer must fund the payer account with the required token amount on the listed chain, then retry the same seller continuation.
  • payment_permission_missing, payment_permission_expired, payment_permission_stale: buyer must re-open the seller setup link and approve a fresh MetaMask ERC-7715 permission.
  • payment_execution_in_progress: render a pending page and retry the same continuation after the retry interval.
  • seller_quote_amount_mismatch: the seller continuation used a different quote/session than the mandate continuation. Retry from the checkout-start URL and preserve the seller checkout session.
  • seller_continuation_missing: the seller setup link is not autonomous-ready because it lacks a session-backed merchantContinuationUrl.

OpenPermit stores sanitized failure fields on payment executions (failure_code, failure_category, failure_actor, failure_detail, and failure_metadata) so support, dashboards, and future webhooks see the same normalized status as the seller page.

Demo app

The monorepo includes apps/storefront-demo, which demonstrates realistic merchant pages:

  • /shoes: Stride Supply, for the prompt "Buy me 2 pairs of white shoes, size 42."
  • /grocery: FreshCart Market, for grocery basket examples.
  • /clothing: Northline Apparel, for apparel examples with color and size constraints.

Each business page renders a realistic ecommerce storefront and exposes its own manifest, catalog, quote, checkout, and order APIs. The active x402 network comes from OpenPermit/storefront payment configuration, not from public checkout URLs.

On this page