Skip to content

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.

  • A host api object 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.

ACP wiring has two surfaces:

  1. A product feed endpoint that serializes Mika catalog/sellable facts.
  2. 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.

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:

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.

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:

src/lib/acp-checkout.ts
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.ts
src/pages/acp/checkout_sessions/[id].ts
src/pages/acp/checkout_sessions/[id]/complete.ts
src/pages/acp/checkout_sessions/[id]/cancel.ts

Each endpoint should be small: read the Astro request, call the matching handler, and return the handler response.

Example endpoint mapping:

src/pages/acp/checkout_sessions/index.ts
import { handlers } from "../../../lib/acp-checkout";
import type { APIRoute } from "astro";
export const prerender = false;
export const POST: APIRoute = ({ request }) => handlers.create(request);
src/pages/acp/checkout_sessions/[id].ts
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.

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.

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().

  • 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-Id and Idempotency-Key echo 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.

  • ../emdash-mika/src/acp.ts
  • ../emdash-mika/src/templates/astro/examples/agent-ready-storefront.md