Preskočiť na obsah

Referencia API (OpenAPI)

Každý endpoint, hlavička, pole a odpoveď UCP API — OpenAPI 3.1 na stiahnutie pre Swagger, Postman a generátory klientov.

Na tejto stránke

Referencia nižšie sa generuje z dokumentu OpenAPI 3.1, ktorý opisuje UCP API Monokaida: každú operáciu, hlavičku, pole, stavový kód aj príklad. Ten istý dokument je zverejnený na stiahnutie, takže ho môžete importovať do svojich nástrojov alebo z neho vygenerovať typovaného klienta. Celý jeden nákup krok za krokom prechádza Rýchly štart pre vývojárov.

Základná adresa a konvencie

  • Každý obchod je samostatný server. Obchod poskytuje API na vlastnej doméne: profil obchodu na https://shop.example.com/.well-known/ucp, operácie REST pod https://shop.example.com/ucp/v1. Základ vezmite z endpoint v profile, ktorý uvádza primárnu doménu obchodu. Cesty pod /ucp/continue/ otvára prehliadač kupujúceho, nikdy agent.
  • Žiadne overovanie. Žiadny API kľúč, OAuth ani cookie: obchod sa určí podľa hostiteľa, ktorého voláte. Každé volanie pod /ucp/v1 uvádza profil vašej platformy v UCP-Agent: profile="https://…".
  • Hlavičky. Content-Type: application/json pri každej požiadavke s telom; Idempotency-Key pri siedmich zápisoch (nepovinný, dôrazne odporúčaný); Accept-Language je nepovinná.
  • JSON v UTF-8. Polia, ktoré API nepozná, sa ignorujú. Ak má pole, ktoré API číta, zlý typ JSON, požiadavka sa odmietne s 400 invalid_request a content to pole pomenuje. Niekoľko nápovied, ktoré API nevie použiť, sa namiesto toho zahodí: nečitateľné context.language, context.currency alebo context.address_country, položky discounts.codes či ids, ktoré nie sú reťazce, selected_option_id či selected_destination_id, ktoré nie je reťazec, a id položiek (celý zoznam: Čo sa namiesto odmietnutia upraví).
  • Poradie kľúčov v objektoch JSON nie je podstatné ani v požiadavkách, ani v odpovediach. Odpovede zostavené z uložených dát — zopakovaný zápis, dokončený, zrušený alebo odovzdaný checkout, uložený buyer či context — prichádzajú v poradí kľúčov typu JSONB v PostgreSQL (kratšie kľúče skôr, potom abecedne).
  • Peňažné sumy sú celé čísla v najmenších jednotkách meny podľa ISO 4217, s DPH. Katalóg ich zapisuje ako objekt {"amount": 69900, "currency": "EUR"} (699,00 €); košíky a checkouty zapisujú holé celé čísla v item.price a totals[].amount popri jednej mene currency pre celú reláciu. EUR, CZK, PLN a HUF majú dve desatinné miesta (1 990 Ft je 199000), JPY žiadne, KWD tri. Súčet typu discount je záporný.
  • Časové údaje sú vo formáte ISO 8601 v UTC s milisekundami: "expires_at": "2026-10-27T09:21:30.551Z".
  • Identifikátory. Košíky a checkouty majú UUID v4 malými písmenami; produkty, varianty, položky, spôsoby dopravy aj objednávky majú tiež UUID. Ak {id} v ceste nie je UUID, odpoveď je rovnaká ako pri neznámej relácii. Token v continue_url má 64 šestnástkových znakov a nikdy nie je identifikátorom relácie.
  • Dva druhy zlyhania. Chyba protokolu je chybový stav HTTP s {"code", "content", "continue_url"?}. Obchodný výsledok je HTTP 200 (201 pri vytvorenom košíku alebo checkoute) so zoznamom messages[] a so ucp.status: "error", keď sa nevrátil žiadny zdroj. Neznámy košík, checkout či produkt je práve taká odpoveď 200, nikdy 404. Niekoľko odpovedí posiela webový server pred API ako HTML. Všetky kódy sú v kapitole Chyby a správy.
  • Vyrovnávacia pamäť. Každá odpoveď API má Cache-Control: no-store, okrem profilu obchodu (public, max-age=300).
  • Limity na IP adresu klienta a operáciu: 600 čítaní, 300 zápisov a 60 požiadaviek odovzdania za minútu, spoločne pre všetky obchody, ktoré z tej IP voláte. Odpovede v rámci limitu nesú X-RateLimit-Limit, X-RateLimit-Remaining a X-RateLimit-Reset (v sekundách); 429 nesie Retry-After: 60. Webový server pred API pridáva limit 100 požiadaviek za sekundu na IP adresu (nárazovo najviac 200 navyše) a obmedzuje veľkosť tela na 1 MiB. Podrobnosti sú v kapitole Idempotencia, súbežnosť a limity.

Na stiahnutie

  • OpenAPI 3.1 (JSON)

    Celé UCP API na import do Swagger Editora, Postmana, Insomnie alebo openapi-generatora — podľa adresy alebo ako súbor.

    https://help.monokaido.com/ucp/openapi.json
  • OpenAPI 3.1 (YAML)

    Ten istý dokument vo formáte YAML, pre nástroje a revízie, ktoré ho uprednostňujú.

    https://help.monokaido.com/ucp/openapi.yaml
  • Definície nástrojov pre agenta

    Nástroje (tools) pre function calling jazykového modelu, mapované na operácie UCP.

    https://help.monokaido.com/ucp/tools.json
  • Celý návod pre LLM

    Celý návod pre vývojárov UCP ako jeden súbor Markdown do kontextu jazykového modelu.

    https://help.monokaido.com/ucp/llms-full.txt
  • llms.txt

    Obsah celého manuálu pre jazykové modely vo formáte llms.txt.

    https://help.monokaido.com/llms.txt

Dokument vo vašich nástrojoch

Súbory sa posielajú s Access-Control-Allow-Origin: *, takže ich podľa adresy naimportujú aj nástroje, ktoré bežia v prehliadači. Server v dokumente je https://{shopDomain} s predvolenou hodnotou shop.example.com: v každom nástroji ho nastavte na obchod, s ktorým sa integrujete, a ukážkovú hodnotu UCP-Agent nahraďte svojím profilom.

Swagger Editor

Otvorte editor.swagger.io, zvoľte File → Import URL a vložte https://help.monokaido.com/ucp/openapi.json. Editor sa hodí na čítanie a kontrolu dokumentu. Jeho Try it out posiela požiadavky z vášho prehliadača a API obchodu požiadavky z cudzích pôvodov nepovoľuje; požiadavky preto posielajte z Postmanu, Insomnie, curl alebo zo svojho servera.

Postman a Insomnia

Použite Import a vložte adresu https://help.monokaido.com/ucp/openapi.json; oba nástroje z operácií vytvoria kolekciu požiadaviek. Potom nastavte základnú adresu kolekcie na pôvod (origin) obchodu a hlavičku UCP-Agent na svoj profil. Oba posielajú požiadavky z desktopovej aplikácie, takže CORS sa ich netýka.

Typovaný klient v TypeScripte

openapi-typescript z dokumentu vytvorí typy a openapi-fetch ich použije v malom typovanom klientovi:

npm install openapi-fetch
npx openapi-typescript https://help.monokaido.com/ucp/openapi.json -o src/ucp.d.ts
import createClient from 'openapi-fetch';
import type { paths } from './ucp.js';

const shop = createClient<paths>({ baseUrl: 'https://shop.example.com' });
const agent = 'profile="https://platform.example.com/.well-known/ucp"';

const { data, error, response } = await shop.POST('/ucp/v1/catalog/search', {
  params: { header: { 'UCP-Agent': agent } },
  body: { query: 'marlin', pagination: { limit: 2 }, context: { language: 'sk' } },
});
if (!data) throw new Error(`HTTP ${response.status}: ${JSON.stringify(error)}`);
// HTTP 200 is either the result or a business error with ucp.status "error".
if ('products' in data) {
  for (const p of data.products) console.log(p.title, p.price_range.min.amount);
} else {
  console.log(data.messages);
}

Odpoveď 200 je typovaná ako výsledok alebo obchodná chyba, takže vás kontrola typov prinúti ošetriť oboje.

openapi-generator

openapi-generator vytvára klientov pre mnoho jazykov. Potrebuje Javu; obal z npm si samotný generátor stiahne:

# TypeScript (fetch)
npx @openapitools/openapi-generator-cli generate \
  -i https://help.monokaido.com/ucp/openapi.json \
  -g typescript-fetch -o ./ucp-client

# Python
npx @openapitools/openapi-generator-cli generate \
  -i https://help.monokaido.com/ucp/openapi.json \
  -g python -o ./ucp-client-py --package-name ucp_client

Generátor označuje svoju podporu OpenAPI 3.1 ako beta a počas behu vypisuje varovania; modely, ktorých názov koliduje s cieľovým jazykom, premenuje (v TypeScripte sa schéma Error volá ModelError a RequestContext ModelRequestContext) a vnorené typy odpovedí pomenuje sám (GetCart200Response, CreateCheckout200Response pre každú operáciu checkoutu). Pôvod obchodu odovzdajte klientovi ako základnú cestu: vygenerovaná predvolená hodnota je https://shop.example.com. Vygenerovaný klient podľa stavu nerozozná obchodnú chybu s HTTP 200 od zdroja: pri každej odpovedi 200 kontrolujte aj ucp.status === "error".

Operácie

Príklady volajú https://shop.example.com — namiesto neho použite doménu obchodu, ktorý má UCP zapnuté.

Discovery

The business profile at /.well-known/ucp: the protocol version, the REST endpoint and the capabilities this shop declares. Needs no UCP-Agent header.

Get the business profile

GET/.well-known/ucp

The shop’s UCP business profile: protocol version, the REST endpoint to call, the capabilities it declares and payment_handlers: {}. Fetch it first and use its endpoint (on the shop’s primary domain). Needs no UCP-Agent header and no negotiation.

  • Cacheable: Cache-Control: public, max-age=300 (every other UCP answer is no-store). Never redirected.
  • A shop in catalog mode declares only dev.ucp.shopping.catalog.search and dev.ucp.shopping.catalog.lookup.
  • No supported_versions (one version only), no signing_keys (no webhooks are sent, responses are not signed).
  • The web server maps this path to the API’s /ucp/well-known, which answers the same. /.well-known/ucp/ (trailing slash) is not this route.

Merchant checks: Turning UCP on for a shop.

Odpovede

200The business profile.

Hlavičky odpovede

  • Cache-Controlstring

    The business profile may be cached for 5 minutes by anyone.

    vždy:public, max-age=300
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0

SchémaBusinessProfile

application/jsonProfile of a shop that takes orders
{
  "ucp": {
    "version": "2026-04-08",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/overview",
          "transport": "rest",
          "endpoint": "https://shop.example.com/ucp/v1",
          "schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/catalog/search",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_search.json"
        }
      ],
      "dev.ucp.shopping.catalog.lookup": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/catalog/lookup",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_lookup.json"
        }
      ],
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/cart",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/cart.json"
        }
      ],
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/checkout",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/checkout.json"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/fulfillment",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/fulfillment.json",
          "extends": "dev.ucp.shopping.checkout"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/discount",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/discount.json",
          "extends": [
            "dev.ucp.shopping.checkout",
            "dev.ucp.shopping.cart"
          ]
        }
      ]
    },
    "payment_handlers": {}
  }
}
application/jsonProfile of a shop in catalog mode
{
  "ucp": {
    "version": "2026-04-08",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/overview",
          "transport": "rest",
          "endpoint": "https://shop.example.com/ucp/v1",
          "schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/catalog/search",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_search.json"
        }
      ],
      "dev.ucp.shopping.catalog.lookup": [
        {
          "version": "2026-04-08",
          "spec": "https://ucp.dev/2026-04-08/specification/catalog/lookup",
          "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_lookup.json"
        }
      ]
    },
    "payment_handlers": {}
  }
}
304Not Modified — only when the request sent If-None-Match equal to the current answer’s ETag (strong as the API generates it on every response, or the weak W/"…"…Celá odpoveď: NotModified
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable "This shop is temporarily unavailable", Retry-After: 60: the shop’s company is deactivated or billing-locked, or the shop lookup failed.Celá odpoveď: ShopUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl 'https://shop.example.com/.well-known/ucp'

Catalog

Search, lookup and product detail over the shop’s catalog — the same products, prices and stock states the shop’s own pages show. Search reads the shop’s product listing, a materialised view the platform refreshes in the background after catalog changes, so a new or changed product can take a few minutes to show up there; lookup and product read the product data itself. Reads only; no Idempotency-Key.

Search the catalog

POST/ucp/v1/catalog/search

Full-text search over the products the shop lists, with paging and an optional price filter.

  • Matching: query is a case-insensitive, diacritic-sensitive (modra does not find Modrá) substring of the product title in the answer’s language, or of the product code / external ids. Descriptions, brand names, variant SKUs and GTINs are not searched — use lookupCatalog for SKUs and ids.
  • Order: newest product first. There is no relevance ranking and no sort option.
  • What is listed: published, enabled, listed and orderable products the storefront shows (its exclusion filters and blocked categories apply). A product without a priced variant, or with no variant inside the price filter, is dropped after the page is read, so a page can be short — even empty while has_next_page is true. Keep following cursor until has_next_page is false.
  • Paging: pagination.limit default 10, at most 20 (larger values are lowered). The cursor encodes only the page number; repeat the query, filters, limit and context with it. total_count is the number of matching listed products and is left out whenever a price filter is in effect. Pages are counted over a listing that can change while you page (a product published meanwhile shifts the later pages): de-duplicate products by id.
  • Prices: in the currency of the answer’s language; filters.price is in context.currency (see PriceFilter). Search reads the shop’s product listing, a materialised view refreshed in the background after catalog changes: a new or changed product can take a few minutes to show here (lookup and product read the product data itself).
  • Categories: not supported; a non-empty filters.categories → 400.

An absent body — or one of a content type the API does not parse (anything but application/json and application/x-www-form-urlencoded) — is an empty search.

Parametre

  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavky

Optional. No body lists every product, newest first.

A catalog search. The whole body is optional: no body (or {}) lists every product. Unknown fields (signals, …) are ignored.

  • querystring | null

    Free text; longer text is cut to 200 characters. Matches, case-insensitively (but not ignoring diacritics) as a substring, the product title in the answer’s language or the product code / external ids — not descriptions, brands, variant SKUs or GTINs (use lookup for SKUs). Absent: every listed product.

    maxLength: 200
  • Filters. Other filters (brand, availability …) are ignored without a message.

  • paginationobject

    Paging.

    • limitinteger

      Page size, default 10. A value above 20 is lowered to 20; 0, decimals and strings → 400.

      predvolené:10minimum: 1
    • cursorstring | null

      pagination.cursor from the previous answer. Opaque, at most 40 characters. It carries only the page number: resend the same query, filters, limit and context with it.

      maxLength: 40
  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

application/jsonSearch “marlin”, two per page
{
  "query": "marlin",
  "pagination": {
    "limit": 2
  },
  "context": {
    "language": "sk",
    "address_country": "SK",
    "currency": "EUR"
  }
}
application/jsonThe next page
{
  "query": "marlin",
  "pagination": {
    "limit": 2,
    "cursor": "cDoy"
  },
  "context": {
    "language": "sk",
    "address_country": "SK",
    "currency": "EUR"
  }
}
application/jsonA price filter in another currency
{
  "query": "marlin",
  "filters": {
    "price": {
      "max": 2500000
    }
  },
  "pagination": {
    "limit": 2
  },
  "context": {
    "language": "sk",
    "currency": "CZK"
  }
}

Odpovede

200A page of results (CatalogSearchResponse), or — when your profile does not declare dev.ucp.shopping.catalog.search — the capabilities_incompatible business err…

A page of results (CatalogSearchResponse), or — when your profile does not declare dev.ucp.shopping.catalog.search — the capabilities_incompatible business error.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. CatalogSearchResponseobject · CatalogSearchResponse

    A page of search results.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • productsarray<Product>povinné

      Newest product first (there is no relevance ranking).

    • paginationCatalogPaginationpovinné

      Search paging.

    • messagesarray<Message>

      Present only when non-empty. Search emits only price_filter_ignored.

      minItems: 1
  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonFirst page: two products, more to come
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "products": [
    {
      "id": "6f1d2b7c-3a84-4e59-b0c2-8d7e1f4a5b62",
      "handle": "trek-marlin-5",
      "title": "Trek Marlin 5",
      "description": {
        "plain": "Horský bicykel s hliníkovým rámom Alpha Silver, odpruženou vidlicou a 29\" kolesami.",
        "html": "<p>Horský bicykel pre začínajúcich jazdcov.</p><ul><li>Rám Alpha Silver Aluminium</li><li>Vidlica SR Suntour XCT 30, zdvih 100 mm</li><li>Pohon Shimano 2×8</li></ul>"
      },
      "url": "https://shop.example.com/products/trek-marlin-5",
      "price_range": {
        "min": {
          "amount": 69900,
          "currency": "EUR"
        },
        "max": {
          "amount": 71900,
          "currency": "EUR"
        }
      },
      "list_price_range": {
        "min": {
          "amount": 74900,
          "currency": "EUR"
        },
        "max": {
          "amount": 74900,
          "currency": "EUR"
        }
      },
      "media": [
        {
          "type": "image",
          "url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp",
          "alt_text": "Trek Marlin 5, modrá, bočný pohľad"
        },
        {
          "type": "image",
          "url": "https://images.vendor.example/trek/marlin-5-detail.jpg"
        }
      ],
      "options": [
        {
          "name": "Farba",
          "values": [
            {
              "label": "Modrá"
            },
            {
              "label": "Čierna"
            }
          ]
        },
        {
          "name": "Veľkosť",
          "values": [
            {
              "label": "M"
            },
            {
              "label": "L"
            }
          ]
        }
      ],
      "variants": [
        {
          "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
          "sku": "MARLIN5-M-MOD",
          "barcodes": [
            {
              "type": "EAN",
              "value": "8594012345674"
            }
          ],
          "title": "Modrá / M",
          "description": {
            "plain": "Modrá / M"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 69900,
            "currency": "EUR"
          },
          "list_price": {
            "amount": 74900,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "in_stock"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Modrá"
            },
            {
              "name": "Veľkosť",
              "label": "M"
            }
          ]
        },
        {
          "id": "7c2f9b1e-4a68-4e3d-b2c9-5d1f7a3e8b06",
          "sku": "MARLIN5-L-MOD",
          "title": "Modrá / L",
          "description": {
            "plain": "Modrá / L"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 69900,
            "currency": "EUR"
          },
          "availability": {
            "available": false,
            "status": "out_of_stock"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Modrá"
            },
            {
              "name": "Veľkosť",
              "label": "L"
            }
          ]
        },
        {
          "id": "d93b4a7e-1f26-4c85-a0e7-3b9c6d2f4e18",
          "sku": "MARLIN5-M-CIE",
          "title": "Čierna / M",
          "description": {
            "plain": "Čierna / M"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 71900,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "preorder"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Čierna"
            },
            {
              "name": "Veľkosť",
              "label": "M"
            }
          ]
        }
      ],
      "tags": [
        "Trek"
      ]
    },
    {
      "id": "2f6c8a1d-7e43-4b95-9c2a-4d1e8b6f3a07",
      "handle": "trek-marlin-7",
      "title": "Trek Marlin 7",
      "description": {
        "plain": "Trek Marlin 7"
      },
      "url": "https://shop.example.com/products/trek-marlin-7",
      "price_range": {
        "min": {
          "amount": 104900,
          "currency": "EUR"
        },
        "max": {
          "amount": 104900,
          "currency": "EUR"
        }
      },
      "media": [
        {
          "type": "image",
          "url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-7.webp"
        }
      ],
      "options": [
        {
          "name": "Veľkosť",
          "values": [
            {
              "label": "L"
            }
          ]
        }
      ],
      "variants": [
        {
          "id": "c8e2a5f1-3d79-4a16-b4e3-9f6c2d8a1e54",
          "sku": "MARLIN7-L",
          "title": "L",
          "description": {
            "plain": "L"
          },
          "url": "https://shop.example.com/products/trek-marlin-7",
          "price": {
            "amount": 104900,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "in_stock"
          },
          "options": [
            {
              "name": "Veľkosť",
              "label": "L"
            }
          ]
        }
      ],
      "tags": [
        "Trek"
      ]
    }
  ],
  "pagination": {
    "has_next_page": true,
    "cursor": "cDoy",
    "total_count": 3
  }
}
application/jsonPrice filter ignored for lack of an exchange rate
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "products": [
    {
      "id": "6f1d2b7c-3a84-4e59-b0c2-8d7e1f4a5b62",
      "handle": "trek-marlin-5",
      "title": "Trek Marlin 5",
      "description": {
        "plain": "Horský bicykel s hliníkovým rámom Alpha Silver, odpruženou vidlicou a 29\" kolesami.",
        "html": "<p>Horský bicykel pre začínajúcich jazdcov.</p><ul><li>Rám Alpha Silver Aluminium</li><li>Vidlica SR Suntour XCT 30, zdvih 100 mm</li><li>Pohon Shimano 2×8</li></ul>"
      },
      "url": "https://shop.example.com/products/trek-marlin-5",
      "price_range": {
        "min": {
          "amount": 69900,
          "currency": "EUR"
        },
        "max": {
          "amount": 71900,
          "currency": "EUR"
        }
      },
      "list_price_range": {
        "min": {
          "amount": 74900,
          "currency": "EUR"
        },
        "max": {
          "amount": 74900,
          "currency": "EUR"
        }
      },
      "media": [
        {
          "type": "image",
          "url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp",
          "alt_text": "Trek Marlin 5, modrá, bočný pohľad"
        },
        {
          "type": "image",
          "url": "https://images.vendor.example/trek/marlin-5-detail.jpg"
        }
      ],
      "options": [
        {
          "name": "Farba",
          "values": [
            {
              "label": "Modrá"
            },
            {
              "label": "Čierna"
            }
          ]
        },
        {
          "name": "Veľkosť",
          "values": [
            {
              "label": "M"
            },
            {
              "label": "L"
            }
          ]
        }
      ],
      "variants": [
        {
          "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
          "sku": "MARLIN5-M-MOD",
          "barcodes": [
            {
              "type": "EAN",
              "value": "8594012345674"
            }
          ],
          "title": "Modrá / M",
          "description": {
            "plain": "Modrá / M"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 69900,
            "currency": "EUR"
          },
          "list_price": {
            "amount": 74900,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "in_stock"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Modrá"
            },
            {
              "name": "Veľkosť",
              "label": "M"
            }
          ]
        },
        {
          "id": "7c2f9b1e-4a68-4e3d-b2c9-5d1f7a3e8b06",
          "sku": "MARLIN5-L-MOD",
          "title": "Modrá / L",
          "description": {
            "plain": "Modrá / L"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 69900,
            "currency": "EUR"
          },
          "availability": {
            "available": false,
            "status": "out_of_stock"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Modrá"
            },
            {
              "name": "Veľkosť",
              "label": "L"
            }
          ]
        },
        {
          "id": "d93b4a7e-1f26-4c85-a0e7-3b9c6d2f4e18",
          "sku": "MARLIN5-M-CIE",
          "title": "Čierna / M",
          "description": {
            "plain": "Čierna / M"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 71900,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "preorder"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Čierna"
            },
            {
              "name": "Veľkosť",
              "label": "M"
            }
          ]
        }
      ],
      "tags": [
        "Trek"
      ]
    },
    {
      "id": "2f6c8a1d-7e43-4b95-9c2a-4d1e8b6f3a07",
      "handle": "trek-marlin-7",
      "title": "Trek Marlin 7",
      "description": {
        "plain": "Trek Marlin 7"
      },
      "url": "https://shop.example.com/products/trek-marlin-7",
      "price_range": {
        "min": {
          "amount": 104900,
          "currency": "EUR"
        },
        "max": {
          "amount": 104900,
          "currency": "EUR"
        }
      },
      "media": [
        {
          "type": "image",
          "url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-7.webp"
        }
      ],
      "options": [
        {
          "name": "Veľkosť",
          "values": [
            {
              "label": "L"
            }
          ]
        }
      ],
      "variants": [
        {
          "id": "c8e2a5f1-3d79-4a16-b4e3-9f6c2d8a1e54",
          "sku": "MARLIN7-L",
          "title": "L",
          "description": {
            "plain": "L"
          },
          "url": "https://shop.example.com/products/trek-marlin-7",
          "price": {
            "amount": 104900,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "in_stock"
          },
          "options": [
            {
              "name": "Veľkosť",
              "label": "L"
            }
          ]
        }
      ],
      "tags": [
        "Trek"
      ]
    }
  ],
  "pagination": {
    "has_next_page": true,
    "cursor": "cDoy",
    "total_count": 3
  },
  "messages": [
    {
      "type": "warning",
      "code": "price_filter_ignored",
      "path": "$.filters.price",
      "content": "filters.price is denominated in CZK, prices are presented in EUR and cannot be converted, so the price filter was not applied"
    }
  ]
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. For this operation: request must be an object (a JSON body that is not an object), request.query must be a string, filters must be an object, filters.price must be an object, filters.price.min|max must be a non-negative integer, filters.categories must be an array of strings, filters.categories is not supported by this shop; search by query (and filters.price) instead, pagination must be an object, pagination.limit must be a positive integer (also null), pagination.cursor is not valid, context must be an object, context.<field> must be a string.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonCategory filter refused
{
  "code": "invalid_request",
  "content": "filters.categories is not supported by this shop; search by query (and filters.price) instead"
}
application/jsonCursor not valid
{
  "code": "invalid_request",
  "content": "pagination.cursor is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "This shop is temporarily unavailable" (Retry-After: 60) — the company is deactivated or billing-locked, or the s…Celá odpoveď: ServiceUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/catalog/search' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "query": "marlin",
    "pagination": {
      "limit": 2
    },
    "context": {
      "language": "sk",
      "address_country": "SK",
      "currency": "EUR"
    }
  }'

Look products up by identifier

POST/ucp/v1/catalog/lookup

Up to 20 identifiers in one call — variant ids, variant SKUs, product ids, product handles — answered from the product data itself, not from the search listing.

  • A variant id or SKU matches that variant (inputs[].match: "exact"); a SKU can match variants of several products. A product id or handle (in any language) matches the product’s featured variant ("featured"). An identifier that matched a variant is not tried as a product.
  • Each product appears once, in the order its first identifier appears in ids, and lists only the variants that were asked for; its price_range, list_price_range and options still describe all its priced variants (within the price filter, when one is set).
  • Every identifier that matched nothing — unknown, hidden by the shop, or the asked variant has no price — gets an info not_found message whose content is the identifier.
  • Unlike search, a product the shop does not list in its catalog pages (but would show by its URL) is found, and a product that is not orderable is returned with every variant discontinued.
  • filters.price works as in search; an asked variant outside it is reported not_found.

Parametre

  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavkypovinné

The identifiers.

A batch lookup by identifier. Unknown fields are ignored.

  • idsarray<string>povinné

    Up to 20 identifiers, each a variant id (any case), a variant SKU (exact, case-sensitive; several products may share one), a product id or a product handle (in any language). Non-string and blank entries are dropped, exact duplicates removed. Empty or missing → 400 invalid_request; more than 20 → 400 request_too_large.

    minItems: 1maxItems: 20každá položka:maxLength: 255
  • Filters. Other filters (brand, availability …) are ignored without a message.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

application/jsonFour identifiers of four kinds
{
  "ids": [
    "7C2F9B1E-4A68-4E3D-B2C9-5D1F7A3E8B06",
    "BOTTLE-750-BLK",
    "trek-marlin-9",
    "a2c94e18-5d37-4b6f-8e21-0f9b3c7d6e15"
  ],
  "context": {
    "language": "sk"
  }
}

Odpovede

200The products found (CatalogLookupResponse) — HTTP 200 also when none was — or the capabilities_incompatible business error.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. CatalogLookupResponseobject · CatalogLookupResponse

    Lookup results. No pagination.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • productsarray<Product>povinné

      One entry per product, in the order its first identifier appears in ids; each holds only the matched variants.

    • messagesarray<Message>

      Present only when non-empty: price_filter_ignored first, then one info not_found per identifier that matched nothing, in ids order.

      minItems: 1
  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonMatched variants only, plus one not_found
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.catalog.lookup": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "products": [
    {
      "id": "6f1d2b7c-3a84-4e59-b0c2-8d7e1f4a5b62",
      "handle": "trek-marlin-5",
      "title": "Trek Marlin 5",
      "description": {
        "plain": "Horský bicykel s hliníkovým rámom Alpha Silver, odpruženou vidlicou a 29\" kolesami.",
        "html": "<p>Horský bicykel pre začínajúcich jazdcov.</p><ul><li>Rám Alpha Silver Aluminium</li><li>Vidlica SR Suntour XCT 30, zdvih 100 mm</li><li>Pohon Shimano 2×8</li></ul>"
      },
      "url": "https://shop.example.com/products/trek-marlin-5",
      "price_range": {
        "min": {
          "amount": 69900,
          "currency": "EUR"
        },
        "max": {
          "amount": 71900,
          "currency": "EUR"
        }
      },
      "list_price_range": {
        "min": {
          "amount": 74900,
          "currency": "EUR"
        },
        "max": {
          "amount": 74900,
          "currency": "EUR"
        }
      },
      "media": [
        {
          "type": "image",
          "url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp",
          "alt_text": "Trek Marlin 5, modrá, bočný pohľad"
        },
        {
          "type": "image",
          "url": "https://images.vendor.example/trek/marlin-5-detail.jpg"
        }
      ],
      "options": [
        {
          "name": "Farba",
          "values": [
            {
              "label": "Modrá"
            },
            {
              "label": "Čierna"
            }
          ]
        },
        {
          "name": "Veľkosť",
          "values": [
            {
              "label": "M"
            },
            {
              "label": "L"
            }
          ]
        }
      ],
      "variants": [
        {
          "id": "7c2f9b1e-4a68-4e3d-b2c9-5d1f7a3e8b06",
          "sku": "MARLIN5-L-MOD",
          "title": "Modrá / L",
          "description": {
            "plain": "Modrá / L"
          },
          "url": "https://shop.example.com/products/trek-marlin-5",
          "price": {
            "amount": 69900,
            "currency": "EUR"
          },
          "availability": {
            "available": false,
            "status": "out_of_stock"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Modrá"
            },
            {
              "name": "Veľkosť",
              "label": "L"
            }
          ],
          "inputs": [
            {
              "id": "7C2F9B1E-4A68-4E3D-B2C9-5D1F7A3E8B06",
              "match": "exact"
            }
          ]
        }
      ],
      "tags": [
        "Trek"
      ]
    },
    {
      "id": "0b7e3f5a-9c12-4d8e-a6f4-2e1c5b9d7a83",
      "handle": "flasa-na-vodu-750-ml",
      "title": "Fľaša na vodu 750 ml",
      "description": {
        "plain": "Fľaša bez BPA s uzáverom proti vytečeniu & úchytom.",
        "html": "<p>Fľaša bez BPA s <b>uzáverom proti vytečeniu</b> &amp; úchytom.</p>"
      },
      "url": "https://shop.example.com/products/flasa-na-vodu-750-ml",
      "price_range": {
        "min": {
          "amount": 990,
          "currency": "EUR"
        },
        "max": {
          "amount": 990,
          "currency": "EUR"
        }
      },
      "options": [
        {
          "name": "Farba",
          "values": [
            {
              "label": "Čierna"
            }
          ]
        }
      ],
      "variants": [
        {
          "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
          "sku": "BOTTLE-750-BLK",
          "title": "Čierna",
          "description": {
            "plain": "Čierna"
          },
          "url": "https://shop.example.com/products/flasa-na-vodu-750-ml",
          "price": {
            "amount": 990,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "in_stock"
          },
          "options": [
            {
              "name": "Farba",
              "label": "Čierna"
            }
          ],
          "inputs": [
            {
              "id": "BOTTLE-750-BLK",
              "match": "exact"
            }
          ]
        }
      ]
    },
    {
      "id": "a2c94e18-5d37-4b6f-8e21-0f9b3c7d6e15",
      "handle": "cyklisticka-prilba-lumen",
      "title": "Cyklistická prilba Lumen",
      "description": {
        "plain": "Ľahká prilba s integrovaným zadným svetlom."
      },
      "url": "https://shop.example.com/products/cyklisticka-prilba-lumen",
      "price_range": {
        "min": {
          "amount": 5990,
          "currency": "EUR"
        },
        "max": {
          "amount": 5990,
          "currency": "EUR"
        }
      },
      "variants": [
        {
          "id": "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90",
          "sku": "LUMEN-HELM-M",
          "barcodes": [
            {
              "type": "UPC",
              "value": "859401234567"
            }
          ],
          "title": "Cyklistická prilba Lumen",
          "description": {
            "plain": "Cyklistická prilba Lumen"
          },
          "url": "https://shop.example.com/products/cyklisticka-prilba-lumen",
          "price": {
            "amount": 5990,
            "currency": "EUR"
          },
          "availability": {
            "available": true,
            "status": "in_stock"
          },
          "inputs": [
            {
              "id": "a2c94e18-5d37-4b6f-8e21-0f9b3c7d6e15",
              "match": "featured"
            }
          ]
        }
      ],
      "tags": [
        "Lumen"
      ]
    }
  ],
  "messages": [
    {
      "type": "info",
      "code": "not_found",
      "content": "trek-marlin-9"
    }
  ]
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. For this operation: request must be an object (also for no body), ids must be a non-empty array, and the price / category filter refusals of search.
  • request_too_large "ids holds at most 20 identifiers" — more than 20 entries in ids (HTTP 400, not 413).

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonNo ids
{
  "code": "invalid_request",
  "content": "ids must be a non-empty array"
}
application/jsonMore than 20 ids
{
  "code": "request_too_large",
  "content": "ids holds at most 20 identifiers"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "This shop is temporarily unavailable" (Retry-After: 60) — the company is deactivated or billing-locked, or the s…Celá odpoveď: ServiceUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/catalog/lookup' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "ids": [
      "7C2F9B1E-4A68-4E3D-B2C9-5D1F7A3E8B06",
      "BOTTLE-750-BLK",
      "trek-marlin-9",
      "a2c94e18-5d37-4b6f-8e21-0f9b3c7d6e15"
    ],
    "context": {
      "language": "sk"
    }
  }'

Get one product in detail

POST/ucp/v1/catalog/product

One product with all its priced variants and, for each option value, whether it exists and is available next to the current selection. Use it to drive a variant picker.

  • id may be a product id, a product handle or a variant id (SKUs are not accepted here).
  • The anchor variant is chosen by selected (all selections must match; when none does, selections are dropped from the end); else the variant named by id; else the featured variant. It comes first in variants, and selected in the answer repeats its options.
  • filters.price and preferences are ignored. Relaxed selections are not reported.
  • An unknown or hidden product is a 200 business error not_found, not an HTTP 404. This operation needs dev.ucp.shopping.catalog.lookup (there is no separate capability).

Parametre

  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavkypovinné

The product and an optional selection.

A product detail request. preferences and other unknown fields are ignored.

  • idstringpovinné

    A product id, a product handle or a variant id (SKUs are not accepted here). Required; longer text is cut to 255 characters.

    minLength: 1maxLength: 255
  • selectedarray<object> | null

    Option values to select (names and labels compared case-insensitively). The variant matching all of them becomes the anchor; when none does, selections are dropped from the end until one matches. selected wins over a variant id in id. Entries without both fields are skipped; at most the first 20 are read. Relaxation is not reported.

    maxItems: 20
    • namestringpovinné

      Option name.

      maxLength: 100
    • labelstringpovinné

      Option value.

      maxLength: 255
  • Only filters.categories is checked (and refused when non-blank); filters.price is ignored here.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

application/jsonProduct detail with a selection
{
  "id": "6f1d2b7c-3a84-4e59-b0c2-8d7e1f4a5b62",
  "selected": [
    {
      "name": "Farba",
      "label": "Čierna"
    },
    {
      "name": "Veľkosť",
      "label": "L"
    }
  ],
  "context": {
    "language": "sk"
  }
}

Odpovede

200The product (GetProductResponse), or a business error: not_found (unknown or hidden product) or capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. GetProductResponseobject · GetProductResponse

    One product in detail.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • productProductDetailpovinné

      A product as getProduct returns it: every priced variant, the selected one first, and per-value availability.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonDetail with the black variant selected
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.catalog.lookup": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "product": {
    "id": "6f1d2b7c-3a84-4e59-b0c2-8d7e1f4a5b62",
    "handle": "trek-marlin-5",
    "title": "Trek Marlin 5",
    "description": {
      "plain": "Horský bicykel s hliníkovým rámom Alpha Silver, odpruženou vidlicou a 29\" kolesami.",
      "html": "<p>Horský bicykel pre začínajúcich jazdcov.</p><ul><li>Rám Alpha Silver Aluminium</li><li>Vidlica SR Suntour XCT 30, zdvih 100 mm</li><li>Pohon Shimano 2×8</li></ul>"
    },
    "url": "https://shop.example.com/products/trek-marlin-5",
    "price_range": {
      "min": {
        "amount": 69900,
        "currency": "EUR"
      },
      "max": {
        "amount": 71900,
        "currency": "EUR"
      }
    },
    "list_price_range": {
      "min": {
        "amount": 74900,
        "currency": "EUR"
      },
      "max": {
        "amount": 74900,
        "currency": "EUR"
      }
    },
    "media": [
      {
        "type": "image",
        "url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp",
        "alt_text": "Trek Marlin 5, modrá, bočný pohľad"
      },
      {
        "type": "image",
        "url": "https://images.vendor.example/trek/marlin-5-detail.jpg"
      }
    ],
    "options": [
      {
        "name": "Farba",
        "values": [
          {
            "label": "Modrá",
            "exists": true,
            "available": true
          },
          {
            "label": "Čierna",
            "exists": true,
            "available": true
          }
        ]
      },
      {
        "name": "Veľkosť",
        "values": [
          {
            "label": "M",
            "exists": true,
            "available": true
          },
          {
            "label": "L",
            "exists": false,
            "available": false
          }
        ]
      }
    ],
    "variants": [
      {
        "id": "d93b4a7e-1f26-4c85-a0e7-3b9c6d2f4e18",
        "sku": "MARLIN5-M-CIE",
        "title": "Čierna / M",
        "description": {
          "plain": "Čierna / M"
        },
        "url": "https://shop.example.com/products/trek-marlin-5",
        "price": {
          "amount": 71900,
          "currency": "EUR"
        },
        "availability": {
          "available": true,
          "status": "preorder"
        },
        "options": [
          {
            "name": "Farba",
            "label": "Čierna"
          },
          {
            "name": "Veľkosť",
            "label": "M"
          }
        ]
      },
      {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "sku": "MARLIN5-M-MOD",
        "barcodes": [
          {
            "type": "EAN",
            "value": "8594012345674"
          }
        ],
        "title": "Modrá / M",
        "description": {
          "plain": "Modrá / M"
        },
        "url": "https://shop.example.com/products/trek-marlin-5",
        "price": {
          "amount": 69900,
          "currency": "EUR"
        },
        "list_price": {
          "amount": 74900,
          "currency": "EUR"
        },
        "availability": {
          "available": true,
          "status": "in_stock"
        },
        "options": [
          {
            "name": "Farba",
            "label": "Modrá"
          },
          {
            "name": "Veľkosť",
            "label": "M"
          }
        ]
      },
      {
        "id": "7c2f9b1e-4a68-4e3d-b2c9-5d1f7a3e8b06",
        "sku": "MARLIN5-L-MOD",
        "title": "Modrá / L",
        "description": {
          "plain": "Modrá / L"
        },
        "url": "https://shop.example.com/products/trek-marlin-5",
        "price": {
          "amount": 69900,
          "currency": "EUR"
        },
        "availability": {
          "available": false,
          "status": "out_of_stock"
        },
        "options": [
          {
            "name": "Farba",
            "label": "Modrá"
          },
          {
            "name": "Veľkosť",
            "label": "L"
          }
        ]
      }
    ],
    "tags": [
      "Trek"
    ],
    "selected": [
      {
        "name": "Farba",
        "label": "Čierna"
      },
      {
        "name": "Veľkosť",
        "label": "M"
      }
    ]
  }
}
application/jsonUnknown or hidden product (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.catalog.lookup": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Product not found",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. For this operation: request must be an object (also for no body), id is required, the category filter refusal.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonNo product id
{
  "code": "invalid_request",
  "content": "id is required"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "This shop is temporarily unavailable" (Retry-After: 60) — the company is deactivated or billing-locked, or the s…Celá odpoveď: ServiceUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/catalog/product' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "id": "6f1d2b7c-3a84-4e59-b0c2-8d7e1f4a5b62",
    "selected": [
      {
        "name": "Farba",
        "label": "Čierna"
      },
      {
        "name": "Veľkosť",
        "label": "L"
      }
    ],
    "context": {
      "language": "sk"
    }
  }'

Cart

Carts (dev.ucp.shopping.cart): a basket priced by the shop, kept 7 days from the last write. A cart can be converted into a checkout (POST /ucp/v1/checkout-sessions with cart_id) or handed to the buyer directly through its continue_url — but only a checkout is ever marked completed by the buyer’s order. Carts carry no status field.

Create a cart

POST/ucp/v1/carts

Creates a cart priced by the shop: the shop’s own prices, VAT, markup rules and stock, in the currency of the session’s language.

  • Answers 201 with the cart. Lines that cannot be bought are left out with a message (item_unavailable, out_of_stock, vat_mix_not_allowed); a quantity above the sellable stock is lowered with quantity_adjusted. A line’s quantity above 999 is lowered to 999 without a message; lines of one item that add up to more than 999 are lowered with quantity_adjusted. line_items: [] creates an empty cart.
  • If lines were sent and none could be added, no cart is created: HTTP 200 business error out_of_stock (all sold out) or item_unavailable.
  • With dev.ucp.shopping.discount the codes are applied (one at a time, see DiscountsRequest) and the answer has discounts.
  • The cart lives 7 days from its last write. It has a continue_url you can give the buyer — but only a checkout is ever marked completed by the buyer’s order, so convert the cart first if you want to follow the outcome.
  • Send an Idempotency-Key: a retried create without one makes a second cart.

Parametre

  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavkypovinné

A new cart. Unknown fields are ignored. Body problems are reported before anything is stored.

  • line_itemsarray<LineItemRequest>povinné

    The lines. Required; [] creates an empty cart. If lines are sent and none can be added the answer is a 200 business error and no cart exists.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

  • fulfillmentFulfillmentRequest | null

    Accepted and stored, never used for a cart (validated like a checkout’s when the fulfillment extension is negotiated, so a malformed one is still a 400).

application/jsonA bike and a helmet
{
  "line_items": [
    {
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47"
      },
      "quantity": 1
    },
    {
      "item": {
        "id": "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90"
      }
    }
  ],
  "buyer": {
    "email": "Jana.Novakova@Example.com"
  },
  "context": {
    "address_country": "SK",
    "language": "sk"
  }
}
application/jsonOnly a sold-out variant
{
  "line_items": [
    {
      "item": {
        "id": "7c2f9b1e-4a68-4e3d-b2c9-5d1f7a3e8b06"
      },
      "quantity": 1
    }
  ]
}

Odpovede

200No cart: none of the requested items could be added, or capabilities_incompatible (always a business error; typed as the union of the other cart answers so tha…

No cart: none of the requested items could be added, or capabilities_incompatible (always a business error; typed as the union of the other cart answers so that generated clients read the 201 cart correctly).

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Cartobject · Cart

    A cart. It has no status: a cancel answers the cart as it was (with continue_url), and only a later read shows not_found.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • idstring (uuid)povinné

      The cart id.

    • line_itemsarray<LineItem>povinné

      In the shop cart’s order, which need not be the order you sent.

    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • totalsarray<Total>povinné

      subtotal, discount? and total (never fulfillment on a cart).

      minItems: 2maxItems: 3
    • messagesarray<Message>

      Present only when non-empty. Line and discount messages appear only in the answer to the write that caused them (a cart the buyer took to the shop repeats its last answer, messages included).

      minItems: 1
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrlpovinné

      The buyer handoff link, https://{primary domain}/ucp/continue/{64 hex}. Give it to the BUYER to open in their browser; never call it yourself. It never changes during the session’s life and works while the session is open and unexpired — an incomplete checkout included: the first line that reaches a buyer’s cart freezes the session in the status it has, so give the link only at requires_escalation. Once a cart was converted, its own link hands over the checkout made from it. Not single-use: every distinct browser (or signed-in customer) that opens it gets the lines once.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonNone of the items could be added (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "out_of_stock",
      "content": "All requested items are currently out of stock",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
201The cart was created.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0

SchémaCart

application/jsonCart created (201)
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83",
  "line_items": [
    {
      "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "5c9e1a7f-2d48-4b36-a8f1-6e3d9c2b7a15",
      "item": {
        "id": "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90",
        "title": "Cyklistická prilba Lumen",
        "price": 5990
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 5990
        },
        {
          "type": "total",
          "amount": 5990
        }
      ]
    }
  ],
  "currency": "EUR",
  "totals": [
    {
      "type": "subtotal",
      "amount": 75890
    },
    {
      "type": "total",
      "amount": 75890
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "address_country": "SK",
    "language": "sk"
  },
  "discounts": {
    "codes": [],
    "applied": []
  },
  "continue_url": "https://shop.example.com/ucp/continue/e1f0907c4abcf12564330feef5762f8471cc386a3563ce53cf8342ce6be08b62",
  "expires_at": "2026-11-02T09:14:05.118Z"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. For this operation: request must be an object (also for no body), line_items must be an array, line_items holds at most 100 entries, line_items[i] must be an object, line_items[i].item must be an object, line_items[i].item.id is required, line_items[i].quantity must be a positive integer, buyer must be an object, buyer.<field> must be a string, context …, discounts.codes must be an array, discounts.codes holds at most 10 codes, the fulfillment … refusals. Nothing is stored.
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonA body field with the wrong shape
{
  "code": "invalid_request",
  "content": "line_items[0].quantity must be a positive integer"
}
application/jsonline_items missing on a cart
{
  "code": "invalid_request",
  "content": "line_items must be an array"
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "This shop is temporarily unavailable" (Retry-After: 60) — the company is deactivated or billing-locked, or the s…Celá odpoveď: ServiceUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/carts' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "line_items": [
      {
        "item": {
          "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47"
        },
        "quantity": 1
      },
      {
        "item": {
          "id": "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90"
        }
      }
    ],
    "buyer": {
      "email": "Jana.Novakova@Example.com"
    },
    "context": {
      "address_country": "SK",
      "language": "sk"
    }
  }'

Get a cart

GET/ucp/v1/carts/{id}

The cart as it is now. A read stores nothing, does not extend expires_at, and carries no line or discount messages (those come only with the write that caused them). Each line keeps the unit price it got when its variant entered the cart (or was re-added for a new language), unless the storefront has Live prices on cart load switched on (not visible through UCP): then every read and write prices the lines again.

  • An unknown, canceled or expired cart is a 200 business error not_found.
  • After the buyer took the cart to the shop (its continue_url page added the lines to the buyer’s shop cart), the last answer is returned again — its messages included — with a fresh ucp object; nothing on it says so. Line and discount messages of the write that produced that answer come back on every read: do not report them to the buyer again, and do not take them for a sign of the handoff.
  • This read does not wait for a write in progress on the same cart.

Parametre

  • idcestastringpovinné

    The cart’s id (a UUID, any letter case). An id that is not a UUID, belongs to another shop, or names a canceled or expired cart is answered with the 200 not_found business error, not a 4xx.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Odpovede

200The cart, or a business error: not_found or capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Cartobject · Cart

    A cart. It has no status: a cancel answers the cart as it was (with continue_url), and only a later read shows not_found.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • idstring (uuid)povinné

      The cart id.

    • line_itemsarray<LineItem>povinné

      In the shop cart’s order, which need not be the order you sent.

    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • totalsarray<Total>povinné

      subtotal, discount? and total (never fulfillment on a cart).

      minItems: 2maxItems: 3
    • messagesarray<Message>

      Present only when non-empty. Line and discount messages appear only in the answer to the write that caused them (a cart the buyer took to the shop repeats its last answer, messages included).

      minItems: 1
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrlpovinné

      The buyer handoff link, https://{primary domain}/ucp/continue/{64 hex}. Give it to the BUYER to open in their browser; never call it yourself. It never changes during the session’s life and works while the session is open and unexpired — an incomplete checkout included: the first line that reaches a buyer’s cart freezes the session in the status it has, so give the link only at requires_escalation. Once a cart was converted, its own link hands over the checkout made from it. Not single-use: every distinct browser (or signed-in customer) that opens it gets the lines once.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonCart read
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83",
  "line_items": [
    {
      "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "8f4a2c6e-1b93-4d57-b6e8-3a9c7d1f5e24",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "currency": "EUR",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/e1f0907c4abcf12564330feef5762f8471cc386a3563ce53cf8342ce6be08b62",
  "expires_at": "2026-11-02T09:16:47.402Z"
}
application/jsonUnknown, canceled or expired cart (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Cart not found or has expired",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
304Not Modified — only when the request sent If-None-Match equal to the current answer’s ETag (strong as the API generates it on every response, or the weak W/"…"…Celá odpoveď: NotModified
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "This shop is temporarily unavailable" (Retry-After: 60) — the company is deactivated or billing-locked, or the s…Celá odpoveď: ServiceUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl 'https://shop.example.com/ucp/v1/carts/9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8'

Update a cart

PUT/ucp/v1/carts/{id}

Replaces the cart’s lines and, when sent, its buyer, context and discount codes; fields you leave out keep their value. line_items is required and is a full replacement (a line you do not list is removed).

  • Every successful write extends expires_at to now + 7 days.
  • buyer replaces the whole stored buyer (a field you leave out is erased); null clears it.
  • A context.language different from the stored one that picks another shop language re-adds every line in it (its currency, new line ids).
  • After the buyer took the cart to the shop, a PUT changes nothing: the last answer comes back with a handed_off warning (no validation of the body’s lines either).
  • Requests on one cart are serialised (see 503).

Parametre

  • idcestastringpovinné

    The cart’s id (a UUID, any letter case). An id that is not a UUID, belongs to another shop, or names a canceled or expired cart is answered with the 200 not_found business error, not a 4xx.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavkypovinné

A cart update. Fields you leave out keep their stored value (except that line_items is required). Every write extends expires_at to 7 days from now.

  • line_itemsarray<LineItemRequest>povinné

    Required, full replacement: lines not listed are removed. Missing → 400 line_items must be an array.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

  • fulfillmentFulfillmentRequest | null

    Accepted and stored, never used for a cart (validated like a checkout’s when the fulfillment extension is negotiated, so a malformed one is still a 400).

application/jsonSwap the helmet for three bottles and add two codes
{
  "line_items": [
    {
      "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47"
      },
      "quantity": 1
    },
    {
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25"
      },
      "quantity": 3
    }
  ],
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ]
  }
}

Odpovede

200The updated cart (messages lists what was adjusted or refused); the unchanged cart with handed_off after a handoff; or a business error: not_found, capabilitie…

The updated cart (messages lists what was adjusted or refused); the unchanged cart with handed_off after a handoff; or a business error: not_found, capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Cartobject · Cart

    A cart. It has no status: a cancel answers the cart as it was (with continue_url), and only a later read shows not_found.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • idstring (uuid)povinné

      The cart id.

    • line_itemsarray<LineItem>povinné

      In the shop cart’s order, which need not be the order you sent.

    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • totalsarray<Total>povinné

      subtotal, discount? and total (never fulfillment on a cart).

      minItems: 2maxItems: 3
    • messagesarray<Message>

      Present only when non-empty. Line and discount messages appear only in the answer to the write that caused them (a cart the buyer took to the shop repeats its last answer, messages included).

      minItems: 1
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrlpovinné

      The buyer handoff link, https://{primary domain}/ucp/continue/{64 hex}. Give it to the BUYER to open in their browser; never call it yourself. It never changes during the session’s life and works while the session is open and unexpired — an incomplete checkout included: the first line that reaches a buyer’s cart freezes the session in the status it has, so give the link only at requires_escalation. Once a cart was converted, its own link hands over the checkout made from it. Not single-use: every distinct browser (or signed-in customer) that opens it gets the lines once.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonCart updated: quantity lowered, one code applied
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83",
  "line_items": [
    {
      "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "8f4a2c6e-1b93-4d57-b6e8-3a9c7d1f5e24",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "currency": "EUR",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "messages": [
    {
      "type": "warning",
      "code": "quantity_adjusted",
      "content": "Only 2 available — quantity lowered from 3",
      "path": "$.line_items[1].quantity"
    },
    {
      "type": "warning",
      "code": "discount_code_combination_disallowed",
      "path": "$.discounts.codes[1]",
      "content": "Code 'LETO5' cannot be combined with 'JESEN10'"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/e1f0907c4abcf12564330feef5762f8471cc386a3563ce53cf8342ce6be08b62",
  "expires_at": "2026-11-02T09:16:47.402Z"
}
application/jsonWrite after the buyer took the cart to the shop
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83",
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "currency": "EUR",
  "messages": [
    {
      "code": "quantity_adjusted",
      "path": "$.line_items[1].quantity",
      "type": "warning",
      "content": "Only 2 available — quantity lowered from 3"
    },
    {
      "code": "discount_code_combination_disallowed",
      "path": "$.discounts.codes[1]",
      "type": "warning",
      "content": "Code 'LETO5' cannot be combined with 'JESEN10'"
    },
    {
      "type": "warning",
      "code": "handed_off",
      "content": "The buyer continued on the shop: the items are in the shop’s cart now and this session is no longer changed"
    }
  ],
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-11-02T09:16:47.402Z",
  "line_items": [
    {
      "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "price": 69900,
        "title": "Trek Marlin 5",
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ],
      "quantity": 1
    },
    {
      "id": "8f4a2c6e-1b93-4d57-b6e8-3a9c7d1f5e24",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "price": 990,
        "title": "Fľaša na vodu 750 ml"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ],
      "quantity": 2
    }
  ],
  "continue_url": "https://shop.example.com/ucp/continue/e1f0907c4abcf12564330feef5762f8471cc386a3563ce53cf8342ce6be08b62"
}
application/jsonUnknown, canceled or expired cart (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Cart not found or has expired",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. As for createCart; line_items missing → line_items must be an array. The cart is not changed.
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonline_items missing on a cart
{
  "code": "invalid_request",
  "content": "line_items must be an array"
}
application/jsonA body field with the wrong shape
{
  "code": "invalid_request",
  "content": "line_items[0].quantity must be a positive integer"
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X PUT 'https://shop.example.com/ucp/v1/carts/9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "line_items": [
      {
        "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
        "item": {
          "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47"
        },
        "quantity": 1
      },
      {
        "item": {
          "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25"
        },
        "quantity": 3
      }
    ],
    "discounts": {
      "codes": [
        "JESEN10",
        "LETO5"
      ]
    }
  }'

Cancel a cart

POST/ucp/v1/carts/{id}/cancel

Cancels the cart. The answer is the cart as it was — with continue_url and the stored expires_at, because a cart has no status. From then on every operation on it answers not_found, and its continue_url shows the “gone” page.

  • Any request body is ignored (the Idempotency-Key hashes {}).
  • Canceling a cart does not cancel a checkout made from it.

Parametre

  • idcestastringpovinné

    The cart’s id (a UUID, any letter case). An id that is not a UUID, belongs to another shop, or names a canceled or expired cart is answered with the 200 not_found business error, not a 4xx.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Odpovede

200The cart as it was when canceled, or a business error: not_found (unknown, already canceled or expired), capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Cartobject · Cart

    A cart. It has no status: a cancel answers the cart as it was (with continue_url), and only a later read shows not_found.

    additionalProperties: false
    • ucpResponseMetadatapovinné

      The ucp object of a successful catalog or cart answer. A missing status means success.

    • idstring (uuid)povinné

      The cart id.

    • line_itemsarray<LineItem>povinné

      In the shop cart’s order, which need not be the order you sent.

    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • totalsarray<Total>povinné

      subtotal, discount? and total (never fulfillment on a cart).

      minItems: 2maxItems: 3
    • messagesarray<Message>

      Present only when non-empty. Line and discount messages appear only in the answer to the write that caused them (a cart the buyer took to the shop repeats its last answer, messages included).

      minItems: 1
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrlpovinné

      The buyer handoff link, https://{primary domain}/ucp/continue/{64 hex}. Give it to the BUYER to open in their browser; never call it yourself. It never changes during the session’s life and works while the session is open and unexpired — an incomplete checkout included: the first line that reaches a buyer’s cart freezes the session in the status it has, so give the link only at requires_escalation. Once a cart was converted, its own link hands over the checkout made from it. Not single-use: every distinct browser (or signed-in customer) that opens it gets the lines once.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonCart canceled
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83",
  "line_items": [
    {
      "id": "e2b7d4a9-3c61-4f85-9a2e-7d1c5b8f3a46",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "8f4a2c6e-1b93-4d57-b6e8-3a9c7d1f5e24",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "currency": "EUR",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/e1f0907c4abcf12564330feef5762f8471cc386a3563ce53cf8342ce6be08b62",
  "expires_at": "2026-11-02T09:16:47.402Z"
}
application/jsonUnknown, canceled or expired cart (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.cart": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Cart not found or has expired",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request "The request is not valid" — a body…

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request "The request is not valid" — a body sent as JSON that does not parse (the body is ignored, but it is parsed before routing);
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/carts/9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83/cancel' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8'

Checkout

Checkout sessions (dev.ucp.shopping.checkout, with the fulfillment and discount extensions): buyer, shipping address and option, codes. Status incomplete → requires_escalation (ready; the buyer pays on the shop) → completed (the buyer placed the order) or canceled. Kept 24 h from the last write.

Create a checkout session

POST/ucp/v1/checkout-sessions

Creates a checkout session — directly from line_items, or by converting a cart (cart_id, when dev.ucp.shopping.cart is negotiated).

  • A new checkout answers 201. Its status is incomplete while anything the platform can provide is missing (every gap is a recoverable error with a path) or this write refused a line (item_unavailable, out_of_stock, vat_mix_not_allowed are recoverable errors too, without a path when the line is not in the answer; the next read no longer repeats them), else requires_escalation with payment_required.
  • Ready needs: at least one line, a valid buyer.email, and — with dev.ucp.shopping.fulfillment — a destination with street, city, postal code and a recognised country that the shop ships to, plus a selected_option_id among the offered options. Names and phone are optional. A Packeta option adds pickup_point_required (chosen by the buyer on the shop) but does not block readiness.
  • From a cart: lines, buyer, context and language come from the cart (this request’s line_items, buyer and context are ignored; buyer and context are still checked); fulfillment from this request; discount codes from this request when sent, else the cart’s. The checkout gets its own lines (new line ids); the cart is left as it was. A cart has at most one open checkout: converting it again answers that checkout with 200 and applies nothing from the request (update the checkout instead). A cart the buyer already took to the shop → 200 handed_off.
  • If the cart’s open checkout has expired, it is canceled and a new one is created (201).
  • If lines were sent and none could be added, no checkout is created (200 out_of_stock / item_unavailable).
  • The checkout lives 24 h from its last write. Send an Idempotency-Key: a retried create without one makes a second checkout.

Parametre

  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavkypovinné

A new checkout session, directly from lines or from a cart. Send line_items, or cart_id to convert a cart (read only when dev.ucp.shopping.cart is negotiated). Without a usable cart_id, line_items is required: a missing one is 400 invalid_request "line_items must be an array". Unknown fields are ignored.

  • cart_idstring

    Convert this cart (only when dev.ucp.shopping.cart is negotiated; otherwise it is ignored and line_items is required). The checkout takes the cart’s lines, buyer, context and language — line_items, buyer and context in this request are ignored (buyer and context are still checked: a malformed one is a 400) — its fulfillment from this request, and its discount codes from this request when discounts.codes is sent (else the cart’s). The cart itself is not changed. One open checkout per cart: when the cart already has one, that checkout is answered with HTTP 200 and this request’s fields are not applied. A cart whose lines the buyer already took to the shop → 200 handed_off. An unknown id → 200 not_found ("Checkout session not found").

  • line_itemsarray<LineItemRequest>

    The lines (direct create). Required unless cart_id converts a cart; [] creates an empty, incomplete checkout.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • fulfillmentFulfillmentRequest | null

    Shipping (only read when dev.ucp.shopping.fulfillment is negotiated; ignored on carts — a cart stores it but never uses it, and a conversion takes fulfillment from the conversion request). null or a body without fulfillment keeps what the session holds.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

application/jsonConvert the cart
{
  "cart_id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83"
}
application/jsonA checkout directly from lines
{
  "line_items": [
    {
      "item": {
        "id": "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90"
      },
      "quantity": 1
    }
  ],
  "buyer": {
    "first_name": "Peter",
    "last_name": "Horváth",
    "email": "peter.horvath@example.com"
  },
  "context": {
    "address_country": "SK",
    "language": "sk"
  },
  "fulfillment": {
    "methods": [
      {
        "type": "shipping",
        "destinations": [
          {
            "id": "office",
            "street_address": "Námestie SNP 5",
            "address_locality": "Banská Bystrica",
            "postal_code": "974 01",
            "address_country": "SK",
            "first_name": "Peter",
            "last_name": "Horváth"
          }
        ],
        "selected_destination_id": "office"
      }
    ]
  }
}

Odpovede

200The cart’s existing open checkout (nothing from this request applied), or a business error: handed_off, not_found (unknown cart_id, reported as "Checkout sessi…

The cart’s existing open checkout (nothing from this request applied), or a business error: handed_off, not_found (unknown cart_id, reported as "Checkout session not found"), out_of_stock / item_unavailable (nothing could be added), capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Checkoutobject · Checkout

    A checkout session.

    Once completed or canceled — and while the buyer’s handoff is done but the order not yet placed — the body is the last answer the platform received, with the current status (and order): it is not re-priced, and it does not show what the buyer changed on the shop. Only the lines and the applied discount code travel to the shop; the buyer enters address, shipping and payment there again.

    additionalProperties: false
    • The ucp object of every checkout answer that returns a checkout (any status).

    • idstring (uuid)povinné

      The checkout session id.

    • statusstring · CheckoutStatuspovinné
      • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
      • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
      • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
      • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

      ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

      jedna z hodnôt:incompleterequires_escalationcompletedcanceled
    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • line_itemsarray<LineItem>povinné

      In the shop cart’s order.

    • totalsarray<Total>povinné

      subtotal, discount?, fulfillment?, total.

      minItems: 2maxItems: 4
    • messagesarray<Message>povinné

      Always present, possibly empty. Order: line messages and discount messages (only in the answer to the write that caused them), then missing / invalid_address / address_undeliverable / invalid_option, pickup_point_required, and payment_required last. Empty on completed and canceled reads.

    • linksarray<Link>povinné

      Always present: at most 10, possibly empty.

      maxItems: 10
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • fulfillmentFulfillment

      Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrl

      Present while the checkout is incomplete or requires_escalation; absent once completed or canceled.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

    • orderOrder

      Present once completed.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonThe cart’s open checkout, answered again
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "incomplete",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "messages": [
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].destinations",
      "content": "A shipping address is required",
      "severity": "recoverable"
    },
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].groups[0].selected_option_id",
      "content": "Choose a shipping option",
      "severity": "recoverable"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": null
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b",
  "expires_at": "2026-10-27T09:18:12.930Z"
}
application/jsonConverting a cart the buyer already took to the shop (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "handed_off",
      "content": "The buyer continued on the shop: the items are in the shop’s cart — finish the purchase there",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/cart"
}
application/jsonUnknown checkout (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Checkout session not found",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
201A new checkout session.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0

SchémaCheckout

application/jsonCheckout created from the cart (201, incomplete)
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "incomplete",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "messages": [
    {
      "type": "warning",
      "code": "discount_code_combination_disallowed",
      "path": "$.discounts.codes[1]",
      "content": "Code 'LETO5' cannot be combined with 'JESEN10'"
    },
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].destinations",
      "content": "A shipping address is required",
      "severity": "recoverable"
    },
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].groups[0].selected_option_id",
      "content": "Choose a shipping option",
      "severity": "recoverable"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": null
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b",
  "expires_at": "2026-10-27T09:18:12.930Z"
}
application/jsonCheckout created from lines (201, incomplete)
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "b7d2e9a4-1c56-4e83-9f2b-6a4d8c1e3f75",
  "status": "incomplete",
  "currency": "EUR",
  "line_items": [
    {
      "id": "9c4f7a2e-6d18-4b39-a5c1-8e3f2d6b9a07",
      "item": {
        "id": "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90",
        "title": "Cyklistická prilba Lumen",
        "price": 5990
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 5990
        },
        {
          "type": "total",
          "amount": 5990
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 5990
    },
    {
      "type": "total",
      "amount": 5990
    }
  ],
  "messages": [
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].groups[0].selected_option_id",
      "content": "Choose a shipping option",
      "severity": "recoverable"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "first_name": "Peter",
    "last_name": "Horváth",
    "email": "peter.horvath@example.com"
  },
  "context": {
    "address_country": "SK",
    "language": "sk"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "9c4f7a2e-6d18-4b39-a5c1-8e3f2d6b9a07"
        ],
        "destinations": [
          {
            "id": "destination",
            "street_address": "Námestie SNP 5",
            "address_locality": "Banská Bystrica",
            "postal_code": "974 01",
            "address_country": "SK",
            "first_name": "Peter",
            "last_name": "Horváth"
          }
        ],
        "selected_destination_id": "destination",
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "9c4f7a2e-6d18-4b39-a5c1-8e3f2d6b9a07"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": null
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [],
    "applied": []
  },
  "continue_url": "https://shop.example.com/ucp/continue/44e9cdc7dee1005696a98525ce10c6090ab8a7eb3654a490c538c5caf3989e39",
  "expires_at": "2026-10-27T10:02:44.207Z"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. For this operation: as for createCart (without a usable cart_id, line_items is required), plus fulfillment must be an object, fulfillment.methods must be an array, fulfillment.methods[i] must be an object, fulfillment.methods[].destinations must be an array, fulfillment.methods[].destinations[i] must be an object, fulfillment.methods[].groups[0] must be an object, fulfillment.methods[].destinations[i].address_country is not a recognised country; send an ISO 3166-1 alpha-2 code such as "SK". Nothing is stored.
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonA body field with the wrong shape
{
  "code": "invalid_request",
  "content": "line_items[0].quantity must be a positive integer"
}
application/jsonUnrecognised destination country
{
  "code": "invalid_request",
  "content": "fulfillment.methods[].destinations[0].address_country is not a recognised country; send an ISO 3166-1 alpha-2 code such as \"SK\""
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/checkout-sessions' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "cart_id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83"
  }'

Get a checkout session

GET/ucp/v1/checkout-sessions/{id}

The checkout as it is now. Poll this to learn the outcome after giving the buyer continue_url: stop at status: "completed" (read order) or "canceled".

  • An open checkout is read again from its backing cart: its status is re-derived (incomplete ⇄ requires_escalation), the shipping options are quoted again, and the answer is stored. So a GET waits for writes on the same session and can answer 503 with Retry-After: 1. Keep one request per session in flight; poll every few seconds, not faster.
  • A read leaves the lines and the codes alone: no stock check, no code applied again, and each line keeps its unit price — unless the storefront has Live prices on cart load switched on (not visible through UCP), which prices the lines again on every read and write.
  • A GET does not extend expires_at. The first request on an open checkout at or after expires_at (a read, a write, or a conversion of its cart) turns it canceled for good; an order placed before that request still completes it.
  • Line and discount messages are not repeated on a read.
  • A completed or canceled checkout returns the last answer it gave, with the current status, messages: [], no continue_url and, once completed, order. An open one the buyer already took to the shop returns its last answer as it was (messages included: the line and discount messages of the write that produced it come back on every read — do not report them again). Neither is re-priced. A GET cannot tell whether the buyer opened the link.
  • An unknown id is a 200 business error not_found.

Parametre

  • idcestastringpovinné

    The checkout session’s id (a UUID, any letter case). An id that is not a UUID or belongs to another shop is answered with the 200 not_found business error, not a 4xx. Completed and canceled checkouts stay readable until 30 days after expires_at.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Odpovede

200The checkout (any status), or a business error: not_found, capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Checkoutobject · Checkout

    A checkout session.

    Once completed or canceled — and while the buyer’s handoff is done but the order not yet placed — the body is the last answer the platform received, with the current status (and order): it is not re-priced, and it does not show what the buyer changed on the shop. Only the lines and the applied discount code travel to the shop; the buyer enters address, shipping and payment there again.

    additionalProperties: false
    • The ucp object of every checkout answer that returns a checkout (any status).

    • idstring (uuid)povinné

      The checkout session id.

    • statusstring · CheckoutStatuspovinné
      • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
      • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
      • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
      • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

      ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

      jedna z hodnôt:incompleterequires_escalationcompletedcanceled
    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • line_itemsarray<LineItem>povinné

      In the shop cart’s order.

    • totalsarray<Total>povinné

      subtotal, discount?, fulfillment?, total.

      minItems: 2maxItems: 4
    • messagesarray<Message>povinné

      Always present, possibly empty. Order: line messages and discount messages (only in the answer to the write that caused them), then missing / invalid_address / address_undeliverable / invalid_option, pickup_point_required, and payment_required last. Empty on completed and canceled reads.

    • linksarray<Link>povinné

      Always present: at most 10, possibly empty.

      maxItems: 10
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • fulfillmentFulfillment

      Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrl

      Present while the checkout is incomplete or requires_escalation; absent once completed or canceled.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

    • orderOrder

      Present once completed.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonReady: the buyer must continue on the shop
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "requires_escalation",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "messages": [
    {
      "type": "error",
      "code": "payment_required",
      "content": "Payment is completed on the shop website — continue there to place the order",
      "severity": "requires_buyer_input"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "first_name": "Jana",
    "last_name": "Nováková",
    "email": "jana.novakova@example.com",
    "phone_number": "+421 905 123 456"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "destinations": [
          {
            "id": "destination",
            "street_address": "Hlavná 12",
            "address_locality": "Bratislava",
            "postal_code": "811 01",
            "address_country": "SK",
            "first_name": "Jana",
            "last_name": "Nováková"
          }
        ],
        "selected_destination_id": "destination",
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b",
  "expires_at": "2026-10-27T09:21:30.551Z"
}
application/jsonThe cart’s open checkout, answered again
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "incomplete",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "total",
      "amount": 66036
    }
  ],
  "messages": [
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].destinations",
      "content": "A shipping address is required",
      "severity": "recoverable"
    },
    {
      "type": "error",
      "code": "missing",
      "path": "$.fulfillment.methods[0].groups[0].selected_option_id",
      "content": "Choose a shipping option",
      "severity": "recoverable"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": null
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10",
      "LETO5"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b",
  "expires_at": "2026-10-27T09:18:12.930Z"
}
application/jsonCompleted by the buyer’s order
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "links": [
    {
      "url": "https://shop.example.com/obchodne-podmienky",
      "type": "terms_of_service",
      "title": "Obchodné podmienky"
    },
    {
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "type": "privacy_policy",
      "title": "Ochrana osobných údajov"
    }
  ],
  "status": "completed",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "currency": "EUR",
  "messages": [],
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "price": 69900,
        "title": "Trek Marlin 5",
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ],
      "quantity": 1
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "price": 990,
        "title": "Fľaša na vodu 750 ml"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ],
      "quantity": 2
    }
  ],
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "groups": [
          {
            "id": "default",
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ],
                "description": "Doručenie na adresu do 1–2 pracovných dní"
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ],
                "description": "Delivery in 2–3 business days"
              }
            ],
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "selected_destination_id": "destination"
      }
    ]
  },
  "order": {
    "id": "8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24",
    "label": "SK2600417",
    "permalink_url": "https://shop.example.com/order-confirmation?orderNumber=SK2600417&orderId=8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24"
  }
}
application/jsonCheckout canceled
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "canceled",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "messages": [],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "selected_destination_id": "destination",
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z"
}
application/jsonUnknown checkout (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Checkout session not found",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
304Not Modified — only when the request sent If-None-Match equal to the current answer’s ETag (strong as the API generates it on every response, or the weak W/"…"…Celá odpoveď: NotModified
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl 'https://shop.example.com/ucp/v1/checkout-sessions/3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8'

Update a checkout session

PUT/ucp/v1/checkout-sessions/{id}

Sets the buyer, the shipping destination and option, discount codes or the lines; fields you leave out keep their value. Answers the checkout with its status re-derived, the shipping options quoted again and, with the discount extension, the codes applied again.

  • line_items (optional) is a full replacement, checked like a create (stock, availability, the quantity caps); without it the lines are not checked again. buyer replaces the whole stored buyer. A destination can be replaced but not removed. selected_option_id: null clears the shipping choice.
  • A line keeps the unit price it got when its variant entered the session, also when its quantity changes. A new context.language re-adds every line at that language’s current prices. A storefront with Live prices on cart load on (not visible through UCP) prices the lines again on every read and write; elsewhere a checkout kept open by updates keeps its old prices — leave a line out of one update and send it again in the next to price it anew.
  • Every update of an open checkout extends expires_at to now + 24 h — also {}.
  • On a completed or canceled checkout nothing changes: it comes back with checkout_completed / checkout_canceled.
  • After the buyer opened continue_url (but before the order), nothing changes: the last answer comes back with a handed_off warning.
  • On an unknown, closed or handed-over checkout the body’s fields are not read (only a body that is not an object is refused): such a 200 says nothing about whether the body was valid.

Parametre

  • idcestastringpovinné

    The checkout session’s id (a UUID, any letter case). An id that is not a UUID or belongs to another shop is answered with the 200 not_found business error, not a 4xx. Completed and canceled checkouts stay readable until 30 days after expires_at.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavkypovinné

A checkout update. Fields you leave out keep their stored value; {} is valid: it re-derives the status, quotes the shipping options again, applies the stored discount codes again (with the discount extension) and extends expires_at; it does not check the lines again. Every update of an open checkout extends expires_at to 24 h from now.

  • line_itemsarray<LineItemRequest>

    Optional. When sent, full replacement; absent keeps the lines.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • fulfillmentFulfillmentRequest | null

    Shipping (only read when dev.ucp.shopping.fulfillment is negotiated; ignored on carts — a cart stores it but never uses it, and a conversion takes fulfillment from the conversion request). null or a body without fulfillment keeps what the session holds.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

application/jsonBuyer, address, shipping option, one code
{
  "buyer": {
    "first_name": "Jana",
    "last_name": "Nováková",
    "email": "jana.novakova@example.com",
    "phone_number": "+421 905 123 456"
  },
  "fulfillment": {
    "methods": [
      {
        "type": "shipping",
        "destinations": [
          {
            "id": "home",
            "street_address": "Hlavná 12",
            "address_locality": "Bratislava",
            "postal_code": "811 01",
            "address_country": "SK",
            "first_name": "Jana",
            "last_name": "Nováková"
          }
        ],
        "selected_destination_id": "home",
        "groups": [
          {
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10"
    ]
  }
}

Odpovede

200The checkout after the update; the unchanged checkout with handed_off, checkout_completed or checkout_canceled; or a business error: not_found, capabilities_in…

The checkout after the update; the unchanged checkout with handed_off, checkout_completed or checkout_canceled; or a business error: not_found, capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Checkoutobject · Checkout

    A checkout session.

    Once completed or canceled — and while the buyer’s handoff is done but the order not yet placed — the body is the last answer the platform received, with the current status (and order): it is not re-priced, and it does not show what the buyer changed on the shop. Only the lines and the applied discount code travel to the shop; the buyer enters address, shipping and payment there again.

    additionalProperties: false
    • The ucp object of every checkout answer that returns a checkout (any status).

    • idstring (uuid)povinné

      The checkout session id.

    • statusstring · CheckoutStatuspovinné
      • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
      • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
      • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
      • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

      ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

      jedna z hodnôt:incompleterequires_escalationcompletedcanceled
    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • line_itemsarray<LineItem>povinné

      In the shop cart’s order.

    • totalsarray<Total>povinné

      subtotal, discount?, fulfillment?, total.

      minItems: 2maxItems: 4
    • messagesarray<Message>povinné

      Always present, possibly empty. Order: line messages and discount messages (only in the answer to the write that caused them), then missing / invalid_address / address_undeliverable / invalid_option, pickup_point_required, and payment_required last. Empty on completed and canceled reads.

    • linksarray<Link>povinné

      Always present: at most 10, possibly empty.

      maxItems: 10
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • fulfillmentFulfillment

      Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrl

      Present while the checkout is incomplete or requires_escalation; absent once completed or canceled.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

    • orderOrder

      Present once completed.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonReady: the buyer must continue on the shop
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "requires_escalation",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "messages": [
    {
      "type": "error",
      "code": "payment_required",
      "content": "Payment is completed on the shop website — continue there to place the order",
      "severity": "requires_buyer_input"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "first_name": "Jana",
    "last_name": "Nováková",
    "email": "jana.novakova@example.com",
    "phone_number": "+421 905 123 456"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "destinations": [
          {
            "id": "destination",
            "street_address": "Hlavná 12",
            "address_locality": "Bratislava",
            "postal_code": "811 01",
            "address_country": "SK",
            "first_name": "Jana",
            "last_name": "Nováková"
          }
        ],
        "selected_destination_id": "destination",
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b",
  "expires_at": "2026-10-27T09:21:30.551Z"
}
application/jsonWriting after the handoff
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "links": [
    {
      "url": "https://shop.example.com/obchodne-podmienky",
      "type": "terms_of_service",
      "title": "Obchodné podmienky"
    },
    {
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "type": "privacy_policy",
      "title": "Ochrana osobných údajov"
    }
  ],
  "status": "requires_escalation",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "currency": "EUR",
  "messages": [
    {
      "code": "payment_required",
      "type": "error",
      "content": "Payment is completed on the shop website — continue there to place the order",
      "severity": "requires_buyer_input"
    },
    {
      "type": "warning",
      "code": "handed_off",
      "content": "The buyer continued on the shop: the items are in the shop’s cart now and this session is no longer changed"
    }
  ],
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "price": 69900,
        "title": "Trek Marlin 5",
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ],
      "quantity": 1
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "price": 990,
        "title": "Fľaša na vodu 750 ml"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ],
      "quantity": 2
    }
  ],
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "groups": [
          {
            "id": "default",
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ],
                "description": "Doručenie na adresu do 1–2 pracovných dní"
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ],
                "description": "Delivery in 2–3 business days"
              }
            ],
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "selected_destination_id": "destination"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b"
}
application/jsonWriting to a completed checkout
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "links": [
    {
      "url": "https://shop.example.com/obchodne-podmienky",
      "type": "terms_of_service",
      "title": "Obchodné podmienky"
    },
    {
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "type": "privacy_policy",
      "title": "Ochrana osobných údajov"
    }
  ],
  "status": "completed",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "currency": "EUR",
  "messages": [
    {
      "type": "error",
      "code": "checkout_completed",
      "content": "This checkout is already completed",
      "severity": "unrecoverable"
    }
  ],
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "price": 69900,
        "title": "Trek Marlin 5",
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ],
      "quantity": 1
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "price": 990,
        "title": "Fľaša na vodu 750 ml"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ],
      "quantity": 2
    }
  ],
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "groups": [
          {
            "id": "default",
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ],
                "description": "Doručenie na adresu do 1–2 pracovných dní"
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ],
                "description": "Delivery in 2–3 business days"
              }
            ],
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "selected_destination_id": "destination"
      }
    ]
  },
  "order": {
    "id": "8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24",
    "label": "SK2600417",
    "permalink_url": "https://shop.example.com/order-confirmation?orderNumber=SK2600417&orderId=8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24"
  }
}
application/jsonUnknown checkout (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Checkout session not found",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request — a field this operation reads has …

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request — a field this operation reads has the wrong JSON type or value; content names it. Malformed JSON or a top-level JSON primitive says only "The request is not valid". Unknown fields are ignored, never refused. As for createCheckout. The checkout is not changed.
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonUnrecognised destination country
{
  "code": "invalid_request",
  "content": "fulfillment.methods[].destinations[0].address_country is not a recognised country; send an ISO 3166-1 alpha-2 code such as \"SK\""
}
application/jsonA body field with the wrong shape
{
  "code": "invalid_request",
  "content": "line_items[0].quantity must be a positive integer"
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X PUT 'https://shop.example.com/ucp/v1/checkout-sessions/3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{
    "buyer": {
      "first_name": "Jana",
      "last_name": "Nováková",
      "email": "jana.novakova@example.com",
      "phone_number": "+421 905 123 456"
    },
    "fulfillment": {
      "methods": [
        {
          "type": "shipping",
          "destinations": [
            {
              "id": "home",
              "street_address": "Hlavná 12",
              "address_locality": "Bratislava",
              "postal_code": "811 01",
              "address_country": "SK",
              "first_name": "Jana",
              "last_name": "Nováková"
            }
          ],
          "selected_destination_id": "home",
          "groups": [
            {
              "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
            }
          ]
        }
      ]
    },
    "discounts": {
      "codes": [
        "JESEN10"
      ]
    }
  }'

Complete a checkout session (no payment, no order)

POST/ucp/v1/checkout-sessions/{id}/complete

Never places an order and never takes a payment. UCP’s complete step is answered with the checkout as it stands: requires_escalation with payment_required and continue_url once it is ready (incomplete with its gaps otherwise), or completed with order once the buyer has ordered on the shop. It never makes a checkout completed: like getCheckout it re-derives incomplete ⇄ requires_escalation of an open checkout from what is stored (the shipping options quoted again, the lines and codes not checked again) and stores that answer, and it does not extend expires_at.

To finish a purchase, give continue_url to the buyer and poll getCheckout. Any payment data in the body is ignored and not stored.

Parametre

  • idcestastringpovinné

    The checkout session’s id (a UUID, any letter case). An id that is not a UUID or belongs to another shop is answered with the 200 not_found business error, not a 4xx. Completed and canceled checkouts stay readable until 30 days after expires_at.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Telo požiadavky

Ignored.

Ignored. Any payment, payment_data or AP2 field is ignored and never stored: no payment is processed through UCP. The body still counts for the Idempotency-Key’s request identity.

  • <ľubovoľný ďalší kľúč>any
application/jsonEmpty body
{}

Odpovede

200The checkout as it stands (a canceled one with checkout_canceled), or a business error: not_found, capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Checkoutobject · Checkout

    A checkout session.

    Once completed or canceled — and while the buyer’s handoff is done but the order not yet placed — the body is the last answer the platform received, with the current status (and order): it is not re-priced, and it does not show what the buyer changed on the shop. Only the lines and the applied discount code travel to the shop; the buyer enters address, shipping and payment there again.

    additionalProperties: false
    • The ucp object of every checkout answer that returns a checkout (any status).

    • idstring (uuid)povinné

      The checkout session id.

    • statusstring · CheckoutStatuspovinné
      • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
      • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
      • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
      • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

      ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

      jedna z hodnôt:incompleterequires_escalationcompletedcanceled
    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • line_itemsarray<LineItem>povinné

      In the shop cart’s order.

    • totalsarray<Total>povinné

      subtotal, discount?, fulfillment?, total.

      minItems: 2maxItems: 4
    • messagesarray<Message>povinné

      Always present, possibly empty. Order: line messages and discount messages (only in the answer to the write that caused them), then missing / invalid_address / address_undeliverable / invalid_option, pickup_point_required, and payment_required last. Empty on completed and canceled reads.

    • linksarray<Link>povinné

      Always present: at most 10, possibly empty.

      maxItems: 10
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • fulfillmentFulfillment

      Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrl

      Present while the checkout is incomplete or requires_escalation; absent once completed or canceled.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

    • orderOrder

      Present once completed.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonComplete changes nothing
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "requires_escalation",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "messages": [
    {
      "type": "error",
      "code": "payment_required",
      "content": "Payment is completed on the shop website — continue there to place the order",
      "severity": "requires_buyer_input"
    }
  ],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "selected_destination_id": "destination",
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "continue_url": "https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b",
  "expires_at": "2026-10-27T09:21:30.551Z"
}
application/jsonCompleted by the buyer’s order
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "links": [
    {
      "url": "https://shop.example.com/obchodne-podmienky",
      "type": "terms_of_service",
      "title": "Obchodné podmienky"
    },
    {
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "type": "privacy_policy",
      "title": "Ochrana osobných údajov"
    }
  ],
  "status": "completed",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "currency": "EUR",
  "messages": [],
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "price": 69900,
        "title": "Trek Marlin 5",
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ],
      "quantity": 1
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "price": 990,
        "title": "Fľaša na vodu 750 ml"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ],
      "quantity": 2
    }
  ],
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "groups": [
          {
            "id": "default",
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ],
                "description": "Doručenie na adresu do 1–2 pracovných dní"
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ],
                "description": "Delivery in 2–3 business days"
              }
            ],
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "selected_destination_id": "destination"
      }
    ]
  },
  "order": {
    "id": "8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24",
    "label": "SK2600417",
    "permalink_url": "https://shop.example.com/order-confirmation?orderNumber=SK2600417&orderId=8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24"
  }
}
application/jsonUnknown checkout (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Checkout session not found",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request "The request is not valid" — a body…

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request "The request is not valid" — a body sent as JSON that does not parse.
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/checkout-sessions/3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97/complete' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8' \
  -H 'Content-Type: application/json' \
  --data '{}'

Cancel a checkout session

POST/ucp/v1/checkout-sessions/{id}/cancel

Cancels an open checkout: status: "canceled", messages: [], no continue_url, the stored expires_at. Its continue_url then shows the “gone” page and a later order can no longer complete it.

  • A completed checkout is not changed (the order is never canceled through UCP): it comes back with checkout_completed. A canceled one comes back with checkout_canceled.
  • Any request body is ignored (the Idempotency-Key hashes {}).

Parametre

  • idcestastringpovinné

    The checkout session’s id (a UUID, any letter case). An id that is not a UUID or belongs to another shop is answered with the 200 not_found business error, not a 4xx. Completed and canceled checkouts stay readable until 30 days after expires_at.

    pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$príklad:3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97
  • UCP-Agenthlavičkastringpovinné

    Names your platform profile. Required on every /ucp/v1/* operation; never read by the profile and handoff routes.

    Podrobnosti

    Grammar — a subset of an RFC 8941 Dictionary, at most 4096 characters:

    • members separated by , (spaces or tabs around it allowed; a trailing comma is an error); keys lower-case, matching [a-z*][a-z0-9_\-.*]*;
    • the profile member must be an sf-string: profile="https://…", printable ASCII 0x20–0x7E, escapes \" and \\ only;
    • other members (tokens, numbers, booleans, strings) and parameters (;key=value) are parsed and ignored; when profile appears twice the last one wins;
    • any parse error makes the whole header unusable.

    Accepted: profile="https://platform.example.com/.well-known/ucp", version="2026-04-08", profile="https://platform.example.com/.well-known/ucp";sig=abc. Refused: a missing header, profile=https://… (a token, not a string), Profile="…", a=1 b=2, profile="…".

    The URL — at most 1000 characters, no whitespace anywhere, starting https://, no user name or password, a non-empty host. Any port, path and query string are accepted (the spec’s /.well-known/ucp path is not enforced). Failing any rule → 400 invalid_profile_url.

    The fetch — GET with Accept: application/json and User-Agent: Monokaido-UCP/1.0 (+https://monokaido.com); no redirects followed; only HTTP 200 accepted; at most 256 KiB; 3 s idle, 5 s total; the host must resolve to a public address (private, loopback, link-local and other reserved ranges are refused). Anything else → 424 profile_unreachable. A body that is not JSON, or whose ucp.version is not a YYYY-MM-DD string → 422 profile_malformed; a valid profile of another version → 422 version_unsupported. Only ucp.version and ucp.capabilities (name → [{"version"}], first 20 entries per name, names like dev.ucp.shopping.cart) are read; everything else in it is ignored.

    Caching — a fetched profile is cached for its Cache-Control: max-age, clamped to 60 s – 1 h (5 min without one; no-store, no-cache and private are ignored) and served up to 1 h past that while one background refresh runs. A failed fetch (424, or 422 profile_malformed) is remembered for 60 s. A profile of another ucp.version is a valid profile and is cached like one, so after you change its version expect 422 version_unsupported until that entry is refreshed (its max-age, then one more request) — or use a new profile URL. These caches are kept in memory by each API process. Fetching a profile the shop does not know yet is budgeted: 5 new profile URLs per minute per client (IPv4 address, IPv6 /64) and 60 per minute in all → 503 service_unavailable with Retry-After. The full URL string is the cache key, so keep it stable (no per-request query strings).

    minLength: 1maxLength: 4096príklad:profile="https://platform.example.com/.well-known/ucp"
  • Idempotency-Keyhlavičkastring

    Optional, strongly recommended. Without it (or with an empty value) the write simply runs: a retried create makes a second session.

    • Format: 1–255 printable ASCII characters 0x21–0x7E — no spaces. Anything else (also a header sent twice, which arrives joined with ", ") → 400 invalid_request "Idempotency-Key is not valid". A UUID v4 per logical write is a good choice.
    • Scope: this shop (all its domains) × the exact UCP-Agent profile URL string × the key.
    • Request identity: the operation, the path {id} as sent (after URL decoding; case included) and the JSON body compared canonically (object key order and whitespace do not matter, array order does). Cancel routes hash {} whatever the body.
    • Same key, same request, finished → the stored HTTP status and body are replayed — the same values, including a 200 business error, the original expires_at and the ucp object negotiated the first time, even when the session has changed since. The replay comes from storage, so its object keys come in JSONB order (and its ETag differs).
    • The UCP-Agent profile is resolved and negotiated before the key is read: a profile error or a 200 capabilities_incompatible answer is never stored.
    • Same key, different request → 409 idempotency_conflict. Same request still running → 409 with Retry-After: 2.
    • If the first request fails with a protocol error (400, 503 session busy, 500 …) the key is released: retrying with the same key runs the operation again.
    • A claim whose request died is taken over by the same request after 120 s. Keys are kept for at least 25 hours after their first use (an hourly job deletes older ones); do not rely on a key more than 24 hours after its first use.
    • Not read by catalog operations, GET routes, the profile or the handoff routes.
    pattern: ^[\x21-\x7E]{1,255}$minLength: 1maxLength: 255príklad:0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02
  • Accept-Languagehlavičkastring

    Fallback for the answer’s language after context.language in the body. The first 10 comma-separated tags are tried in header order (q= weights are ignored, * is skipped) against the languages the shop offers: an exact tag (case-insensitive), then the primary subtag (en-US → en-GB); with no match, the shop’s first language. (A storefront without a language list of its own offers every language of the company: then the primary subtag of the first usable tag is used as it is — a language the company has not set up is answered in its default currency, untranslated — and, with no tag at all, the company’s default language.)

    The language decides the currency of every price and the translations. A cart or checkout keeps the language it was created in: a later request changes it only when its body names a context.language different from the stored one, and when that switches the shop language every line is re-added in the new currency and gets a new line id. Once a session has a language, this header alone never switches it. On a storefront without a language list, a session created with no usable tag stores none: each later request is then answered in the language its own tags pick, and a different pick adds the lines again (new line ids). Send context.language on the create.

    príklad:sk-SK,sk;q=0.9,en;q=0.8

Odpovede

200The canceled checkout; a closed one with checkout_completed / checkout_canceled; or a business error: not_found, capabilities_incompatible.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0
oneOf

Práve jedna z možností

  1. Checkoutobject · Checkout

    A checkout session.

    Once completed or canceled — and while the buyer’s handoff is done but the order not yet placed — the body is the last answer the platform received, with the current status (and order): it is not re-priced, and it does not show what the buyer changed on the shop. Only the lines and the applied discount code travel to the shop; the buyer enters address, shipping and payment there again.

    additionalProperties: false
    • The ucp object of every checkout answer that returns a checkout (any status).

    • idstring (uuid)povinné

      The checkout session id.

    • statusstring · CheckoutStatuspovinné
      • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
      • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
      • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
      • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

      ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

      jedna z hodnôt:incompleterequires_escalationcompletedcanceled
    • currencystring · CurrencyCodepovinné

      ISO 4217 currency code.

      pattern: ^[A-Z]{3}$
    • line_itemsarray<LineItem>povinné

      In the shop cart’s order.

    • totalsarray<Total>povinné

      subtotal, discount?, fulfillment?, total.

      minItems: 2maxItems: 4
    • messagesarray<Message>povinné

      Always present, possibly empty. Order: line messages and discount messages (only in the answer to the write that caused them), then missing / invalid_address / address_undeliverable / invalid_option, pickup_point_required, and payment_required last. Empty on completed and canceled reads.

    • linksarray<Link>povinné

      Always present: at most 10, possibly empty.

      maxItems: 10
    • buyerBuyer

      The stored buyer; empty fields are left out. Absent when nothing is stored.

    • contextContext

      The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

    • fulfillmentFulfillment

      Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

    • discountsDiscounts

      Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

    • continue_urlstring (uri) · ContinueUrl

      Present while the checkout is incomplete or requires_escalation; absent once completed or canceled.

      pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
    • expires_atstring (date-time) · ExpiresAtpovinné

      When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

    • orderOrder

      Present once completed.

  2. UcpBusinessErrorResponseobject · UcpBusinessErrorResponse

    HTTP 200 with no resource. The cases:

    • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
    • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
    • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
    • handed_off — converting a cart whose lines the buyer already took to the shop.

    Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

    additionalProperties: false
    • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

    • messagesarray<MessageError>povinné

      Exactly one error message saying why no resource could be returned.

      minItems: 1maxItems: 1
    • continue_urlstring (uri)povinné

      Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.

application/jsonCheckout canceled
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "status": "canceled",
  "currency": "EUR",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "title": "Trek Marlin 5",
        "price": 69900,
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "quantity": 1,
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ]
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "title": "Fľaša na vodu 750 ml",
        "price": 990
      },
      "quantity": 2,
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ]
    }
  ],
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "messages": [],
  "links": [
    {
      "type": "terms_of_service",
      "url": "https://shop.example.com/obchodne-podmienky",
      "title": "Obchodné podmienky"
    },
    {
      "type": "privacy_policy",
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "title": "Ochrana osobných údajov"
    }
  ],
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "selected_destination_id": "destination",
        "groups": [
          {
            "id": "default",
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "description": "Doručenie na adresu do 1–2 pracovných dní",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ]
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "description": "Delivery in 2–3 business days",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ]
              }
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ]
      }
    ]
  },
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z"
}
application/jsonWriting to a completed checkout
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    },
    "payment_handlers": {}
  },
  "id": "3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97",
  "buyer": {
    "email": "jana.novakova@example.com",
    "last_name": "Nováková",
    "first_name": "Jana",
    "phone_number": "+421 905 123 456"
  },
  "links": [
    {
      "url": "https://shop.example.com/obchodne-podmienky",
      "type": "terms_of_service",
      "title": "Obchodné podmienky"
    },
    {
      "url": "https://shop.example.com/ochrana-osobnych-udajov",
      "type": "privacy_policy",
      "title": "Ochrana osobných údajov"
    }
  ],
  "status": "completed",
  "totals": [
    {
      "type": "subtotal",
      "amount": 71880
    },
    {
      "type": "discount",
      "amount": -5844,
      "display_text": "JESEN10"
    },
    {
      "type": "fulfillment",
      "amount": 490
    },
    {
      "type": "total",
      "amount": 66526
    }
  ],
  "context": {
    "language": "sk",
    "address_country": "SK"
  },
  "currency": "EUR",
  "messages": [
    {
      "type": "error",
      "code": "checkout_completed",
      "content": "This checkout is already completed",
      "severity": "unrecoverable"
    }
  ],
  "discounts": {
    "codes": [
      "JESEN10"
    ],
    "applied": [
      {
        "code": "JESEN10",
        "title": "JESEN10",
        "amount": 5844,
        "method": "across"
      }
    ]
  },
  "expires_at": "2026-10-27T09:21:30.551Z",
  "line_items": [
    {
      "id": "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
      "item": {
        "id": "1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47",
        "price": 69900,
        "title": "Trek Marlin 5",
        "image_url": "https://shop.example.com/api/public/uploads/c4d1e5a2-8b3f-4a6c-9e07-5f2d8a1b3c40/products/trek-marlin-5-modra.webp"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 69900
        },
        {
          "type": "total",
          "amount": 69900
        }
      ],
      "quantity": 1
    },
    {
      "id": "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49",
      "item": {
        "id": "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25",
        "price": 990,
        "title": "Fľaša na vodu 750 ml"
      },
      "totals": [
        {
          "type": "subtotal",
          "amount": 1980
        },
        {
          "type": "total",
          "amount": 1980
        }
      ],
      "quantity": 2
    }
  ],
  "fulfillment": {
    "methods": [
      {
        "id": "shipping",
        "type": "shipping",
        "groups": [
          {
            "id": "default",
            "options": [
              {
                "id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75",
                "title": "GLS kuriér",
                "totals": [
                  {
                    "type": "total",
                    "amount": 490
                  }
                ],
                "description": "Doručenie na adresu do 1–2 pracovných dní"
              },
              {
                "id": "6b2d9f4e-3a17-4c68-9d5b-1f8e3a7c2b40",
                "title": "Packeta – výdajné miesto",
                "totals": [
                  {
                    "type": "total",
                    "amount": 290
                  }
                ],
                "description": "Delivery in 2–3 business days"
              }
            ],
            "line_item_ids": [
              "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
              "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
            ],
            "selected_option_id": "0c5e8a3f-7b21-4d96-8f4c-9e2a6d1b3c75"
          }
        ],
        "destinations": [
          {
            "id": "destination",
            "last_name": "Nováková",
            "first_name": "Jana",
            "postal_code": "811 01",
            "street_address": "Hlavná 12",
            "address_country": "SK",
            "address_locality": "Bratislava"
          }
        ],
        "line_item_ids": [
          "f6a3c8e1-9d27-4b54-8e6a-2c1f7b9d3a58",
          "2e9d5b7a-4c83-4f16-a9b2-6d8e1c3f7a49"
        ],
        "selected_destination_id": "destination"
      }
    ]
  },
  "order": {
    "id": "8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24",
    "label": "SK2600417",
    "permalink_url": "https://shop.example.com/order-confirmation?orderNumber=SK2600417&orderId=8e1c4f7a-2d95-4b36-a7e8-5c3f9b1d6a24"
  }
}
application/jsonUnknown checkout (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-04-08"
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-04-08"
        }
      ]
    }
  },
  "messages": [
    {
      "type": "error",
      "code": "not_found",
      "content": "Checkout session not found",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
application/jsoncapabilities_incompatible (HTTP 200)
{
  "ucp": {
    "version": "2026-04-08",
    "status": "error",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "capabilities_incompatible",
      "content": "The platform and this business share no version of the capability this operation needs",
      "severity": "unrecoverable"
    }
  ],
  "continue_url": "https://shop.example.com/"
}
400Bad Request (Error): invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url); invalid_request "The request is not valid" — a body…

Bad Request (Error):

  • invalid_profile_url — the UCP-Agent header is missing or unusable (with continue_url);
  • invalid_request "The request is not valid" — a body sent as JSON that does not parse (the body is ignored, but it is parsed before routing);
  • invalid_request "Idempotency-Key is not valid" — see Idempotency-Key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUCP-Agent missing or unusable
{
  "code": "invalid_profile_url",
  "content": "The UCP-Agent header must carry profile=\"https://…\"",
  "continue_url": "https://shop.example.com/"
}
application/jsonIdempotency-Key malformed
{
  "code": "invalid_request",
  "content": "Idempotency-Key is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switc…Celá odpoveď: NoBusinessOnHost
409idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). W…Celá odpoveď: IdempotencyConflict
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
422Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupport…Celá odpoveď: ProfileProblem
424Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB,…Celá odpoveď: ProfileUnreachable
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable, always with Retry-After: "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the sa…Celá odpoveď: ServiceUnavailableSession
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/v1/checkout-sessions/3d7b9e2a-6f14-4c58-9b3e-8a2f1c6d4e97/cancel' \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H 'Idempotency-Key: 0c2f8a4e-6b1d-4f37-9a85-3e7d1c9b5f02' \
  -H 'Accept-Language: sk-SK,sk;q=0.9,en;q=0.8'

Buyer handoff

continue_url (https://{shopDomain}/ucp/continue/{token}) is opened by the buyer’s browser, never by an agent. The page posts to the same URL from the buyer’s browser, which adds the session’s lines and discount code to the shop cart that browser shows and continues to /checkout or /cart (/ on a shop in catalog mode). These two routes are documented for completeness and for platforms that render the link; agents must never call them.

The buyer’s handoff page (browser only)

GET/ucp/continue/{token}

Opened by the buyer’s browser — agents and platform back-ends must never call this route. Documented so that platforms rendering continue_url know what the buyer will see.

An HTML page on the shop’s own domain. Its script reads the browser’s storefront guest-cart id (localStorage["sf-cart"], created if missing) and the tab’s storefront sign-in (sessionStorage["sf-auth"]), posts them to the same URL, and continues with location.replace to the path the claim returns (/checkout, /cart or /); without JavaScript, a link to the fallback path. It retries a 5xx / 429 twice (after 1 s and 2 s), then goes to the fallback.

  • Opening the page changes nothing (link previews and prefetchers are harmless); only the page’s own POST adds the lines.
  • It needs JavaScript and the shop’s browser storage. The lines land in the cart of the browser (and storage partition) that opened the link: an in-app browser or WebView keeps a cart of its own. Where storage is blocked they land in a cart no page of the shop shows, and the session is frozen all the same.
  • Works while the session is open (incomplete / requires_escalation checkout, or a cart) and unexpired; afterwards 410. A host without UCP answers 404. Both are HTML.
  • Rate-limit, outage and unexpected errors are answered as UCP JSON, not HTML.
  • Headers from the API: Cache-Control: no-store, Referrer-Policy: no-referrer, X-Robots-Tag: noindex, nofollow and a strict Content-Security-Policy. The shop’s web server adds its own security headers, among them a second Referrer-Policy: strict-origin-when-cross-origin, which a browser applies over the first (the last valid value wins). The page’s <meta name="referrer" content="no-referrer"> overrides the header again, so no request the page makes carries a Referer.

What happens on the shop: Concepts and lifecycle.

Parametre

  • tokencestastringpovinné

    The session’s continue token: exactly 64 lower-case hexadecimal characters, the last path segment of continue_url. It is a secret of its own, never the session id, and never changes. Anything else is treated as an unknown token.

    pattern: ^[0-9a-f]{64}$príklad:9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b

Odpovede

200The handoff page.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Content-Security-Policystring

    The handoff page’s policy: only its own inline script (by hash), same-origin fetch, inline styles. The shop’s web server may add its own policy as a second header.

    príklad:default-src 'none'; script-src 'sha256-VYn/7oWaIZE1N3eYOOLF7hjVtQcwMb7njm95xAMr…
  • Referrer-Policystring

    What a browser receives on the handoff page, from two layers: the API sends no-referrer, and the shop’s web server adds its general strict-origin-when-cross-origin as a second Referrer-Policy field after it. A browser applies the last valid value, so the header alone gives strict-origin-when-cross-origin. The page’s <meta name="referrer" content="no-referrer">, applied after the header, is what keeps the token out of every Referer.

    príklad:no-referrer, strict-origin-when-cross-origin
  • X-Robots-Tagstring

    The handoff page is never indexed.

    vždy:noindex, nofollow
string

The handoff page.

text/htmlThe handoff page (200)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Horské bicykle s.r.o.</title>
<style>body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0;color:#1f2937;background:#f8fafc}main{text-align:center;padding:24px}a{color:inherit}</style>
</head>
<body>
<main>
<p><strong>Horské bicykle s.r.o.</strong></p>
<p>&#8230;</p>
<noscript><p><a href="/checkout">Horské bicykle s.r.o.</a></p></noscript>
</main>
<script type="application/json" id="ucp-handoff">{"claimUrl":"/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b","fallback":"/checkout"}</script>
<script>(function () {
  var data = JSON.parse(document.getElementById('ucp-handoff').textContent);
  var KEY = 'sf-cart';
  var AUTH_KEY = 'sf-auth';
  var MAX_ATTEMPTS = 3;
  function uuid() {
    if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      var r = (Math.random() * 16) | 0;
      return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
    });
  }
  var stored = null;
  try { stored = JSON.parse(localStorage.getItem(KEY) || 'null'); } catch (e) {}
  var state = stored && typeof stored === 'object' && stored.state && typeof stored.state === 'object' ? stored.state : {};
  var sessionId = typeof state.sessionId === 'string' && state.sessionId ? state.sessionId : uuid();
  var next = { state: Object.assign({}, state, { sessionId: sessionId, hasCart: true }), version: stored && typeof stored.version === 'number' ? stored.version : 0 };
  try { localStorage.setItem(KEY, JSON.stringify(next)); } catch (e) {}
  var token = null;
  try {
    var auth = JSON.parse(sessionStorage.getItem(AUTH_KEY) || 'null');
    var t = auth && auth.state && auth.state.accessToken;
    if (typeof t === 'string' && /^[A-Za-z0-9_.-]{16,4096}$/.test(t)) token = t;
  } catch (e) {}
  function go(path) { window.location.replace(path || data.fallback); }
  function retry(attempt) {
    if (attempt >= MAX_ATTEMPTS) { go(null); return; }
    setTimeout(function () { claim(attempt + 1); }, 1000 * attempt);
  }
  function claim(attempt) {
    var headers = { 'Content-Type': 'application/json' };
    if (token) headers.Authorization = 'Bearer ' + token;
    fetch(data.claimUrl, {
      method: 'POST',
      headers: headers,
      credentials: 'omit',
      body: JSON.stringify({ sessionId: sessionId })
    }).then(function (r) {
      if (r.ok) {
        return r.json().then(function (j) {
          go(j && typeof j.redirect === 'string' && /^[/](?:[A-Za-z0-9_-]+(?:[/][A-Za-z0-9_-]+)*)?$/.test(j.redirect) ? j.redirect : null);
        }, function () { go(null); });
      }
      if (r.status >= 500 || r.status === 429) { retry(attempt); return; }
      go(null);
    }, function () { retry(attempt); });
  }
  claim(1);
})();</script>
</body>
</html>
404The host serves no UCP (switched off, domain no longer verified …): an empty “gone” page.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Content-Security-Policystring

    The handoff page’s policy: only its own inline script (by hash), same-origin fetch, inline styles. The shop’s web server may add its own policy as a second header.

    príklad:default-src 'none'; script-src 'sha256-VYn/7oWaIZE1N3eYOOLF7hjVtQcwMb7njm95xAMr…
  • Referrer-Policystring

    What a browser receives on the handoff page, from two layers: the API sends no-referrer, and the shop’s web server adds its general strict-origin-when-cross-origin as a second Referrer-Policy field after it. A browser applies the last valid value, so the header alone gives strict-origin-when-cross-origin. The page’s <meta name="referrer" content="no-referrer">, applied after the header, is what keeps the token out of every Referer.

    príklad:no-referrer, strict-origin-when-cross-origin
  • X-Robots-Tagstring

    The handoff page is never indexed.

    vždy:noindex, nofollow
string

The “gone” page with an empty shop name.

text/htmlThe page on a host without UCP (404)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0;color:#1f2937;background:#f8fafc}main{text-align:center;padding:24px}a{color:inherit}</style>
</head>
<body>
<main>
<p><strong></strong></p>
<p><a href="/"></a></p>
</main>
</body>
</html>
410Gone — unknown or malformed token, or the session is completed, canceled or expired: a page with a link to the shop’s home.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Content-Security-Policystring

    The handoff page’s policy: only its own inline script (by hash), same-origin fetch, inline styles. The shop’s web server may add its own policy as a second header.

    príklad:default-src 'none'; script-src 'sha256-VYn/7oWaIZE1N3eYOOLF7hjVtQcwMb7njm95xAMr…
  • Referrer-Policystring

    What a browser receives on the handoff page, from two layers: the API sends no-referrer, and the shop’s web server adds its general strict-origin-when-cross-origin as a second Referrer-Policy field after it. A browser applies the last valid value, so the header alone gives strict-origin-when-cross-origin. The page’s <meta name="referrer" content="no-referrer">, applied after the header, is what keeps the token out of every Referer.

    príklad:no-referrer, strict-origin-when-cross-origin
  • X-Robots-Tagstring

    The handoff page is never indexed.

    vždy:noindex, nofollow
string

The “gone” page.

text/htmlThe “gone” page (410)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Horské bicykle s.r.o.</title>
<style>body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0;color:#1f2937;background:#f8fafc}main{text-align:center;padding:24px}a{color:inherit}</style>
</head>
<body>
<main>
<p><strong>Horské bicykle s.r.o.</strong></p>
<p><a href="/">Horské bicykle s.r.o.</a></p>
</main>
</body>
</html>
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable "This shop is temporarily unavailable", Retry-After: 60: the shop’s company is deactivated or billing-locked, or the shop lookup failed.Celá odpoveď: ShopUnavailable
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl 'https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b'

Claim the handoff (the page’s own request, browser only)

POST/ucp/continue/{token}

Opened by the buyer’s browser — agents and platform back-ends must never call this route. Documented so that platforms rendering continue_url know what the buyer will see.

Adds the session’s lines (and its applied discount code) to the shop cart this browser shows — the signed-in customer’s cart when a valid storefront token of this shop is sent as Authorization: Bearer <token> (the optional storefrontCustomerToken security scheme), else the guest cart named by sessionId — through the shop’s normal add-to-cart rules, then names the path to continue to.

  • Lines are added to what the cart holds (same variant: quantities summed, capped at 999); a line the shop refuses (sold out since, over stock, unpriced) is skipped silently. The browser’s cart is never emptied: when it already holds items in another currency the lines go in that currency, or — if they cannot — nothing is added (redirect /cart).
  • Only lines and the code travel; buyer data, address and shipping choice do not. The buyer enters them again on the shop’s checkout.
  • Once per browser cart (or customer) and session state: a reload or a second click adds nothing. Another browser or another customer gets the lines once too. A cart link whose cart has an open checkout hands over that checkout instead.
  • After a successful handoff the session is frozen for the agent (writes answer handed_off), and an order placed from that browser cart that contains at least one of the session’s variants completes the checkout.
  • The claim queues behind requests on the same session; when it cannot get its turn the answer is the generic 503 with Retry-After: 60.

Parametre

  • tokencestastringpovinné

    The session’s continue token: exactly 64 lower-case hexadecimal characters, the last path segment of continue_url. It is a secret of its own, never the session id, and never changes. Anything else is treated as an unknown token.

    pattern: ^[0-9a-f]{64}$príklad:9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b

Telo požiadavkypovinné

Sent by the handoff page’s own script.

  • sessionIdstringpovinné

    The browser’s storefront guest-cart id (localStorage["sf-cart"].state.sessionId; the page creates one when there is none). 8–128 characters of A-Z a-z 0-9 _ -, not starting with ucp_. It is not the UCP session id.

    pattern: ^(?!ucp_)[A-Za-z0-9_-]{8,128}$
application/jsonThe page’s claim
{
  "sessionId": "5f0c2b1e-8d4a-4f6b-9c3e-2a1b0c9d8e7f"
}

Odpovede

200Where the browser goes next. Also answered, with nothing added, when this browser already claimed this session state, when every line was refused, or on a shop…

Where the browser goes next. Also answered, with nothing added, when this browser already claimed this session state, when every line was refused, or on a shop in catalog mode (/).

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

  • X-RateLimit-Limitinteger

    Requests this client IP may make to this operation per 60-second window (600 reads, 300 writes, 60 handoff). Set on every answer that passed the rate limiter.

  • X-RateLimit-Remaininginteger

    The operation’s limit minus this client IP’s current count. The counts live in the API server’s memory; each request is meant to leave the count 60 s after it was made, but the rate limiter (@nestjs/throttler 6.5) drops those pending decrements, for every client, whenever any client’s block ends. Read it as an upper bound and keep steady traffic at half a limit or less.

    minimum: 0
  • X-RateLimit-Resetinteger

    Seconds left in a 60-second window the limiter keeps per client IP and operation. The count is not reset when that window ends, so this is not the moment the budget comes back.

    minimum: 0

SchémaHandoffClaimResponse

application/jsonContinue to the shop’s checkout
{
  "redirect": "/checkout"
}
400invalid_request "The request is not valid": sessionId missing or not acceptable (no JSON body counts as missing), or a body sent as JSON that does not parse.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonBad guest-cart id
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
application/jsonBody is not valid JSON
{
  "code": "invalid_request",
  "content": "The request is not valid"
}
403Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessTo…Celá odpoveď: CrossOriginRejected
404not_found: "No UCP business on this host", or "Not found" when the token is unknown or malformed or the session can no longer be handed over (completed, cancel…

not_found: "No UCP business on this host", or "Not found" when the token is unknown or malformed or the session can no longer be handed over (completed, canceled, expired).

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonThis host serves no UCP
{
  "code": "not_found",
  "content": "No UCP business on this host"
}
application/jsonThe session can no longer be handed over
{
  "code": "not_found",
  "content": "Not found"
}
413Payload Too Large, from one of two layers: The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, th…Celá odpoveď: PayloadTooLarge
415invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encod…Celá odpoveď: UnsupportedMediaType
429Too Many Requests, from one of two layers: The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit…Celá odpoveď: RateLimited
500internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.Celá odpoveď: InternalError
502Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.Celá odpoveď: BadGateway
503service_unavailable "This shop is temporarily unavailable", Retry-After: 60: the session is busy with another request (nothing was added; the page retries), or…

service_unavailable "This shop is temporarily unavailable", Retry-After: 60: the session is busy with another request (nothing was added; the page retries), or the company is deactivated / billing-locked, or the shop lookup failed.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Retry-Afterinteger

    Seconds to wait before retrying: 60 after 429 rate_limited and the generic 503; 1 when another request holds the session; 2 when the same Idempotency-Key is still running; 10 when profile discovery is busy; the rest of the current minute (at least 1) when your client used its 5 new profiles this minute.

    minimum: 1

SchémaError

application/jsonShop temporarily unavailable
{
  "code": "service_unavailable",
  "content": "This shop is temporarily unavailable"
}
504Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A…Celá odpoveď: GatewayTimeout

Požiadavka cez curl

curl
curl -X POST 'https://shop.example.com/ucp/continue/9bbd3dedf887f93c6287fa7f4b8de141501155714c3b2050bb83551e36ec780b' \
  -H 'Content-Type: application/json' \
  --data '{
    "sessionId": "5f0c2b1e-8d4a-4f6b-9c3e-2a1b0c9d8e7f"
  }'

Spoločné odpovede

Odpovede, ktoré vracia viac operácií (chyby protokolu, limity, nedostupnosť). Operácia ich uvádza jedným riadkom so stavom; celé sú tu — hlavičky, schéma aj príklady.

NotModified

Not Modified — only when the request sent If-None-Match equal to the current answer’s ETag (strong as the API generates it on every response, or the weak W/"…" form the web server sends with a compressed answer). No body. Clients that do not send If-None-Match never see it.

Hlavičky odpovede

  • ETagstring

    An ETag of the body, generated by the API on every response with a body (writes included). The API’s ETag is strong; the web server sends it weak (W/"…") when it compresses the response, which it does for a client that sends Accept-Encoding: gzip. A GET that sends a matching If-None-Match (either form) can be answered 304 Not Modified even though the answer is no-store.

Bez tela

NoBusinessOnHost

not_found. Either the host serves no UCP — an unknown host, a *.shop.monokaido.com subdomain, a domain without verified DNS or an active certificate, UCP switched off, the built-in storefront off, an inactive or expired storefront key, or the e-shop module off; one answer for all, on purpose — or the path or method does not exist under /ucp/ (an unknown method is 404, not 405). A domain that has no certificate of its own yet never gets this answer: its TLS handshake fails first. A missing cart, checkout or product is NOT this: it is a 200 business error.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonThis host serves no UCP
{
  "code": "not_found",
  "content": "No UCP business on this host"
}
application/jsonUnknown path or method
{
  "code": "not_found",
  "content": "Not found"
}

IdempotencyConflict

idempotency_conflict. The Idempotency-Key was used for a different request (409, no Retry-After), or the same request is still running (409, Retry-After: 2). Writes only.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Retry-Afterinteger

    Seconds to wait before retrying: 60 after 429 rate_limited and the generic 503; 1 when another request holds the session; 2 when the same Idempotency-Key is still running; 10 when profile discovery is busy; the rest of the current minute (at least 1) when your client used its 5 new profiles this minute.

    minimum: 1

SchémaError

application/jsonKey reused for another request
{
  "code": "idempotency_conflict",
  "content": "This Idempotency-Key was used for a different request"
}
application/jsonThe same request is still running
{
  "code": "idempotency_conflict",
  "content": "A request with this Idempotency-Key is still being processed"
}

PayloadTooLarge

Payload Too Large, from one of two layers:

  • The web server (text/html): a request body over 1 MiB, whatever its content type. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. No Cache-Control: no-store.
  • The API (application/json, request_too_large "Request body too large"): a body over the API’s own limits once decompressed — 2 MiB for JSON, 100 KiB or more than 1 000 fields for application/x-www-form-urlencoded. A JSON body reaches this through the shop’s domain only when it is sent compressed (Content-Encoding), since the web server limits the body as sent to 1 MiB.

Bodies of this API are far below either limit: 100 line items take a few kilobytes.

Hlavičky odpovede

  • Cache-Controlstring

    On the API’s JSON answer only.

    vždy:no-store

text/html

string

nginx error page.

text/htmlBody over 1 MiB (HTML)
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx</center>
</body>
</html>

application/json

SchémaError

application/jsonForm-encoded body too large
{
  "code": "request_too_large",
  "content": "Request body too large"
}

UnsupportedMediaType

invalid_request "The request is not valid" with HTTP 415: the body declares a charset its parser does not read (JSON: charset= must start with utf-; form-encoded: utf-8 or iso-8859-1), or a Content-Encoding other than gzip, deflate, br or identity. Send Content-Type: application/json (UTF-8). A body of another content type is not read at all.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonCharset or encoding not supported
{
  "code": "invalid_request",
  "content": "The request is not valid"
}

ProfileProblem

Unprocessable Content — your platform profile: profile_malformed (not JSON, no ucp object, no YYYY-MM-DD ucp.version; remembered for 60 s) or version_unsupported (ucp.version is not 2026-04-08). Both carry continue_url (the shop home). Fix the profile. A profile_malformed failure is remembered for 60 s for that exact URL; a profile of another version is a valid profile and stays cached for its max-age (60 s – 1 h, 5 min without one), so version_unsupported persists until that entry is refreshed — or change the profile URL.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonYour profile is not a UCP profile
{
  "code": "profile_malformed",
  "content": "The platform profile is not a valid UCP profile",
  "continue_url": "https://shop.example.com/"
}
application/jsonYour profile declares another protocol version
{
  "code": "version_unsupported",
  "content": "Protocol version 2026-01-11 is not supported. This business supports version 2026-04-08.",
  "continue_url": "https://shop.example.com/"
}

ProfileUnreachable

Failed Dependency — profile_unreachable: your platform profile could not be fetched (network error, timeout, a redirect, a status other than 200, over 256 KiB, a non-public address). Remembered for 60 s for that exact URL.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonYour profile could not be fetched
{
  "code": "profile_unreachable",
  "content": "Unable to fetch the platform profile",
  "continue_url": "https://shop.example.com/"
}

RateLimited

Too Many Requests, from one of two layers:

  • The API (application/json, rate_limited, Retry-After: 60): this client IP’s count for the operation passed its limit (see X-RateLimit-Remaining) — 600 for reads, 300 for writes, 60 for the handoff routes, each operation counted separately, across every Monokaido shop.
  • The web server (text/html): above 100 requests/s per client IP, burst 200: one budget for everything that IP sends to any shop host Monokaido serves (UCP calls, pages and images alike) and to the storefront API, not per shop. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. No Retry-After.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Retry-Afterinteger

    Seconds to wait before retrying: 60 after 429 rate_limited and the generic 503; 1 when another request holds the session; 2 when the same Idempotency-Key is still running; 10 when profile discovery is busy; the rest of the current minute (at least 1) when your client used its 5 new profiles this minute.

    minimum: 1

application/json

SchémaError

application/jsonRate limit of the operation
{
  "code": "rate_limited",
  "content": "Too many requests"
}

text/html

string

nginx error page.

text/htmlWeb server rate limit (HTML)
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>nginx</center>
</body>
</html>

InternalError

internal_error: an unexpected failure (logged on the server; no detail is sent). A failed write released its Idempotency-Key: retry with the same key.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonUnexpected failure
{
  "code": "internal_error",
  "content": "Unexpected server error"
}

BadGateway

Bad Gateway — the API is down or restarting. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page.

string

nginx error page.

text/htmlAPI unreachable (HTML)
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx</center>
</body>
</html>

GatewayTimeout

Gateway Timeout — the API did not answer within 60 s. Not produced by the API: nginx, the web server in front of the shop, answers it with its own HTML page. A write may have been applied: retry it with the same Idempotency-Key.

string

nginx error page.

text/htmlAPI did not answer within 60 s (HTML)
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx</center>
</body>
</html>

ShopUnavailable

service_unavailable "This shop is temporarily unavailable", Retry-After: 60: the shop’s company is deactivated or billing-locked, or the shop lookup failed.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Retry-Afterinteger

    Seconds to wait before retrying: 60 after 429 rate_limited and the generic 503; 1 when another request holds the session; 2 when the same Idempotency-Key is still running; 10 when profile discovery is busy; the rest of the current minute (at least 1) when your client used its 5 new profiles this minute.

    minimum: 1

SchémaError

application/jsonShop temporarily unavailable
{
  "code": "service_unavailable",
  "content": "This shop is temporarily unavailable"
}

ServiceUnavailable

service_unavailable, always with Retry-After:

  • "This shop is temporarily unavailable" (Retry-After: 60) — the company is deactivated or billing-locked, or the shop lookup failed;
  • "Too many new platform profiles from this client, retry shortly" (Retry-After: the rest of the one-minute window) — your IP started 5 fetches of unknown profile URLs this minute;
  • "Platform profile discovery is busy, retry shortly" (Retry-After: 10).

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Retry-Afterinteger

    Seconds to wait before retrying: 60 after 429 rate_limited and the generic 503; 1 when another request holds the session; 2 when the same Idempotency-Key is still running; 10 when profile discovery is busy; the rest of the current minute (at least 1) when your client used its 5 new profiles this minute.

    minimum: 1

SchémaError

application/jsonShop temporarily unavailable
{
  "code": "service_unavailable",
  "content": "This shop is temporarily unavailable"
}
application/jsonToo many new profiles from your IP
{
  "code": "service_unavailable",
  "content": "Too many new platform profiles from this client, retry shortly",
  "continue_url": "https://shop.example.com/"
}
application/jsonProfile discovery busy
{
  "code": "service_unavailable",
  "content": "Platform profile discovery is busy, retry shortly",
  "continue_url": "https://shop.example.com/"
}

ServiceUnavailableSession

service_unavailable, always with Retry-After:

  • "Another request on this session is being processed — retry shortly" (Retry-After: 1) — another request on the same cart or checkout was running and this one could not get its turn within 10 s, or 4 were already waiting behind it (this request did not run); or, rarely, another writer changed the session while this request ran (its result was not stored). Either way the Idempotency-Key was released: retry the same request with the same key;
  • "This shop is temporarily unavailable" (Retry-After: 60);
  • the two profile-discovery answers (see UCP-Agent).

Keep at most one request in flight per session.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store
  • Retry-Afterinteger

    Seconds to wait before retrying: 60 after 429 rate_limited and the generic 503; 1 when another request holds the session; 2 when the same Idempotency-Key is still running; 10 when profile discovery is busy; the rest of the current minute (at least 1) when your client used its 5 new profiles this minute.

    minimum: 1

SchémaError

application/jsonAnother request holds the session
{
  "code": "service_unavailable",
  "content": "Another request on this session is being processed — retry shortly"
}
application/jsonShop temporarily unavailable
{
  "code": "service_unavailable",
  "content": "This shop is temporarily unavailable"
}
application/jsonToo many new profiles from your IP
{
  "code": "service_unavailable",
  "content": "Too many new platform profiles from this client, retry shortly",
  "continue_url": "https://shop.example.com/"
}
application/jsonProfile discovery busy
{
  "code": "service_unavailable",
  "content": "Platform profile discovery is busy, retry shortly",
  "continue_url": "https://shop.example.com/"
}

CrossOriginRejected

Forbidden — invalid_request "The request is not valid" from the platform’s cross-origin write gate: the POST / PUT carried a Monokaido session cookie (accessToken, refreshToken, platformAccessToken, platformRefreshToken) and was not sent by a page the platform trusts: a Sec-Fetch-Site other than same-origin / none without a trusted Origin, or — without Sec-Fetch-Site — an Origin that is neither trusted nor on the requested host (Origin: null included). A server-side client that sends no such cookie never gets it.

Hlavičky odpovede

  • Cache-Controlstring

    Every UCP answer (success or error) except the business profile.

    vždy:no-store

SchémaError

application/jsonRefused by the cross-origin write gate
{
  "code": "invalid_request",
  "content": "The request is not valid"
}

Schémy

Každá pomenovaná schéma dokumentu je tu raz. Typy, na ktoré operácie vyššie odkazujú, vedú sem.

UcpVersion

string

The UCP protocol version. This shop implements exactly one.

vždy:2026-04-08

CapabilityName

string

A capability this business can declare. catalog.search, catalog.lookup, cart and checkout are roots; fulfillment extends checkout; discount extends checkout and cart.

jedna z hodnôt:dev.ucp.shopping.catalog.searchdev.ucp.shopping.catalog.lookupdev.ucp.shopping.cartdev.ucp.shopping.checkoutdev.ucp.shopping.fulfillmentdev.ucp.shopping.discount

CapabilityVersion

object

A negotiated capability: only its version.

additionalProperties: false
  • versionstring · UcpVersionpovinné

    The UCP protocol version. This shop implements exactly one.

    vždy:2026-04-08

ResponseCapabilities

object

The capabilities active for this answer: the operation’s root capability and every negotiated extension of it.

  • Catalog search: dev.ucp.shopping.catalog.search.
  • Catalog lookup and product: dev.ucp.shopping.catalog.lookup.
  • Carts: dev.ucp.shopping.cart, plus dev.ucp.shopping.discount when negotiated.
  • Checkouts: dev.ucp.shopping.checkout, plus dev.ucp.shopping.fulfillment and dev.ucp.shopping.discount when negotiated.

An extension your profile did not declare is absent. Empty ({}) only in the capabilities_incompatible answer.

additionalProperties: false

ResponseMetadata

object

The ucp object of a successful catalog or cart answer. A missing status means success.

additionalProperties: false
  • versionstring · UcpVersionpovinné

    The UCP protocol version. This shop implements exactly one.

    vždy:2026-04-08
  • capabilitiesResponseCapabilitiespovinné

    The capabilities active for this answer: the operation’s root capability and every negotiated extension of it.

    • Catalog search: dev.ucp.shopping.catalog.search.
    • Catalog lookup and product: dev.ucp.shopping.catalog.lookup.
    • Carts: dev.ucp.shopping.cart, plus dev.ucp.shopping.discount when negotiated.
    • Checkouts: dev.ucp.shopping.checkout, plus dev.ucp.shopping.fulfillment and dev.ucp.shopping.discount when negotiated.

    An extension your profile did not declare is absent. Empty ({}) only in the capabilities_incompatible answer.

CheckoutResponseMetadata

object

The ucp object of every checkout answer that returns a checkout (any status).

additionalProperties: false
  • versionstring · UcpVersionpovinné

    The UCP protocol version. This shop implements exactly one.

    vždy:2026-04-08
  • capabilitiesResponseCapabilitiespovinné

    The capabilities active for this answer: the operation’s root capability and every negotiated extension of it.

    • Catalog search: dev.ucp.shopping.catalog.search.
    • Catalog lookup and product: dev.ucp.shopping.catalog.lookup.
    • Carts: dev.ucp.shopping.cart, plus dev.ucp.shopping.discount when negotiated.
    • Checkouts: dev.ucp.shopping.checkout, plus dev.ucp.shopping.fulfillment and dev.ucp.shopping.discount when negotiated.

    An extension your profile did not declare is absent. Empty ({}) only in the capabilities_incompatible answer.

  • payment_handlersobjectpovinné

    Always {}: no payment is taken through UCP. The buyer pays on the shop’s own checkout, reached through continue_url, with the shop’s own payment methods.

    maxProperties: 0

ErrorResponseMetadata

object

The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

additionalProperties: false
  • versionstring · UcpVersionpovinné

    The UCP protocol version. This shop implements exactly one.

    vždy:2026-04-08
  • statusstringpovinné

    Present only on a business error: the body carries no resource.

    vždy:error
  • capabilitiesResponseCapabilitiespovinné

    The capabilities active for this answer: the operation’s root capability and every negotiated extension of it.

    • Catalog search: dev.ucp.shopping.catalog.search.
    • Catalog lookup and product: dev.ucp.shopping.catalog.lookup.
    • Carts: dev.ucp.shopping.cart, plus dev.ucp.shopping.discount when negotiated.
    • Checkouts: dev.ucp.shopping.checkout, plus dev.ucp.shopping.fulfillment and dev.ucp.shopping.discount when negotiated.

    An extension your profile did not declare is absent. Empty ({}) only in the capabilities_incompatible answer.

MessageError

object

An error message. Always carries a severity.

additionalProperties: false
  • typestringpovinné
    vždy:error
  • codestringpovinné

    Machine-readable reason. Codes this shop emits:

    • recoverable — missing (path $.line_items, $.buyer.email, $.fulfillment.methods[0].destinations or $.fulfillment.methods[0].groups[0].selected_option_id), invalid_address, address_undeliverable, invalid_option, item_unavailable, out_of_stock, vat_mix_not_allowed. Any of them keeps a checkout incomplete.
    • requires_buyer_input — payment_required (always on a requires_escalation checkout), pickup_point_required (the selected shipping option needs a pickup point, which the buyer picks on the shop; it does not block requires_escalation).
    • unrecoverable — not_found, capabilities_incompatible, handed_off, checkout_completed, checkout_canceled, and out_of_stock / item_unavailable when a create could add none of the requested items.

    Treat an unknown code by its type and severity.

  • contentstringpovinné

    A sentence in English for logs and developers. Not localised; do not parse it.

  • pathstring

    JSONPath into the RETURNED resource (not the request), e.g. $.line_items[1].quantity. Line items are returned in the shop cart’s order, which need not be your order: match lines by id or item.id, never by position. Absent when the message is about the whole request or about a line that is not in the answer.

    pattern: ^\$
  • severitystringpovinné

    recoverable: the platform can fix it with another request. requires_buyer_input: only the buyer can, on the shop (through continue_url). unrecoverable: retrying the same request cannot help. requires_buyer_review is part of the UCP vocabulary but never emitted by this shop.

    jedna z hodnôt:recoverablerequires_buyer_inputrequires_buyer_reviewunrecoverable

MessageWarning

object

A warning: the request was processed, something was adjusted or refused. Never has a severity.

additionalProperties: false
  • typestringpovinné
    vždy:warning
  • codestringpovinné

    Codes this shop emits: quantity_adjusted (a quantity was lowered: to the sellable stock, or to 999 when several lines of one item add up to more — a single line above 999 is lowered to 999 without a message), discount_code_combination_disallowed, discount_code_expired, discount_code_user_ineligible, discount_code_invalid (path $.discounts.codes[i]), handed_off (the buyer already continued on the shop; nothing was changed), price_filter_ignored (path $.filters.price — the request field: no returned resource carries the filter).

  • contentstringpovinné

    A sentence in English for logs and developers. Not localised; do not parse it.

  • pathstring

    JSONPath into the RETURNED resource (not the request), e.g. $.line_items[1].quantity. Line items are returned in the shop cart’s order, which need not be your order: match lines by id or item.id, never by position. Absent when the message is about the whole request or about a line that is not in the answer.

    pattern: ^\$

MessageInfo

object

An informational message. Never has a severity or a path.

additionalProperties: false
  • typestringpovinné
    vždy:info
  • codestringpovinné

    This shop emits not_found (catalog lookup: an identifier that matched nothing).

  • contentstringpovinné

    For not_found: the identifier as you sent it (after trimming).

Message

oneOf

A business message. Discriminated by type.

Práve jedna z možností

  1. MessageErrorobject · MessageError

    An error message. Always carries a severity.

    additionalProperties: false
    • typestringpovinné
      vždy:error
    • codestringpovinné

      Machine-readable reason. Codes this shop emits:

      • recoverable — missing (path $.line_items, $.buyer.email, $.fulfillment.methods[0].destinations or $.fulfillment.methods[0].groups[0].selected_option_id), invalid_address, address_undeliverable, invalid_option, item_unavailable, out_of_stock, vat_mix_not_allowed. Any of them keeps a checkout incomplete.
      • requires_buyer_input — payment_required (always on a requires_escalation checkout), pickup_point_required (the selected shipping option needs a pickup point, which the buyer picks on the shop; it does not block requires_escalation).
      • unrecoverable — not_found, capabilities_incompatible, handed_off, checkout_completed, checkout_canceled, and out_of_stock / item_unavailable when a create could add none of the requested items.

      Treat an unknown code by its type and severity.

    • contentstringpovinné

      A sentence in English for logs and developers. Not localised; do not parse it.

    • pathstring

      JSONPath into the RETURNED resource (not the request), e.g. $.line_items[1].quantity. Line items are returned in the shop cart’s order, which need not be your order: match lines by id or item.id, never by position. Absent when the message is about the whole request or about a line that is not in the answer.

      pattern: ^\$
    • severitystringpovinné

      recoverable: the platform can fix it with another request. requires_buyer_input: only the buyer can, on the shop (through continue_url). unrecoverable: retrying the same request cannot help. requires_buyer_review is part of the UCP vocabulary but never emitted by this shop.

      jedna z hodnôt:recoverablerequires_buyer_inputrequires_buyer_reviewunrecoverable
  2. MessageWarningobject · MessageWarning

    A warning: the request was processed, something was adjusted or refused. Never has a severity.

    additionalProperties: false
    • typestringpovinné
      vždy:warning
    • codestringpovinné

      Codes this shop emits: quantity_adjusted (a quantity was lowered: to the sellable stock, or to 999 when several lines of one item add up to more — a single line above 999 is lowered to 999 without a message), discount_code_combination_disallowed, discount_code_expired, discount_code_user_ineligible, discount_code_invalid (path $.discounts.codes[i]), handed_off (the buyer already continued on the shop; nothing was changed), price_filter_ignored (path $.filters.price — the request field: no returned resource carries the filter).

    • contentstringpovinné

      A sentence in English for logs and developers. Not localised; do not parse it.

    • pathstring

      JSONPath into the RETURNED resource (not the request), e.g. $.line_items[1].quantity. Line items are returned in the shop cart’s order, which need not be your order: match lines by id or item.id, never by position. Absent when the message is about the whole request or about a line that is not in the answer.

      pattern: ^\$
  3. MessageInfoobject · MessageInfo

    An informational message. Never has a severity or a path.

    additionalProperties: false
    • typestringpovinné
      vždy:info
    • codestringpovinné

      This shop emits not_found (catalog lookup: an identifier that matched nothing).

    • contentstringpovinné

      For not_found: the identifier as you sent it (after trimming).

Error

object

A protocol error: the request could not be processed. It carries no ucp object and is always Cache-Control: no-store.

additionalProperties: false
  • codestringpovinné

    The protocol error code (status in brackets): invalid_profile_url (400), invalid_request (400; 403 from the cross-origin write gate; 415), request_too_large (400 for more than 20 lookup ids; 413 for a body over the API’s limits once decompressed: JSON 2 MiB, form-encoded 100 KiB or more than 1 000 fields — a body over 1 MiB as sent is refused by the shop’s web server first, with an HTML 413), not_found (404), idempotency_conflict (409), profile_malformed and version_unsupported (422), profile_unreachable (424), rate_limited (429), internal_error (500), service_unavailable (503).

    jedna z hodnôt:invalid_profile_urlprofile_unreachableprofile_malformedversion_unsupportedidempotency_conflictrequest_too_largeinvalid_requestnot_foundrate_limitedservice_unavailableinternal_error
  • contentstringpovinné

    A sentence in English. For invalid_request raised by a body check it names the field, e.g. line_items[0].quantity must be a positive integer; errors raised before the body is read (malformed JSON, an unsupported charset) say only The request is not valid.

  • continue_urlstring (uri)

    The shop’s home page (https://{primary domain}/). Present only on the discovery errors: invalid_profile_url, profile_unreachable, profile_malformed, version_unsupported and the discovery-budget service_unavailable.

CurrencyCode

string

ISO 4217 currency code.

pattern: ^[A-Z]{3}$

Money

object

An amount with its currency.

additionalProperties: false
  • amountintegerpovinné

    ISO 4217 minor units (EUR: cents; JPY: whole yen; HUF: fillér, so 1 990 Ft = 199000). VAT-inclusive whenever the shop knows the gross price, with the shop’s markup rules, conversion and rounding applied — the price its own product page shows.

  • currencystring · CurrencyCodepovinné

    ISO 4217 currency code.

    pattern: ^[A-Z]{3}$

PriceRange

object

The lowest and highest of a set of prices.

additionalProperties: false
  • minMoneypovinné

    An amount with its currency.

  • maxMoneypovinné

    An amount with its currency.

ProductDescription

object

The product’s description.

additionalProperties: false
  • plainstringpovinné

    Plain text: the short description, else the description, with tags removed and common entities decoded; the product title when both are empty. At most 5 000 characters.

    maxLength: 5000
  • htmlstring

    The stored (translated) description HTML, trimmed, at most 50 000 characters (cut, possibly mid-tag). Not sanitised by UCP — sanitise before rendering. Absent when the product has no description.

    maxLength: 50000

VariantDescription

object

The variant’s description.

additionalProperties: false
  • plainstringpovinné

    The same text as the variant’s title.

Media

object

A product image, in the shop’s gallery order (main image first).

additionalProperties: false
  • typestringpovinné

    Always image.

    vždy:image
  • urlstringpovinné

    Absolute URL. The shop’s own copies are served from https://{primary domain}/api/public/…; vendor images keep the vendor’s URL as stored (some vendor hosts refuse unknown clients).

    pattern: ^[Hh][Tt][Tt][Pp][Ss]?://
  • alt_textstring

    The image’s (translated) alternative text, when set.

Barcode

object

The variant’s GTIN.

additionalProperties: false
  • typestringpovinné

    UPC when the stored GTIN has exactly 12 characters, EAN otherwise (GTIN-8, -13 and -14 all read as EAN).

    jedna z hodnôt:EANUPC
  • valuestringpovinné

    The GTIN as stored, trimmed.

Availability

object

Stock state, as the shop’s own stock badge reads it.

additionalProperties: false
  • availablebooleanpovinné

    Whether the variant can be bought now: true for in_stock and preorder, false for out_of_stock and discontinued.

  • statusstringpovinné

    discontinued: the product is not orderable. preorder: a warehouse marks it pre-order (no stock limit). in_stock: on-hand minus reserved stock is above 0. out_of_stock: otherwise. Stock counts are not published.

    jedna z hodnôt:in_stockpreorderout_of_stockdiscontinued

SelectedOption

object

One option value of a variant.

additionalProperties: false
  • namestringpovinné

    Option name, e.g. Veľkosť.

  • labelstringpovinné

    Option value, e.g. M.

VariantInput

object

Which requested identifier this variant answers.

additionalProperties: false
  • idstringpovinné

    The identifier from your ids, as you sent it (after trimming; upper-case UUIDs stay upper-case).

  • matchstringpovinné

    exact: the identifier is this variant’s id or SKU. featured: it named the product (id or handle) and this is its featured variant.

    jedna z hodnôt:exactfeatured

Variant

object

A purchasable variant. Only active variants with a price in the shop’s region are listed.

additionalProperties: false
  • idstring (uuid)povinné

    The variant id — the value to put in line_items[].item.id.

  • skustring

    The variant’s SKU, as the shop stores it. Not guaranteed unique — variants of different products may share one. Absent when empty.

  • barcodesarray<Barcode>

    Present only when a GTIN is stored.

    minItems: 1maxItems: 1
  • titlestringpovinné

    The option labels joined by / (e.g. Modrá / M); the product title for a variant without options.

  • descriptionVariantDescriptionpovinné

    The variant’s description.

  • urlstring (uri)povinné

    The product’s page on the shop (no variant parameter).

  • priceMoneypovinné

    An amount with its currency.

  • list_priceMoney

    The compare-at price, only when it is higher than price. Shown as the shop shows it; the shop may store it without VAT, so treat it as indicative.

  • availabilityAvailabilitypovinné

    Stock state, as the shop’s own stock badge reads it.

  • optionsarray<SelectedOption>

    The variant’s option values (after the shop’s value mapping). Absent when it has none.

    minItems: 1
  • inputsarray<VariantInput>

    Catalog lookup only: the identifiers this variant answers.

    minItems: 1

ProductOption

object

An option of the product and the values its listed variants have.

additionalProperties: false
  • namestringpovinné

    Option name.

  • valuesarray<object>povinné

    Values in first-seen order over the listed variants.

    minItems: 1
    • labelstringpovinné

      Option value.

ProductDetailOption

object

An option with availability signals relative to selected.

additionalProperties: false
  • namestringpovinné

    Option name.

  • valuesarray<object>povinné

    Values in first-seen order.

    minItems: 1
    • labelstringpovinné

      Option value.

    • existsbooleanpovinné

      Some variant has this value together with every other option value of the selected variant.

    • availablebooleanpovinné

      Such a variant is availability.available.

Product

object

A product as search and lookup return it. Fields not published: categories, ratings, stock counts, delivery times, sales-unit data.

In lookup answers variants holds only the variants your identifiers matched (each with inputs), while price_range, list_price_range and options still describe all of the product’s priced variants (within the price filter, when one is set).

additionalProperties: false
  • idstring (uuid)povinné

    The product id. Stable for the product’s life.

  • handlestring

    The product’s URL slug in the answer’s language. Absent when the product has none.

  • titlestringpovinné

    The product title in the answer’s language (prefix and suffix included).

  • descriptionProductDescriptionpovinné

    The product’s description.

  • urlstring (uri)povinné

    The product page on the shop’s primary domain: https://{primary domain}/products/{handle, else id}.

  • price_rangePriceRangepovinné

    Over the listed variants’ price.

  • list_price_rangePriceRange

    Over the variants’ list_price; absent when none has one.

  • mediaarray<Media>

    Up to 20 images; absent when there is none.

    minItems: 1maxItems: 20
  • optionsarray<ProductOption>

    The product’s options; absent when variants have no options.

    minItems: 1
  • variantsarray<Variant>povinné

    Priced variants (within the price filter, if any). The first available variant comes first; the others follow in the order the shop’s catalog returns them, which is not guaranteed to be stable.

    minItems: 1
  • tagsarray<string>

    Only the brand name, when the product has a brand.

    minItems: 1maxItems: 1

ProductDetail

object

A product as getProduct returns it: every priced variant, the selected one first, and per-value availability.

additionalProperties: false
  • idstring (uuid)povinné

    The product id. Stable for the product’s life.

  • handlestring

    The product’s URL slug in the answer’s language. Absent when the product has none.

  • titlestringpovinné

    The product title in the answer’s language (prefix and suffix included).

  • descriptionProductDescriptionpovinné

    The product’s description.

  • urlstring (uri)povinné

    The product page on the shop’s primary domain: https://{primary domain}/products/{handle, else id}.

  • price_rangePriceRangepovinné

    Over the listed variants’ price.

  • list_price_rangePriceRange

    Over the variants’ list_price; absent when none has one.

  • mediaarray<Media>

    Up to 20 images; absent when there is none.

    minItems: 1maxItems: 20
  • optionsarray<ProductDetailOption>

    The product’s options with availability relative to selected; absent when variants have no options.

    minItems: 1
  • variantsarray<Variant>povinné

    All priced variants. The selected (anchor) variant comes first.

    minItems: 1
  • tagsarray<string>

    Only the brand name, when the product has a brand.

    minItems: 1maxItems: 1
  • selectedarray<SelectedOption>

    The option values of the selected variant (variants[0]) — not necessarily what you sent. Absent when it has no options.

    minItems: 1

RequestContext

object | null

Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • address_countrystring | null

    The buyer’s country: ISO 3166-1 alpha-2 (SK; UK reads as GB), alpha-3 (SVK) or an English, Slovak, Czech, Hungarian, German … country name. Not recognised → silently dropped. Stored and echoed only: it does not change prices or shipping (shipping uses the destination’s country).

    maxLength: 60
  • address_regionstring | null

    Region. Stored and echoed only.

    maxLength: 100
  • postal_codestring | null

    Postal code. Stored and echoed only.

    maxLength: 20
  • languagestring | null

    Preferred language, a BCP 47-like tag matching ^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$ and at most 35 characters (otherwise silently dropped). Tried before Accept-Language; see that header for the matching rules. It decides the currency.

    maxLength: 100
  • currencystring | null

    ISO 4217 code (^[A-Za-z]{3}$, upper-cased; otherwise silently dropped). Does not change the currency of prices, which follows the language. In catalog requests it only says which currency filters.price is in (without it, the bounds are in the answer’s currency).

    maxLength: 10

PriceFilter

object

Keeps a variant when min ≤ price.amount ≤ max (the final VAT-inclusive amount); a product stays while any variant does. The bounds are in context.currency, else (no context.currency, or one the API cannot read) in the currency of the answer, which the language decides. In the answer’s currency → used as sent; another currency → converted at the shop’s exchange rate (min rounded down, max up), or — with no rate — ignored with a price_filter_ignored warning.

Decimals and negative numbers → 400 invalid_request. min > max matches nothing.

  • mininteger | null

    Lowest price, inclusive, in minor units of context.currency (else of the answer’s currency).

    minimum: 0
  • maxinteger | null

    Highest price, inclusive, in minor units of context.currency (else of the answer’s currency).

    minimum: 0

SearchFilters

object

Filters. Other filters (brand, availability …) are ignored without a message.

  • Keeps a variant when min ≤ price.amount ≤ max (the final VAT-inclusive amount); a product stays while any variant does. The bounds are in context.currency, else (no context.currency, or one the API cannot read) in the currency of the answer, which the language decides. In the answer’s currency → used as sent; another currency → converted at the shop’s exchange rate (min rounded down, max up), or — with no rate — ignored with a price_filter_ignored warning.

    Decimals and negative numbers → 400 invalid_request. min > max matches nothing.

  • categoriesarray<string> | null

    Not supported. A list holding any non-blank value → 400 invalid_request "filters.categories is not supported by this shop; search by query (and filters.price) instead". An empty list reads as absent.

    každá položka:pattern: ^\s*$

CatalogSearchRequest

object

A catalog search. The whole body is optional: no body (or {}) lists every product. Unknown fields (signals, …) are ignored.

  • querystring | null

    Free text; longer text is cut to 200 characters. Matches, case-insensitively (but not ignoring diacritics) as a substring, the product title in the answer’s language or the product code / external ids — not descriptions, brands, variant SKUs or GTINs (use lookup for SKUs). Absent: every listed product.

    maxLength: 200
  • Filters. Other filters (brand, availability …) are ignored without a message.

  • paginationobject

    Paging.

    • limitinteger

      Page size, default 10. A value above 20 is lowered to 20; 0, decimals and strings → 400.

      predvolené:10minimum: 1
    • cursorstring | null

      pagination.cursor from the previous answer. Opaque, at most 40 characters. It carries only the page number: resend the same query, filters, limit and context with it.

      maxLength: 40
  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

CatalogLookupRequest

object

A batch lookup by identifier. Unknown fields are ignored.

  • idsarray<string>povinné

    Up to 20 identifiers, each a variant id (any case), a variant SKU (exact, case-sensitive; several products may share one), a product id or a product handle (in any language). Non-string and blank entries are dropped, exact duplicates removed. Empty or missing → 400 invalid_request; more than 20 → 400 request_too_large.

    minItems: 1maxItems: 20každá položka:maxLength: 255
  • Filters. Other filters (brand, availability …) are ignored without a message.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

GetProductRequest

object

A product detail request. preferences and other unknown fields are ignored.

  • idstringpovinné

    A product id, a product handle or a variant id (SKUs are not accepted here). Required; longer text is cut to 255 characters.

    minLength: 1maxLength: 255
  • selectedarray<object> | null

    Option values to select (names and labels compared case-insensitively). The variant matching all of them becomes the anchor; when none does, selections are dropped from the end until one matches. selected wins over a variant id in id. Entries without both fields are skipped; at most the first 20 are read. Relaxation is not reported.

    maxItems: 20
    • namestringpovinné

      Option name.

      maxLength: 100
    • labelstringpovinné

      Option value.

      maxLength: 255
  • Only filters.categories is checked (and refused when non-blank); filters.price is ignored here.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

CatalogPagination

object

Search paging.

additionalProperties: false
  • has_next_pagebooleanpovinné

    Whether another page may follow. Pages can be short or even empty while this is true (products without a priced variant, or outside the price filter, are dropped after the page is read): keep following cursor until it is false.

  • cursorstring

    Send it as pagination.cursor for the next page. Present only when has_next_page is true.

    maxLength: 40
  • total_countinteger

    Number of listed products matching query, counted before the per-variant price checks. Left out whenever a price filter is in effect.

    minimum: 0

CatalogSearchResponse

object

A page of search results.

additionalProperties: false
  • ucpResponseMetadatapovinné

    The ucp object of a successful catalog or cart answer. A missing status means success.

  • productsarray<Product>povinné

    Newest product first (there is no relevance ranking).

  • paginationCatalogPaginationpovinné

    Search paging.

  • messagesarray<Message>

    Present only when non-empty. Search emits only price_filter_ignored.

    minItems: 1

CatalogLookupResponse

object

Lookup results. No pagination.

additionalProperties: false
  • ucpResponseMetadatapovinné

    The ucp object of a successful catalog or cart answer. A missing status means success.

  • productsarray<Product>povinné

    One entry per product, in the order its first identifier appears in ids; each holds only the matched variants.

  • messagesarray<Message>

    Present only when non-empty: price_filter_ignored first, then one info not_found per identifier that matched nothing, in ids order.

    minItems: 1

GetProductResponse

object

One product in detail.

additionalProperties: false
  • ucpResponseMetadatapovinné

    The ucp object of a successful catalog or cart answer. A missing status means success.

  • productProductDetailpovinné

    A product as getProduct returns it: every priced variant, the selected one first, and per-value availability.

LineItemRequest

object

One requested line.

  • idstring | null

    A line id from an earlier answer. Accepted and ignored (whatever its type): lines are matched by item.id.

    maxLength: 100
  • itemobjectpovinné

    What to buy.

    • idstringpovinné

      A variant id from the catalog (longer text is cut to 100 characters). A product id is accepted too and means the product’s active variant created first — not necessarily the featured, available or priced variant the catalog shows (the answer then names that variant). Ids are checked against the company’s published products, not against this storefront’s catalog: its exclusion filters do not apply, so send only variant ids this shop’s catalog returned. Anything else — a SKU, an unknown id — is not a 400: the line is left out with an item_unavailable message.

      minLength: 1maxLength: 100
  • quantityinteger

    How many, default 1. 0, negatives, decimals and strings → 400. A value above 999 is lowered to 999 without a message; lines of the same variant are summed, and a sum above 999 is lowered with a quantity_adjusted warning. A quantity above the sellable stock is lowered with quantity_adjusted; a sold-out variant is left out with out_of_stock. Pre-order variants are never limited.

    predvolené:1minimum: 1

BuyerRequest

object | null

Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • first_namestring | null

    At most 100 characters.

    maxLength: 100
  • last_namestring | null

    At most 100 characters.

    maxLength: 100
  • emailstring | null

    At most 254 characters. Lower-cased when it looks like an address (^[^@\s"]{1,64}@[^@\s"]{1,255}$); any other string is stored as sent, and a checkout then reports missing at $.buyer.email.

    maxLength: 254
  • phone_numberstring | null

    At most 40 characters.

    maxLength: 40

PostalAddressRequest

object

A shipping address. Strings longer than stated are cut; a non-string → 400.

  • idstring | null

    Your id for this address, used only to pick it with selected_destination_id. Not echoed: the answer always says destination.

    maxLength: 100
  • street_addressstring | null

    Street and number. Required for a ready checkout.

    maxLength: 255
  • extended_addressstring | null

    Apartment, floor …

    maxLength: 255
  • address_localitystring | null

    City. Required for a ready checkout.

    maxLength: 100
  • address_regionstring | null

    Region.

    maxLength: 100
  • postal_codestring | null

    Postal code. Required for a ready checkout.

    maxLength: 20
  • address_countrystring | null

    Country: alpha-2, alpha-3 or a country name. Required for a ready checkout. On the chosen destination an unrecognised value → 400 invalid_request "…address_country is not a recognised country; send an ISO 3166-1 alpha-2 code such as "SK"".

    maxLength: 60
  • first_namestring | null

    Recipient first name.

    maxLength: 100
  • last_namestring | null

    Recipient last name.

    maxLength: 100
  • phone_numberstring | null

    Recipient phone.

    maxLength: 40

FulfillmentRequest

object | null

Shipping (only read when dev.ucp.shopping.fulfillment is negotiated; ignored on carts — a cart stores it but never uses it, and a conversion takes fulfillment from the conversion request). null or a body without fulfillment keeps what the session holds.

  • methodsarray<object> | null

    Only the first method (of the first 10) whose type is shipping or absent is read; pickup and other types are ignored.

    • typestring | null

      shipping (or absent). Other types are ignored.

    • destinationsarray<PostalAddressRequest> | null

      Addresses (the first 10 are read). The one named by selected_destination_id, else the first, becomes the session’s shipping address and replaces the stored one. A stored address cannot be removed, only replaced.

    • selected_destination_idstring | null

      The id of the address to use. A non-string, or one that names no address, is ignored: the first address is used.

      maxLength: 100
    • groupsarray<object>

      Only groups[0] is read (a non-array is ignored).

      • selected_option_idstring | null

        An option id from fulfillment.methods[0].groups[0].options of an earlier answer. null clears the choice; absent — or an empty string or a non-string — keeps it. A choice that is not (or no longer) offered stays stored, reads as null and adds invalid_option.

        maxLength: 100

DiscountsRequest

object | null

Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

  • codesarray<string> | null

    Discount codes, replacing the stored list; [] clears them, null or absent keeps them. At most 10 (counted before duplicates are removed; more → 400), each cut to 50 characters; blanks and non-strings dropped; duplicates removed case-insensitively. The shop applies one code: the first it accepts. Every later code gets discount_code_combination_disallowed; a refused code gets discount_code_expired, discount_code_user_ineligible (below the code’s minimum order total, which is compared with the subtotal without VAT, before the discount) or discount_code_invalid. On a session that has never held a line every code reads as discount_code_invalid; the stored codes are tried again on every create and update (not on cancel).

    maxItems: 10každá položka:maxLength: 50

CartCreateRequest

object

A new cart. Unknown fields are ignored. Body problems are reported before anything is stored.

  • line_itemsarray<LineItemRequest>povinné

    The lines. Required; [] creates an empty cart. If lines are sent and none can be added the answer is a 200 business error and no cart exists.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

  • fulfillmentFulfillmentRequest | null

    Accepted and stored, never used for a cart (validated like a checkout’s when the fulfillment extension is negotiated, so a malformed one is still a 400).

CartUpdateRequest

object

A cart update. Fields you leave out keep their stored value (except that line_items is required). Every write extends expires_at to 7 days from now.

  • line_itemsarray<LineItemRequest>povinné

    Required, full replacement: lines not listed are removed. Missing → 400 line_items must be an array.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

  • fulfillmentFulfillmentRequest | null

    Accepted and stored, never used for a cart (validated like a checkout’s when the fulfillment extension is negotiated, so a malformed one is still a 400).

CheckoutCreateRequest

object

A new checkout session, directly from lines or from a cart. Send line_items, or cart_id to convert a cart (read only when dev.ucp.shopping.cart is negotiated). Without a usable cart_id, line_items is required: a missing one is 400 invalid_request "line_items must be an array". Unknown fields are ignored.

  • cart_idstring

    Convert this cart (only when dev.ucp.shopping.cart is negotiated; otherwise it is ignored and line_items is required). The checkout takes the cart’s lines, buyer, context and language — line_items, buyer and context in this request are ignored (buyer and context are still checked: a malformed one is a 400) — its fulfillment from this request, and its discount codes from this request when discounts.codes is sent (else the cart’s). The cart itself is not changed. One open checkout per cart: when the cart already has one, that checkout is answered with HTTP 200 and this request’s fields are not applied. A cart whose lines the buyer already took to the shop → 200 handed_off. An unknown id → 200 not_found ("Checkout session not found").

  • line_itemsarray<LineItemRequest>

    The lines (direct create). Required unless cart_id converts a cart; [] creates an empty, incomplete checkout.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • fulfillmentFulfillmentRequest | null

    Shipping (only read when dev.ucp.shopping.fulfillment is negotiated; ignored on carts — a cart stores it but never uses it, and a conversion takes fulfillment from the conversion request). null or a body without fulfillment keeps what the session holds.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

CheckoutUpdateRequest

object

A checkout update. Fields you leave out keep their stored value; {} is valid: it re-derives the status, quotes the shipping options again, applies the stored discount codes again (with the discount extension) and extends expires_at; it does not check the lines again. Every update of an open checkout extends expires_at to 24 h from now.

  • line_itemsarray<LineItemRequest>

    Optional. When sent, full replacement; absent keeps the lines.

    maxItems: 100
  • buyerBuyerRequest | null

    Replaces the whole stored buyer: a field you leave out is erased (send the e-mail again when you add a name). null clears the buyer; leaving buyer out keeps it. A non-string value → 400; a longer string is cut to the stated length (counted in code points). Names and phone are never required.

  • contextRequestContext | null

    Buyer context. Every field is optional; strings longer than the stated length are cut (counted in code points). Unknown fields (intent, eligibility, payment …) are ignored.

  • fulfillmentFulfillmentRequest | null

    Shipping (only read when dev.ucp.shopping.fulfillment is negotiated; ignored on carts — a cart stores it but never uses it, and a conversion takes fulfillment from the conversion request). null or a body without fulfillment keeps what the session holds.

  • discountsDiscountsRequest | null

    Discount codes (only read when dev.ucp.shopping.discount is negotiated). Without the extension a code is never applied: one applied earlier is taken off the session’s cart for that answer (the stored codes are kept, and a later request that negotiates the extension applies them again).

CheckoutCompleteRequest

object

Ignored. Any payment, payment_data or AP2 field is ignored and never stored: no payment is processed through UCP. The body still counts for the Idempotency-Key’s request identity.

  • <ľubovoľný ďalší kľúč>any

LineItemTotal

object

A line amount. subtotal and total are equal: discounts are not allocated to lines.

additionalProperties: false
  • typestringpovinné
    jedna z hodnôt:subtotaltotal
  • amountintegerpovinné

    The line’s total (unit price × quantity). Integer in ISO 4217 minor units of the session’s currency (EUR: cents), VAT included.

    minimum: 0

LineItem

object

A line of the shop cart that prices the session.

additionalProperties: false
  • idstring (uuid)povinné

    The line id. Stable while the line exists; new after a language change, and different in a checkout made from a cart.

  • itemobjectpovinné

    The variant on this line.

    additionalProperties: false
    • idstring (uuid)povinné

      The variant id — also when the request named a product id.

    • titlestringpovinné

      The product title in the session’s language (the variant id when there is none) — never the variant’s title: two lines of one product read the same. Identify the variant by item.id (keep the title and options the catalog showed for it, or look it up).

    • priceintegerpovinné

      Unit price. Integer in ISO 4217 minor units of the session’s currency (EUR: cents), VAT included.

      minimum: 0
    • image_urlstring

      The product’s main image, absolute. Absent when there is none.

      pattern: ^[Hh][Tt][Tt][Pp][Ss]?://
  • quantityintegerpovinné

    Quantity after adjustments (stock, the 999 cap), in the unit the shop sells — one package for goods sold in packages. UCP publishes no package content.

    minimum: 1maximum: 999
  • totalsarray<LineItemTotal>povinné

    [subtotal, total].

    minItems: 2maxItems: 2

Total

object

One total. Order: subtotal, discount?, fulfillment?, total; every entry except total sums to total. Payment-method surcharges are chosen on the shop and never quoted.

additionalProperties: false
  • typestringpovinné

    subtotal: sum of the line totals. discount (negative; only when a code is applied with an amount above 0): what the shop deducts — a percentage code is a percentage of the subtotal WITHOUT VAT, deducted from this VAT-inclusive subtotal, exactly as the shop’s checkout charges it. fulfillment (checkouts, only once a valid shipping option is selected; may be 0): the shipping fee. total: subtotal + discount + fulfillment.

    jedna z hodnôt:subtotaldiscountfulfillmenttotal
  • amountintegerpovinné

    The amount. Integer in ISO 4217 minor units of the session’s currency (EUR: cents), VAT included.

  • display_textstring

    On discount: the applied code, upper-cased.

Buyer

object

The stored buyer; empty fields are left out. Absent when nothing is stored.

minProperties: 1additionalProperties: false
  • first_namestring

    As stored.

  • last_namestring

    As stored.

  • emailstring

    As stored (lower-cased when it looks like an address).

  • phone_numberstring

    As stored.

Context

object

The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

minProperties: 1additionalProperties: false
  • address_countrystring

    Alpha-2 country code.

    pattern: ^[A-Z]{2}$
  • address_regionstring

    As sent.

  • postal_codestring

    As sent.

  • languagestring

    The language you sent — not necessarily the one the session uses (that is not published; the currency and titles show it).

  • currencystring

    As sent (upper-cased). Not the currency of the prices.

    pattern: ^[A-Z]{3}$

AppliedDiscount

object

The code the shop applied.

additionalProperties: false
  • codestringpovinné

    The applied code, upper-cased.

  • titlestringpovinné

    The same as code.

  • amountintegerpovinné

    The deduction, positive. Integer in ISO 4217 minor units of the session’s currency (EUR: cents), VAT included.

    minimum: 1
  • methodstringpovinné

    Always across (spread over the whole cart).

    vždy:across

Discounts

object

Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

additionalProperties: false
  • codesarray<string>povinné

    The codes you asked for, as stored (your spelling, duplicates removed).

    maxItems: 10
  • appliedarray<AppliedDiscount>povinné

    At most one entry: the session’s cart holds one code.

    maxItems: 1

ShippingDestination

object

The stored shipping address; empty fields are left out.

additionalProperties: false
  • idstringpovinné

    Always destination (your own id is not echoed).

    vždy:destination
  • street_addressstring

    As sent.

  • extended_addressstring

    As sent.

  • address_localitystring

    As sent.

  • address_regionstring

    As sent.

  • postal_codestring

    As sent.

  • address_countrystring

    Alpha-2 country code.

    pattern: ^[A-Z]{2}$
  • first_namestring

    As sent.

  • last_namestring

    As sent.

  • phone_numberstring

    As sent.

FulfillmentOption

object

A shipping option: one of the shop’s active shipping methods, filtered by the destination country (no destination → no country filter) and by the methods’ product rules. No carrier code, delivery date or pickup flag is published.

additionalProperties: false
  • idstring (uuid)povinné

    The shipping method’s id — the value for selected_option_id.

  • titlestringpovinné

    The method’s name in the session’s language.

  • descriptionstring

    The method’s description in the session’s language, else its own description (which can be in another language of the shop), else the English Delivery in {min}–{max} business days (or Delivery in {n} business days) from its delivery estimate; absent when none exists. At most 300 characters.

    maxLength: 300
  • totalsarray<object>povinné

    One entry.

    minItems: 1maxItems: 1
    • typestringpovinné
      vždy:total
    • amountintegerpovinné

      The shipping fee for this checkout — the same rule the shop’s checkout charges (0 once the cart’s subtotal without VAT reaches the method’s free-shipping threshold). Integer in ISO 4217 minor units of the session’s currency (EUR: cents), VAT included.

      minimum: 0

FulfillmentGroup

object

The single shipping group.

additionalProperties: false
  • idstringpovinné
    vždy:default
  • line_item_idsarray<string (uuid)>povinné

    Every line of the checkout.

    každá položka:format: uuid
  • optionsarray<FulfillmentOption>povinné

    In the shop’s order. Empty while the checkout has no line or the shop does not ship to the destination.

  • selected_option_idstring (uuid) | nullpovinné

    The selected option, only when it is among options; otherwise null.

FulfillmentMethod

object

The single shipping method covering every line.

additionalProperties: false
  • idstringpovinné
    vždy:shipping
  • typestringpovinné
    vždy:shipping
  • line_item_idsarray<string (uuid)>povinné

    Every line of the checkout.

    každá položka:format: uuid
  • destinationsarray<ShippingDestination>

    Present once an address is stored.

    minItems: 1maxItems: 1
  • selected_destination_idstring

    Present once an address is stored.

    vždy:destination
  • groupsarray<FulfillmentGroup>povinné

    Exactly one group.

    minItems: 1maxItems: 1

Fulfillment

object

Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

additionalProperties: false

Order

object

The order the buyer placed on the shop.

additionalProperties: false
  • idstring (uuid)povinné

    The order id (the parent order when the shop split it by VAT).

  • labelstring

    The shop’s order number. Absent when the order has none.

    maxLength: 50
  • permalink_urlstring (uri)povinné

    The shop’s order confirmation page, https://{primary domain}/order-confirmation?orderNumber=…&orderId=… (orderNumber only when there is one). Renders without a sign-in; shows the number, not the order’s status.

ContinueUrl

string (uri)

The buyer handoff link, https://{primary domain}/ucp/continue/{64 hex}. Give it to the BUYER to open in their browser; never call it yourself. It never changes during the session’s life and works while the session is open and unexpired — an incomplete checkout included: the first line that reaches a buyer’s cart freezes the session in the status it has, so give the link only at requires_escalation. Once a cart was converted, its own link hands over the checkout made from it. Not single-use: every distinct browser (or signed-in customer) that opens it gets the lines once.

pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$

ExpiresAt

string (date-time)

When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

Cart

object

A cart. It has no status: a cancel answers the cart as it was (with continue_url), and only a later read shows not_found.

additionalProperties: false
  • ucpResponseMetadatapovinné

    The ucp object of a successful catalog or cart answer. A missing status means success.

  • idstring (uuid)povinné

    The cart id.

  • line_itemsarray<LineItem>povinné

    In the shop cart’s order, which need not be the order you sent.

  • currencystring · CurrencyCodepovinné

    ISO 4217 currency code.

    pattern: ^[A-Z]{3}$
  • totalsarray<Total>povinné

    subtotal, discount? and total (never fulfillment on a cart).

    minItems: 2maxItems: 3
  • messagesarray<Message>

    Present only when non-empty. Line and discount messages appear only in the answer to the write that caused them (a cart the buyer took to the shop repeats its last answer, messages included).

    minItems: 1
  • buyerBuyer

    The stored buyer; empty fields are left out. Absent when nothing is stored.

  • contextContext

    The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

  • discountsDiscounts

    Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

  • continue_urlstring (uri) · ContinueUrlpovinné

    The buyer handoff link, https://{primary domain}/ucp/continue/{64 hex}. Give it to the BUYER to open in their browser; never call it yourself. It never changes during the session’s life and works while the session is open and unexpired — an incomplete checkout included: the first line that reaches a buyer’s cart freezes the session in the status it has, so give the link only at requires_escalation. Once a cart was converted, its own link hands over the checkout made from it. Not single-use: every distinct browser (or signed-in customer) that opens it gets the lines once.

    pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
  • expires_atstring (date-time) · ExpiresAtpovinné

    When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

CheckoutStatus

string
  • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
  • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
  • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
  • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

jedna z hodnôt:incompleterequires_escalationcompletedcanceled

Checkout

object

A checkout session.

Once completed or canceled — and while the buyer’s handoff is done but the order not yet placed — the body is the last answer the platform received, with the current status (and order): it is not re-priced, and it does not show what the buyer changed on the shop. Only the lines and the applied discount code travel to the shop; the buyer enters address, shipping and payment there again.

additionalProperties: false
  • The ucp object of every checkout answer that returns a checkout (any status).

  • idstring (uuid)povinné

    The checkout session id.

  • statusstring · CheckoutStatuspovinné
    • incomplete — at least one recoverable error is listed (no line, no valid e-mail, no or incomplete address, no shipping to it, no or an unoffered option, a line error in this answer).
    • requires_escalation — nothing the platform can provide is missing; payment_required is listed and the buyer must continue on the shop through continue_url.
    • completed — the buyer placed an order on the shop from the cart the handoff filled; see order. It means placed, not paid. Set shortly after the order (once the shop has started the payment and handed the order to its ERP) and best effort: a failure is only logged, so an order can exist for a checkout that never reads completed.
    • canceled — canceled by POST …/cancel, or expired (an open checkout read or written after expires_at turns canceled).

    ready_for_complete and complete_in_progress are never used: the API never completes a checkout.

    jedna z hodnôt:incompleterequires_escalationcompletedcanceled
  • currencystring · CurrencyCodepovinné

    ISO 4217 currency code.

    pattern: ^[A-Z]{3}$
  • line_itemsarray<LineItem>povinné

    In the shop cart’s order.

  • totalsarray<Total>povinné

    subtotal, discount?, fulfillment?, total.

    minItems: 2maxItems: 4
  • messagesarray<Message>povinné

    Always present, possibly empty. Order: line messages and discount messages (only in the answer to the write that caused them), then missing / invalid_address / address_undeliverable / invalid_option, pickup_point_required, and payment_required last. Empty on completed and canceled reads.

  • linksarray<Link>povinné

    Always present: at most 10, possibly empty.

    maxItems: 10
  • buyerBuyer

    The stored buyer; empty fields are left out. Absent when nothing is stored.

  • contextContext

    The stored context, echoed as you sent it (after cleaning); empty fields are left out. Absent when nothing is stored.

  • fulfillmentFulfillment

    Present only when dev.ucp.shopping.fulfillment is negotiated. One shipping method with one group; there is no pickup method — a pickup point (Packeta) is chosen by the buyer on the shop.

  • discountsDiscounts

    Present only when dev.ucp.shopping.discount is negotiated. The discount total is present whether or not it is.

  • continue_urlstring (uri) · ContinueUrl

    Present while the checkout is incomplete or requires_escalation; absent once completed or canceled.

    pattern: ^https://[^/]+/ucp/continue/[0-9a-f]{64}$
  • expires_atstring (date-time) · ExpiresAtpovinné

    When the session expires (UTC, ISO 8601 with milliseconds). A write extends it (cart: now + 7 days, checkout: now + 24 h); reads, complete and cancel do not. After a handoff nothing extends it.

  • orderOrder

    Present once completed.

BusinessProfile

object

The business profile. Apart from the capabilities a shop in catalog mode leaves out, the only value that differs between shops is the host in endpoint (the primary domain).

additionalProperties: false
  • ucpobjectpovinné

    The profile. There is no supported_versions, no signing_keys, no business name or logo.

    additionalProperties: false
    • versionstring · UcpVersionpovinné

      The UCP protocol version. This shop implements exactly one.

      vždy:2026-04-08
    • servicesobjectpovinné

      One service.

      additionalProperties: false
      • dev.ucp.shoppingarray<object>povinné
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/overview.

        • transportstringpovinné

          REST is the only transport (no MCP, A2A or embedded).

          vždy:rest
        • endpointstring (uri)povinné

          The REST base URL, https://{primary domain}/ucp/v1. Call this.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json (the protocol’s own OpenAPI).

    • capabilitiesobjectpovinné

      What this shop declares. A shop in catalog mode (it takes no orders) declares only the two catalog capabilities.

      additionalProperties: false
      • dev.ucp.shopping.catalog.searcharray<object>povinné
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/… for this capability.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/schemas/shopping/….json for this capability.

      • dev.ucp.shopping.catalog.lookuparray<object>povinné
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/… for this capability.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/schemas/shopping/….json for this capability.

      • dev.ucp.shopping.cartarray<object>
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/… for this capability.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/schemas/shopping/….json for this capability.

      • dev.ucp.shopping.checkoutarray<object>
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/… for this capability.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/schemas/shopping/….json for this capability.

      • dev.ucp.shopping.fulfillmentarray<object>
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/… for this capability.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/schemas/shopping/….json for this capability.

        • extendsstringpovinné

          The parent capability.

          vždy:dev.ucp.shopping.checkout
      • dev.ucp.shopping.discountarray<object>
        minItems: 1maxItems: 1
        • versionstring · UcpVersionpovinné

          The UCP protocol version. This shop implements exactly one.

          vždy:2026-04-08
        • specstring (uri)povinné

          https://ucp.dev/2026-04-08/specification/… for this capability.

        • schemastring (uri)povinné

          https://ucp.dev/2026-04-08/schemas/shopping/….json for this capability.

        • extendsarray<string · CapabilityName>povinné

          The parent capabilities: ["dev.ucp.shopping.checkout","dev.ucp.shopping.cart"].

          každá položka · jedna z hodnôt:dev.ucp.shopping.catalog.searchdev.ucp.shopping.catalog.lookupdev.ucp.shopping.cartdev.ucp.shopping.checkoutdev.ucp.shopping.fulfillmentdev.ucp.shopping.discount
    • payment_handlersobjectpovinné

      Always {}: the buyer pays on the shop’s own checkout.

      maxProperties: 0

HandoffClaimRequest

object

Sent by the handoff page’s own script.

  • sessionIdstringpovinné

    The browser’s storefront guest-cart id (localStorage["sf-cart"].state.sessionId; the page creates one when there is none). 8–128 characters of A-Z a-z 0-9 _ -, not starting with ucp_. It is not the UCP session id.

    pattern: ^(?!ucp_)[A-Za-z0-9_-]{8,128}$

HandoffClaimResponse

object

Where the buyer’s browser goes next.

additionalProperties: false
  • redirectstringpovinné

    Same-origin path the page continues to: /checkout for a checkout (also when a cart link handed over the cart’s open checkout), /cart for a cart (and when nothing could be added because the browser’s cart holds items in another currency), / on a shop in catalog mode.

    jedna z hodnôt:/checkout/cart/

UcpBusinessErrorResponse

object

HTTP 200 with no resource. The cases:

  • capabilities_incompatible — your profile and this shop share no version of the operation’s root capability (always for carts and checkouts on a shop in catalog mode). Nothing ran; no Idempotency-Key was used.
  • not_found — the product, cart (Cart not found or has expired) or checkout (Checkout session not found) does not exist for this shop, is malformed, or (carts) was canceled or expired. An unknown cart_id on POST /checkout-sessions also reads Checkout session not found.
  • out_of_stock / item_unavailable — a create in which none of the requested items could be added; no session was created.
  • handed_off — converting a cart whose lines the buyer already took to the shop.

Inside a write the last three are stored under the Idempotency-Key like a success (capabilities_incompatible is answered before the key is read).

additionalProperties: false
  • The ucp object of a business error. Never has payment_handlers. capabilities is {} for capabilities_incompatible and lists the operation’s root (and its negotiated extensions) otherwise.

  • messagesarray<MessageError>povinné

    Exactly one error message saying why no resource could be returned.

    minItems: 1maxItems: 1
  • continue_urlstring (uri)povinné

    Where a buyer could go instead: the shop home (https://{primary domain}/), or https://{primary domain}/cart for handed_off. Not a handoff link.