ACP Checkout
Use this guide when building host-owned ACP endpoints around Mika semantics. It focuses on endpoint wiring; exact helper contracts live in ACP.
ACP helpers live under @bnomei/emdash-mika/acp. They serialize Mika catalog and sellable facts into product-feed shapes and expose checkout session handlers for host Astro endpoints.
ACP endpoints are host-owned. They need durable session storage with atomic idempotency coordination, official ACP request headers, host auth, and provider-backed order delivery.
Do not bake ACP field names into Mika core DTOs. ACP is a projection around Mika semantics.
Mika exports MIKA_ACP_API_VERSION with value 2025-09-12 and MIKA_ACP_DEFAULT_SESSION_PREFIX with value acp_checkout. The checkout helper does not mount routes and does not enforce an API-Version header by itself; host endpoints that implement OpenAI ACP should validate the official version header before calling the handler.
Prerequisites
Section titled “Prerequisites”- A host
apiobject that can quote and start checkout from Mika sellables. - Durable ACP checkout session storage with atomic idempotency claim, bind, and release methods for production.
- Host endpoint auth using
Authorization: Bearer <apiKey>or the host’s chosen signature protection. - Idempotency storage for write endpoints.
- Provider-backed order delivery, payment verification, and support policy.
Minimal Host Shape
Section titled “Minimal Host Shape”ACP wiring has two surfaces:
- A product feed endpoint that serializes Mika catalog/sellable facts.
- Checkout session endpoints that delegate to
createMikaAcpCheckoutHandlers().
Mika supplies helpers for those surfaces. The host mounts routes, validates official ACP headers, stores sessions, verifies callers, and handles production payment/provider obligations.
Product Feed
Section titled “Product Feed”Build an ACP product feed from Mika catalog facts, then serialize it from a host endpoint. Put this in a host-owned Astro endpoint, for example src/pages/acp/product-feed.json.ts:
import { createMikaAcpProductFeed, serializeMikaAcpProductFeed } from "@bnomei/emdash-mika/acp";
const feed = createMikaAcpProductFeed({ products: [/* mapped from Mika sellables */] });
export const GET = () => new Response(serializeMikaAcpProductFeed(feed), { headers: { "content-type": "application/json" } });serializeMikaAcpProductFeed() validates the feed before returning JSON and throws with the first invalid path. For file-upload ingestion, use createMikaAcpFileUploadRows() and serializeMikaAcpFileUploadRows(); those flatten each active sellable/price pair into newline-delimited JSON rows. Structured product-feed prices use Mika minor-unit integer amounts, while file-upload row price values are rendered as decimal major-unit strings such as 19.99 USD.
Checkout Handlers
Section titled “Checkout Handlers”createMikaAcpCheckoutHandlers() returns create, update, complete, get, and cancel methods for host checkout-session routes. The core ACP checkout flow centers on create, update, and complete; Mika also provides read and cancel handlers for hosts that expose those optional flows. The helper requires an apiKey or signatureSecret and throws without one — knowing a session id must never be enough to read or mutate another buyer’s session:
import { createMikaAcpCheckoutHandlers, createMemoryMikaAcpSessionStore,} from "@bnomei/emdash-mika/acp";
export const handlers = createMikaAcpCheckoutHandlers({ api, // the host MikaApi store: createMemoryMikaAcpSessionStore(), // test/demo only — back it with durable storage seller: { /* MikaAcpSeller identity */ }, apiKey: process.env.ACP_API_KEY, // or signatureSecret});
// POST /checkout_sessions -> handlers.create(request)// GET /checkout_sessions/[id] -> handlers.get(request, id)Then import handlers from the host Astro endpoints that expose the ACP routes:
src/pages/acp/checkout_sessions/index.tssrc/pages/acp/checkout_sessions/[id].tssrc/pages/acp/checkout_sessions/[id]/complete.tssrc/pages/acp/checkout_sessions/[id]/cancel.tsEach endpoint should be small: read the Astro request, call the matching handler, and return the handler response.
Example endpoint mapping:
import { handlers } from "../../../lib/acp-checkout";import type { APIRoute } from "astro";
export const prerender = false;export const POST: APIRoute = ({ request }) => handlers.create(request);import { handlers } from "../../../lib/acp-checkout";import type { APIRoute } from "astro";
export const prerender = false;export const GET: APIRoute = ({ request, params }) => handlers.get(request, params.id!);export const POST: APIRoute = ({ request, params }) => handlers.update(request, params.id!);Write endpoints require an Idempotency-Key header. The session store must implement claimIdempotencyKey, bindIdempotencyKey, and releaseIdempotencyKey; Mika coordinates in-progress, replay, and conflict behavior through those atomic methods. The in-memory store is for tests and demos only.
Headers And Auth
Section titled “Headers And Auth”Mika’s helper supports one or both of these host auth modes:
| Option | Required request header |
|---|---|
apiKey |
Authorization: Bearer <apiKey> |
signatureSecret |
Signature: <base64 hmac-sha256> and Signature-Timestamp |
The Signature mode is Mika helper behavior for host-owned protection; it is not a claim that OpenAI’s public ACP spec uses that header. Mika signs a canonical payload of uppercase method, pathname plus search, SHA-256 hash of the raw body, and timestamp joined with newlines. The timestamp must parse and fall within Mika’s five-minute freshness window. OpenAI ACP integrations should still honor the official Authorization, API-Version, Idempotency-Key, and Request-Id expectations at the host endpoint boundary.
Body-bearing handlers (create, update, complete, and cancel) require Idempotency-Key. get authenticates but does not require a request body or idempotency key. Handler responses are JSON and echo Idempotency-Key and Request-Id headers when the request included them. Accept-Language is forwarded into the Mika request context.
Completion Behavior
Section titled “Completion Behavior”complete() serializes completion by session with an internal key shaped like acp_complete_lock:<checkoutSessionId> so two concurrent completion requests cannot both authorize payment. It currently supports delegated Stripe payment data only: payment_data.provider must be stripe and payment_data.token must be present. Mika stores Stripe delegated-payment metadata before calling api.checkout.start().
Verify
Section titled “Verify”- The feed serializer validates the response and fails with a clear path for invalid product data.
- Write endpoints reject missing auth and missing
Idempotency-Key. Request-IdandIdempotency-Keyecho when callers provide them.- Completion cannot authorize the same session twice.
- Delegated payment data is accepted only for supported provider data.
Next: ACP lists exact helper exports. Agent-Ready Storefront covers the public metadata that should agree with the feed.
External References
Section titled “External References”- OpenAI Agentic Commerce for the protocol family this host-owned projection targets.
- OpenAI commerce key concepts for the merchant-of-record and checkout responsibility split.
- OpenAI Checkout Spec for checkout session endpoint semantics and required request headers.
- OpenAI production guide for feed, checkout, and payment integration production considerations.
Source Anchors
Section titled “Source Anchors”- ⓟ
../emdash-mika/src/acp.ts - ⓐ
../emdash-mika/src/templates/astro/examples/agent-ready-storefront.md