Skip to content

UCP conformance

Which parts of UCP 2026-04-08 Monokaido implements, which it does not, and what that means for an integration.

On this page

Monokaido implements the Universal Commerce Protocol version 2026-04-08 — that version only — over REST. This chapter compares the implementation with the specification of that version, rule by rule, and says for each rule what Monokaido does and what your client should do about it.

The comparison was made against the specification pages under ucp.dev/2026-04-08 and the official REST schema (rest.openapi.json) as published on 26 October 2026. What this chapter says about Monokaido is the behaviour of the deployed code; Concepts and lifecycle explains it in context.

StatusMeaning
SupportedImplemented as the specification describes it.
PartialImplemented with a narrower scope or another reading; the row says exactly how.
Not supportedNot implemented. Do not rely on it.
Not applicableThe rule cannot apply to this implementation.

What a shop publishes

Every custom domain that serves UCP answers GET /.well-known/ucp with the profile below. Shops differ in two things only. The first is the host, written <shop host> here: the shop's primary domain when that domain serves UCP, otherwise the domain the request came to; continue_url and order.permalink_url use the same host. The second is catalog mode: a shop in catalog mode lists the two catalog capabilities only.

CapabilityversionspecschemaextendsDeclared
dev.ucp.shopping.catalog.search2026-04-08https://ucp.dev/2026-04-08/specification/catalog/searchhttps://ucp.dev/2026-04-08/schemas/shopping/catalog_search.json—always
dev.ucp.shopping.catalog.lookup2026-04-08https://ucp.dev/2026-04-08/specification/catalog/lookuphttps://ucp.dev/2026-04-08/schemas/shopping/catalog_lookup.json—always
dev.ucp.shopping.cart2026-04-08https://ucp.dev/2026-04-08/specification/carthttps://ucp.dev/2026-04-08/schemas/shopping/cart.json—not in catalog mode
dev.ucp.shopping.checkout2026-04-08https://ucp.dev/2026-04-08/specification/checkouthttps://ucp.dev/2026-04-08/schemas/shopping/checkout.json—not in catalog mode
dev.ucp.shopping.fulfillment2026-04-08https://ucp.dev/2026-04-08/specification/fulfillmenthttps://ucp.dev/2026-04-08/schemas/shopping/fulfillment.json"dev.ucp.shopping.checkout"not in catalog mode
dev.ucp.shopping.discount2026-04-08https://ucp.dev/2026-04-08/specification/discounthttps://ucp.dev/2026-04-08/schemas/shopping/discount.json["dev.ucp.shopping.checkout", "dev.ucp.shopping.cart"]not in catalog mode
Profile keyValue
ucp.version"2026-04-08"
ucp.servicesone key, dev.ucp.shopping, with one entry: transport: "rest", endpoint: "https://<shop host>/ucp/v1", version: "2026-04-08", spec: "https://ucp.dev/2026-04-08/specification/overview", schema: "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json"
ucp.payment_handlers{}
ucp.supported_versionsabsent
signing_keys, keysabsent

All spec and schema URLs sit on https://ucp.dev/, the origin the specification binds the dev.ucp.* namespace to.

OperationRoot capability
POST /ucp/v1/catalog/searchdev.ucp.shopping.catalog.search
POST /ucp/v1/catalog/lookup, POST /ucp/v1/catalog/productdev.ucp.shopping.catalog.lookup
every /ucp/v1/carts routedev.ucp.shopping.cart
every /ucp/v1/checkout-sessions routedev.ucp.shopping.checkout

Capabilities, extensions and transports

Specification itemStatusWhat it means for you
dev.ucp.shopping.catalog.searchPartialText search over the product title (in the answer's language) and the shop's own product codes — a substring match that ignores case but not diacritics — with an optional price filter. A non-empty filters.categories is refused with 400; other filters are ignored. Results come newest first, with no relevance ranking. The query does not match a variant's SKU or GTIN: lookup finds a variant by its SKU, and nothing finds one by GTIN.
dev.ucp.shopping.catalog.lookupPartialcatalog/lookup takes up to 20 ids (variant id, variant SKU, product id or handle); catalog/product reads one product by product id, variant id or handle, with an option selection. preferences is ignored: a selection no variant matches is relaxed from the end of selected.
dev.ucp.shopping.cartPartialAn update keeps the fields it leaves out, and a checkout made from a cart does not write its changes back to the cart (see Cart). A shop in catalog mode does not declare it; cart calls there answer 200 with capabilities_incompatible.
dev.ucp.shopping.checkoutPartialA checkout is never completed through the API: a ready checkout is requires_escalation and the buyer finishes on the shop through continue_url.
dev.ucp.shopping.fulfillmentPartialOne shipping method with one group of options. No pickup method type, no split shipments.
dev.ucp.shopping.discountPartialOne code is applied per session; further codes get discount_code_combination_disallowed. Without the negotiated extension no code is read or applied.
dev.ucp.shopping.orderNot supportedNo GET /orders/{id}, no order webhooks. Poll GET /ucp/v1/checkout-sessions/{id} for status: "completed" and order.
dev.ucp.common.identity_linkingNot supportedEvery session is anonymous. A buyer signed in to the shop in the browser tab that opens continue_url gets the items in their customer cart.
dev.ucp.shopping.ap2_mandateNot supportedNo autonomous payment: the buyer always finishes on the shop.
dev.ucp.shopping.buyer_consentNot supportedThe shop's legal links arrive in the checkout's links[]; consent is given on the shop.
dev.ucp.common.loyaltyNot supported—
dev.ucp.shopping.permalinkNot supportedUse continue_url.
Payment handlersNot supportedpayment_handlers is always {}. The buyer pays on the shop's own checkout with the shop's own payment methods.
Signals (dev.ucp.buyer_ip, dev.ucp.user_agent) and attributionNot supportedAccepted and ignored, like every field the API does not use. No signal message ever asks for one.
Transport restSupportedThe only transport.
Transports mcp, a2a, embeddedNot supportedThe profile lists a single REST service. continue_url is a full page that refuses to be shown in a frame, not an embeddable checkout.

Protocol rules

The first column names the rule and, in brackets, the specification page it comes from.

Discovery and the business profile

RuleStatusMonokaidoFor your client
The profile is at /.well-known/ucp, over HTTPS, and does not redirect (overview)SupportedServed on every custom domain that serves UCP. Only plain http:// is redirected (301) to https://.Fetch the https:// address directly.
Cache-Control is public with max-age of at least 60, never private, no-store or no-cache (overview)Supportedpublic, max-age=300Cache the profile for up to five minutes.
version, services and payment_handlers are present, registries even when empty (overview)Supportedpayment_handlers is {} by design.—
signing_keys (may) (overview)Not supportedNone published: Monokaido sends no webhooks and signs nothing.Answers cannot be verified by signature.
supported_versions for the older versions a business supports (overview)Not applicableMonokaido supports one version, so there is no older one to list.—

Platform profile and negotiation

RuleStatusMonokaidoFor your client
The platform sends UCP-Agent: profile="…", an RFC 8941 dictionary, on every request (overview, REST binding)SupportedRead on every /ucp/v1 call by a parser for the subset UCP uses: lower-case keys, profile as a quoted string, other members and parameters ignored, the last profile wins. A missing header, a header without a quoted profile, or one that does not parse as a whole is 400 invalid_profile_url.Send exactly profile="https://…".
The profile URL points to /.well-known/ucp (REST binding)PartialNot enforced: any https:// URL of at most 1000 characters without whitespace or credentials is accepted, any port, path and query.Host it at /.well-known/ucp anyway.
The business fetches and validates the platform profile unless it has it cached (overview)SupportedGET with no redirects, 200 only, at most 256 KiB, 5 s, public addresses only. Cached for the profile's max-age clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored), served stale for up to 1 h while it refreshes; failures cached 60 s. At most 5 profiles that are not cached yet are fetched a minute per client (an IPv4 address or an IPv6 /64), and 60 in all per API server process; beyond that 503 with Retry-After. Only ucp.version and ucp.capabilities are validated.Serve it fast with Cache-Control: public, max-age=3600. Nothing else in your profile is checked.
Intersection: same name, highest common version, prune extensions without an active parent, repeat (overview)SupportedParents come from the shop's declaration; an extension with several parents stays while any one is active. Your own extends is ignored. The shop lists each capability at 2026-04-08 only, so a capability your profile lists at other versions only is not active.Declare checkout to get fulfillment; declare cart or checkout to get discount.
The response's ucp names the version and the capabilities active for the operation: checkout with discount and fulfillment, cart with discount (overview)Supportedcapabilities lists the root capability and each negotiated extension of it.Read the extensions there before relying on their fields.
Request-time validation: the platform's version must be the business's or a key of its supported_versions, otherwise version_unsupported (overview)SupportedAny version other than 2026-04-08 is refused with 422 version_unsupported.Declare 2026-04-08.
A business SHOULD handle requests from platforms using its version or older (overview)Not supportedOlder published versions, such as 2026-01-23, are refused like any other.—
Negotiation errors and their HTTP status: invalid_profile_url 400, profile_unreachable 424, profile_malformed 422, version_unsupported 422, capabilities_incompatible 200 (overview)SupportedEach carries continue_url to the shop's home page. capabilities_incompatible is a 200 business answer with ucp.status: "error".Treat capabilities_incompatible as final for that operation on that shop until its profile changes.

Transport

RuleStatusMonokaidoFor your client
HTTPS with TLS 1.3 at least (REST binding)PartialTLS 1.2 and TLS 1.3 are both accepted.If your policy demands TLS 1.3, require it in your client; the shop offers it.
JSON with Content-Type: application/json (overview)SupportedEvery /ucp/v1 answer is JSON. The continue_url page is HTML, by design: it is for the buyer's browser.Send Content-Type: application/json in UTF-8.
Request-Id header, marked required on every operation in the REST header table and the official schema (REST binding)Not supportedNeither read nor returned. No request id of any kind comes back.Sending it is harmless; log your own correlation id.
Idempotency-Key header, marked required in the REST header table and on every write in the official schema (REST binding)PartialOptional; see Idempotency.Send it on every write.

Authentication, signing and identity

RuleStatusMonokaidoFor your client
Businesses SHOULD authenticate platforms — API key, OAuth bearer token, mutual TLS or HTTP message signatures (overview, REST binding)Not supportedThe API is open, the "Open API" option the REST binding lists. The shop is chosen by the Host header; the session id is the only credential of a session. Credentials you send are ignored.Keep session ids private. Do not assume another caller cannot reach a session whose id it knows.
Request signing (RFC 9421 Signature, Signature-Input, Content-Digest) and signed responses (overview, signatures)Not supportedSignatures you send are ignored; nothing is signed.—
Signature and trust errors signature_missing, signature_invalid, key_not_found, digest_mismatch, algorithm_unsupported, profile_not_trusted (overview, signatures)Not supportedNever emitted.—
Identity linking (OAuth 2.0) and the scopes dev.ucp.shopping.checkout:manage, dev.ucp.shopping.cart:manage (identity linking, checkout, cart)Not supportedSessions are guest sessions. The buyer signs in, if at all, on the shop.—

Checkout and its completion

RuleStatusMonokaidoFor your client
payment_handlers in the checkout's ucp (checkout)Supported{} on every checkout answer that is not an error.—
Update is a full replacement: the platform sends the entire checkout (checkout)Partialline_items, when sent, replaces every line, and a buyer or context sent replaces the stored one whole (null clears it). A field the request leaves out keeps its stored value instead of being cleared: the discount codes are cleared by discounts.codes: [], and a shipping address stays until another one is sent.Send the whole resource on every update, as the specification asks.
Complete checkout places the order and returns the checkout with order (checkout)Not supportedPOST …/complete answers the checkout as it stands. Payment data in the body is ignored.Never expect an order from complete. Hand the buyer continue_url, then poll GET.
Statuses incomplete, requires_escalation, ready_for_complete, complete_in_progress, completed, canceled (checkout)PartialFour are used. Any recoverable error → incomplete; none → requires_escalation with payment_required. completed only when the buyer opened the checkout's continue_url (or that of the cart it was made from) and then placed an order on the shop from the cart the handoff filled, holding at least one of the checkout's items. ready_for_complete and complete_in_progress never occur.Treat requires_escalation as "ready: hand over".
requires_escalation carries continue_url and at least one message needing the buyer (checkout)SupportedAlways payment_required with severity requires_buyer_input, plus pickup_point_required when the chosen method needs a pickup point.continue_url is a link for the buyer's browser, not for your server.
continue_url in every non-final answer, absent in final ones, absolute HTTPS (checkout)SupportedIn every cart answer and every open checkout; absent from completed and canceled checkouts.—
Order confirmation order: {id, label?, permalink_url} (checkout)Supportedid is the order's UUID, label its number, permalink_url the shop's confirmation page, which needs no sign-in. The rest of the body is the last quote, not the order.Do not present the quote's totals as the order's.
A completed checkout is immutable (checkout)SupportedWrites are compare-and-set; nothing reopens a completed checkout. An update of a completed or canceled checkout answers it unchanged with checkout_completed or checkout_canceled.—
Cancel is refused for a completed or canceled checkout (checkout)SupportedAnswered 200 with the resource and the message checkout_completed or checkout_canceled (unrecoverable); ucp.status is absent. An order is never canceled through UCP.Read the message, not only the HTTP status.
The business sends a confirmation e-mail once the checkout is completed (checkout)PartialThe shop's own order confirmation e-mail, the same as for every order placed on its checkout, goes to the address the buyer entered there. A merchant who switched off its transactional e-mails sends none.—
expires_at, RFC 3339; 6 hours when the business sets none (checkout)SupportedAlways set. Carts live 7 days and checkouts 24 hours from their last create or update; a read does not extend them.Read expires_at; do not assume 6 hours.
links[] with the legal links (checkout)PartialFrom the shop's checkout consents that have a link: type is privacy_policy when the consent looks like one, otherwise terms_of_service, at most 10, links starting with / made absolute on the shop's host. refund_policy, shipping_policy and faq are never used. [] when the shop has no consent with a link.Show them to the buyer before the handoff.
context: address_country, address_region, postal_code, intent, language, currency, eligibility, payment (checkout)Partialcontext.language picks the language and thus the currency of prices. context.currency only denominates filters.price in catalog requests; it never changes the currency of prices. address_country, address_region and postal_code are checked, stored and returned in carts and checkouts, but change neither prices nor shipping options: the shipping address does that. intent, eligibility and payment are ignored, without the eligibility_not_accepted warning the specification suggests.Choose the currency through the language; send the shipping address for shipping options.

Cart

RuleStatusMonokaidoFor your client
POST /carts, GET and PUT /carts/{id}, POST /carts/{id}/cancel (cart)Supported——
Create answers 201 Created (official REST schema)Supported201 for a new cart or checkout. 200 when a conversion returns the cart's open checkout, and for business errors, which the REST binding answers with 200.—
Update is a full replacement: the platform sends the entire cart (cart)Partialline_items is required on every update (400 without it) and replaces every line. A buyer or context sent replaces the stored one whole (null clears it); buyer, context and discounts the request leaves out keep their stored value, and discounts.codes: [] clears the codes.Send the whole resource on every update.
Get cart answers not_found for a cart that does not exist, has expired or was canceled (cart)SupportedAs an HTTP 200 business answer with ucp.status: "error", never a 404.—
Cancel cart returns the cart as it was; later operations SHOULD answer not_found (cart)SupportedEvery later call on the cart answers not_found. The cancel answer still carries the cart's continue_url, which no longer works.—
The cart provides continue_url and estimated totals and may set expires_at (cart)SupportedA cart's continue_url opens the shop's cart page, or its checkout page when an open checkout was made from the cart: that checkout is then handed over. Cart totals hold no shipping.—
Cart to checkout with cart_id: the cart's contents, overlapping checkout fields ignored, an existing incomplete checkout returned (cart)Partialcart_id is read only when the cart capability is negotiated, as the specification says; otherwise the request is an ordinary create and needs line_items. Lines, buyer, context and language come from the cart; the request's line_items, buyer and context are ignored. Discount codes in the request replace the cart's when sent. An open checkout of the cart is returned (200) with nothing from the request applied; an expired one is canceled and a new one made. A cart already taken to the shop is refused with handed_off.Send cart_id without discounts to keep the cart's codes; change the checkout with PUT.
The cart's discount codes carry forward to the checkout (discount)PartialOnly when the conversion request sends no codes.As above.
Changes made during checkout are reflected back to the cart (should) (cart)Not supportedThe checkout gets its own copy of the lines; the cart stays as it was.Once converted, work on the checkout only.

Catalog

RuleStatusMonokaidoFor your client
POST /catalog/search, /catalog/lookup and /catalog/product; with lookup, both lookup and product (catalog REST binding)Supported——
A search carries at least one input (query, filters), and the business validates it (catalog search)PartialNot enforced: a search without query and filters lists the whole catalog, newest first.Always send query or filters.price.
Cursor pagination, default page size 10 (catalog REST binding)SupportedAt most 20 per page; a larger limit is lowered without an error. The cursor carries only the page number. Pages can be short, or empty while has_next_page is true.Send the same query, filters, limit and context with every cursor; follow has_next_page, not the page size.
Lookup answers 200; unknown ids give fewer products, optionally an info not_found (catalog REST binding)SupportedOne info message not_found per id that matched nothing.—
Too many ids in a batch → 400 request_too_large (catalog lookup, catalog REST binding)SupportedMore than 20 ids. Other caps (100 lines, 10 discount codes) answer 400 invalid_request.—
Product not found → 200, ucp.status: "error", not_found unrecoverable (catalog REST binding)SupportedWith continue_url to the shop's home page.—
filters.categories (catalog search)Not supportedA non-empty list is refused with 400 invalid_request.Search by query and filters.price.

Fulfillment extension

RuleStatusMonokaidoFor your client
methods[] of type shipping or pickup, with destinations, groups, options and selected_option_id (fulfillment)PartialOne method, type: "shipping", with one group whose options are the shop's shipping methods, even when your profile declares supports_multi_group. The request's first shipping method is read: its selected (else first) destination and the first group's selected_option_id. The destination's own id comes back as "destination". Options carry id, title, optionally description, and totals. A method that needs a pickup point adds pickup_point_required: the buyer picks the point on the shop.Send one shipping method with one destination; leave pickup points to the shop.

Discount extension

RuleStatusMonokaidoFor your client
discounts.codes replaces the stored codes, [] clears them; discounts.applied; rejected codes as warnings with a path; amounts in totals[] and line_items[].totals[] (discount)PartialOne code is applied — the first the shop accepts. Each further code gets discount_code_combination_disallowed; a rejected code gets discount_code_expired, discount_code_user_ineligible or discount_code_invalid, with path $.discounts.codes[<index>], the index in discounts.codes as the answer returns it (duplicates and blank codes removed). applied holds at most one entry, with method: "across". The amount appears in the top-level totals only, not in line_items[].totals. discount_code_already_applied and discount_code_user_not_logged_in are never sent.Send the most important code first.

Order capability and webhooks

RuleStatusMonokaidoFor your client
GET /orders/{id} and signed order webhooks to the platform's webhook_url (order)Not supportedNo order route, no webhooks of any kind. Only a checkout reports an order; a cart without a checkout, handed over through its own continue_url, never does.Poll the checkout; stop at completed or canceled.

Idempotency

RuleStatusMonokaidoFor your client
Writes carry an Idempotency-Key (REST header table: required; REST binding prose: should support)PartialOptional. Without a key a write runs unprotected: a retried create makes a second session. A key is 1–255 printable ASCII characters without spaces.Send a fresh key with every write and reuse it on every retry of that write.
Results are kept at least 24 hours, 48 recommended (REST binding, signatures)SupportedAt least 25 hours from the key's first use: an hourly job deletes older keys. The recommended 48 hours are not met.Do not rely on a key more than 24 hours after its first use.
A duplicate with the same payload gets the stored result; a different payload gets 409 (REST binding)Supported409 idempotency_conflict. A duplicate while the first request still runs also gets 409, with Retry-After: 2. Business answers (200 with ucp.status: "error") are stored and replayed too. A request whose operation fails with a protocol error (400, 503, 500) releases its key.After a 503 or a network error, retry with the same key.
The payload is matched by the SHA-256 of the raw body bytes (signatures)PartialMatched by the canonical JSON of the operation, the path id and the body with sorted keys: whitespace and key order do not matter, array order does.—
Keys are unique per client and per operation type (signatures)PartialScoped to the shop and to your exact profile URL, not to the operation: the same key on another operation or another session id is 409 idempotency_conflict.Keep your profile URL stable, and never reuse a key across writes.
A key has at least 128 bits of entropy (signatures)Not applicableA rule for the key you generate. Monokaido checks only its format.Use a UUID v4.
On a storage failure the business fails closed with 503 (signatures)PartialA key that cannot be claimed fails the request with 500 internal_error before anything runs. A result that cannot be stored after the operation ran is logged and the answer still goes out; a retry with the key then gets 409 until the 120-second claim runs out, and after that the operation runs again.Treat 409 with Retry-After as "retry later".

Errors and messages

RuleStatusMonokaidoFor your client
Protocol errors as JSON {code, content} with their HTTP status, Retry-After on 429 and 503 (overview, REST binding)Supportedinvalid_request 400 (415 for an unsupported charset or encoding), invalid_profile_url 400, not_found 404, idempotency_conflict 409, request_too_large 400 or 413, profile_malformed and version_unsupported 422, profile_unreachable 424, rate_limited 429, internal_error 500, service_unavailable 503. Retry-After is 60 on a 429 and 1 to 60 on a 503. Profile, version and discovery errors add a continue_url to the shop's home page. Exception: the web server answers some requests itself with HTML — 413 above 1 MiB, 429 above 100 requests a second per IP, 502 and 504 when the API is down or slow.Handle the HTML answers by status code alone.
401 and 403 (overview)Not applicable401 is never sent. 403 comes only from the platform's cross-origin write gate, for a write that carries a Monokaido session cookie and comes from a foreign web origin — a server-side agent never does that; its body is invalid_request.—
Business outcomes are HTTP 200 with messages[]; ucp.status is an optional discriminator (checkout, overview)SupportedSuccessful answers omit ucp.status; answers without a resource set "error". Session and product not_found are business answers.Read a missing ucp.status as success.
Message shapes and severities recoverable, requires_buyer_input, requires_buyer_review, unrecoverable (checkout)Partialrecoverable, requires_buyer_input and unrecoverable are used; requires_buyer_review never. Warnings carry no presentation; no message has content_type. A missing field is reported with the code missing (the specification's example uses field_required; codes are open-ended).Branch on type and severity; treat codes you do not know by their severity.

Where the specification is ambiguous

TopicWhat the specification saysWhat Monokaido implementsFor your client
The parents of dev.ucp.shopping.discountThe overview's example business profile has "extends": "dev.ucp.shopping.checkout"; the overview's multi-parent example and the discount page declare cart and checkout as parents.Both parents. Discount is active with the cart alone.Declare discount whenever you use carts or checkouts.
The shape of version_unsupportedThe error table gives REST status 422 and the REST example is a {code, content, continue_url} body; the example under request-time validation shows an answer with a ucp envelope and a message.A 422 protocol error, {code, content, continue_url}, without a ucp envelope.Handle it as a protocol error.
Idempotency-Key and Request-IdThe REST header table and the official schema mark both as required; the prose says operations that modify state should support idempotency.The prose: the key is optional, Request-Id is ignored.Send the key on every write.
Cart-to-checkout and discount codesThe cart page asks the business to use the cart's contents and ignore overlapping checkout fields; the discount page asks it to carry the cart's codes forward.Lines, buyer, context and language from the cart; codes from the request when it sends some, else from the cart; shipping from the request.Omit discounts from the conversion request to keep the cart's codes.
When to use requires_escalationThe status of a checkout that needs information the API cannot take, or the buyer's input; the checkout page also says a checkout is finalized by the buyer through a trusted UI unless the AP2 mandates extension is supported.Every ready checkout, because payment always happens on the shop. It is the last state the API reaches by itself.Hand over at requires_escalation; do not wait for ready_for_complete.
An empty links[]The field is required.[] when the shop has no checkout consent with a link, or the consents could not be read.Accept an empty list.
The platform profile's own cachingA profile must be served with public and a max-age of at least 60; the fetching rules let a business cache it with a 60-second floor regardless of its Cache-Control and refresh it in the background.Its max-age is used, clamped to 60 s – 1 h; no-store, no-cache and private are ignored.Set max-age to what you can live with.

Beyond the specification

Monokaido adds constraints the specification leaves open. They are part of the contract:

  • One shop per host. The shop is chosen by the Host header. A host that does not serve UCP answers 404 not_found whatever the reason; a suspended company answers 503. See Concepts and lifecycle.
  • Rate limits per client IP and per operation, per minute: 600 reads, 300 writes, 60 handoff requests; 429 rate_limited with Retry-After: 60. Answers carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. The web server adds 100 requests a second per IP (burst 200), counted together with that IP's other storefront traffic. Details in Idempotency, concurrency and limits.
  • Request caps. 100 lines, quantity 999 per line (a larger quantity is lowered to 999), 10 discount codes, 20 lookup ids, page size 20, body 1 MiB.
  • One request at a time per session. Writes and checkout reads on one session queue; a request that cannot get its turn within 10 seconds, or finds four already waiting, gets 503 with Retry-After: 1.
  • Server-side calls only. There is no CORS for third-party origins, so a web page on another origin cannot call the API.
  • Caching. Every answer is no-store except the business profile; answers also carry an ETag.
  • The handoff. continue_url carries the lines and the applied discount code to the shop's cart, not the buyer's details. See Security and data protection for what is stored and for how long.

How changes are announced

  • The business profile is the authoritative statement of what a shop serves at a given moment: the protocol version, the endpoint and the capabilities. It can change when the merchant changes the shop (catalog mode, switching UCP off, its domains), and it is meant to be re-read: it is cacheable for five minutes.
  • The protocol version is fixed. The specification allows backward- compatible changes within a version — new optional fields, new error codes, new enum values — and requires a new version for a breaking one. Monokaido does not change what 2026-04-08 means: support for another version would appear in the profile's ucp.version and supported_versions.
  • This manual documents the contract. Each page shows the date it was last updated, and the OpenAPI document and the other machine-readable files are published with these pages. The manual offers no subscription, deprecation schedule or service-level commitment.

To notice a change early, compare ucp.version and the list of capabilities each time you re-read a shop's profile, compare the OpenAPI document with the copy you built against from time to time, and ignore fields, codes and enum values you do not know in answers — new ones may appear.