Preskočiť na obsah

Rýchly štart pre vývojárov

Celý nákup cez UCP krok za krokom — curl, JavaScript a Python, od profilu obchodu po odovzdanie checkoutu kupujúcemu.

Na tejto stránke

Táto kapitola vás prevedie jedným nákupom v obchode na Monokaide od prvého volania až po objednávku. Prečítate si profil obchodu, vyhľadáte v jeho katalógu, zostavíte košík, urobíte z neho checkout, doplníte kupujúceho a dopravu, odovzdáte kupujúcemu continue_url a zistíte, že objednávka vznikla. Pri každom kroku je presná požiadavka aj s hlavičkami v curl, JavaScripte (Node 20, vstavaný fetch) a Pythone 3 (requests) a za ňou odpoveď obchodu. Na konci sú dva kompletné skripty, ktoré prejdú celý postup aj s opakovaním požiadaviek a čakaním na objednávku.

Príklady používajú vymyslený obchod https://shop.example.com (slovenský obchod s bicyklami, ceny v eurách) a profil platformy https://platform.example.com/.well-known/ucp. Odpovede v JSON sú príklady z referencie API, ktoré priamo z ukážkových dát vytvoril kód Monokaida. Majú teda presne taký tvar, aký vráti skutočný obchod; identifikátory, ceny a texty sa líšia od obchodu k obchodu.

Nákup cez UCP od profilu obchodu po objednávku
  1. Agent / platforma → Obchod (UCP API):

    GET /.well-known/ucp

    Načíta profil obchodu: verziu protokolu, adresu REST API a schopnosti, ktoré obchod ponúka. Netreba žiadnu hlavičku; odpoveď sa smie uchovať v cache päť minút.

  2. Agent / platforma → Obchod (UCP API):

    POST /ucp/v1/catalog/search

    Hľadá v katalógu obchodu (lookup a product čítajú položky podľa id). Každé volanie /ucp/v1 nesie hlavičku UCP-Agent s adresou profilu platformy.

  3. Agent / platforma → Obchod (UCP API):

    POST /ucp/v1/carts

    Založí košík, ktorý nacení obchod — jeho ceny, sklad a zľavy. Košík platí 7 dní od poslednej zmeny.

  4. Agent / platforma → Obchod (UCP API):

    POST /ucp/v1/checkout-sessionsincomplete

    Vytvorí checkout z košíka (cart_id) alebo priamo z line_items. Kým niečo chýba, má stav incomplete.

  5. Agent / platforma → Obchod (UCP API):

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

    Doplní e-mail kupujúceho a s rozšírením fulfillment aj adresu a spôsob dopravy. Keď nič nechýba: requires_escalation so správou payment_required a s continue_url.

  6. Agent / platforma → Kupujúci (prehliadač):

    continue_url

    Odovzdá continue_url kupujúcemu. Monokaido cez UCP platbu neprijíma a POST …/complete checkout nedokončí.

  7. Kupujúci (prehliadač) → Obchod (UCP API):

    GET /ucp/continue/{token}

    Otvorí continue_url na doméne obchodu. Stránka presunie položky a zľavový kód do jeho košíka v tomto prehliadači a otvorí pokladňu obchodu.

  8. Kupujúci (prehliadač) → Obchod (UCP API):

    /checkout

    V pokladni obchodu dokončí nákup vrátane platby — ako ktorýkoľvek zákazník.

  9. Agent / platforma → Obchod (UCP API):

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

    Keď objednávka obsahuje aspoň jednu položku z relácie, ďalšie čítanie checkoutu vráti completed s objednávkou (order). Webhooky neexistujú: stav treba zisťovať čítaním.

continue_url košíka (bez checkoutu) funguje rovnako, len otvorí košík obchodu namiesto pokladne.

Pred začiatkom

Obchod so zapnutým UCP

Potrebujete doménu obchodu, ktorému obchodník UCP zapol (Zapnutie UCP v obchode). UCP beží len na vlastnej doméne obchodu s aktívnym certifikátom, nikdy na adrese *.shop.monokaido.com. Rýchla kontrola:

curl -sS -o /dev/null -w '%{http_code}\n' https://shop.example.com/.well-known/ucp

200 znamená, že obchod UCP poskytuje. 404 znamená, že nie, nech je dôvod akýkoľvek: odpoveď je zámerne vo všetkých prípadoch rovnaká. 503 znamená, že obchod práve nemôže odpovedať: skúste to znova po Retry-After.

Testovacie prostredie (sandbox) neexistuje. Košíky a checkouty sa naceňujú aktuálnymi cenami a skladom obchodu, no nie sú to objednávky: nič sa neobjedná ani nezaplatí, kým kupujúci neotvorí continue_url a nezadá objednávku v obchode. Taká objednávka je skutočnou objednávkou obchodu, preto každý test dohodnite s obchodníkom.

Profil vašej platformy

Každé volanie pod /ucp/v1 uvádza v hlavičke UCP-Agent profil vašej platformy a obchod si ho stiahne. Na adrese https://, ktorú spravujete, zverejnite JSON dokument ako tento:

{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.catalog.lookup": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.cart": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.checkout": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.fulfillment": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.discount": [{ "version": "2026-04-08" }]
    }
  }
}

Obchod z neho číta len toto: ucp.version, ktorá musí byť presne 2026-04-08, a ucp.capabilities. Schopnosť (capability), ktorú vynecháte, sa nevyjedná a každá operácia, ktorá ju potrebuje, odpovie capabilities_incompatible. Bez dev.ucp.shopping.fulfillment checkout nežiada adresu ani spôsob dopravy; bez dev.ucp.shopping.discount sa zľavové kódy ignorujú. Ďalšie časti profilu sú dovolené a obchod ich ignoruje.

Obchod si profil stiahne obyčajným GET a prijme ho, len ak:

  • adresa začína https://, neobsahuje meno ani heslo, nemá v sebe medzery a má najviac 1 000 znakov;
  • hostiteľ sa preloží na verejnú IP adresu (súkromné, loopback a ďalšie vyhradené rozsahy sa odmietajú);
  • odpoveď je HTTP 200 (presmerovania sa nesledujú) s telom JSON s veľkosťou najviac 256 KiB a príde do 5 sekúnd.

Profil posielajte s hlavičkou Cache-Control: public, max-age=3600. Obchod si ho uchová na dobu podľa max-age, obmedzenú na 60 sekúnd až jednu hodinu (bez max-age päť minút). Keď ten čas uplynie, ďalšia požiadavka ešte dostane uložený profil a obchod si ho medzitým na pozadí stiahne znova, takže zmena profilu sa do obchodu dostane krátko po tom čase. Neúspešné stiahnutie profilu, ktorý obchod ešte nemá, si pamätá 60 sekúnd: keď hosting opravíte, počkajte minútu. Kľúčom vyrovnávacej pamäte je celá adresa a podľa nej sa oddeľujú aj vaše kľúče Idempotency-Key, preto ju nemeňte. Každá adresa profilu, ktorá ešte nie je uložená, znamená jedno stiahnutie a pre jednu IP adresu klienta sa stiahne najviac 5 nových profilov za minútu, spolu vo všetkých obchodoch na Monokaide; nad tento počet obchod odpovie 503 s Retry-After.

Hlavičky

HlavičkaKedy ju poslaťHodnota
UCP-Agentpri každom volaní pod /ucp/v1profile="https://platform.example.com/.well-known/ucp". Úvodzovky sú súčasťou zápisu a profile sa píše malými písmenami.
Content-Typepri každej požiadavke s telomapplication/json, UTF-8. Telo vo formáte formulára (application/x-www-form-urlencoded) sa spracuje tiež, no jeho hodnoty prídu ako reťazce, takže číselné polia ako quantity sa odmietnu; telo iného typu sa nečíta.
Idempotency-Keypri siedmich zápisoch: vytvorenie, úprava a zrušenie košíka; vytvorenie, úprava, dokončenie a zrušenie checkoutuNové UUID pre každý logický zápis, pri každom opakovaní toho istého zápisu poslané znova bez zmeny. Prijme sa ľubovoľná hodnota s 1 až 255 viditeľnými znakmi ASCII; iná hodnota sa odmietne s 400. Kľúč sa uchováva najmenej 25 hodín od prvého použitia (spoliehajte sa na 24), zvlášť pre každý obchod a každú adresu profilu. Je nepovinný, no bez neho zopakované vytvorenie založí druhý košík alebo checkout.
Accept-LanguagenepovinnáPoužije sa, keď telo neobsahuje context.language alebo ho obchod neponúka. Jazyky sa skúšajú v poradí, v akom sú uvedené; váhy q= sa ignorujú. Ak sa žiadny nezhoduje, odpoveď je v predvolenom jazyku obchodu. Košík aj checkout si jazyk ponechajú, kým ho telo požiadavky nezmení.

Žiadny API kľúč, OAuth ani cookie: obchod sa určí podľa hostiteľa, ktorého voláte. API volajte zo svojho servera. Pre cudzie pôvody neposiela hlavičky CORS, takže webová stránka na vašej doméne ho z prehliadača nezavolá.

Volania

KrokVolanieOdpoveď
1GET /.well-known/ucpprofil obchodu
2POST /ucp/v1/catalog/searchprodukty s variantmi
3POST /ucp/v1/carts201, nový košík
4PUT /ucp/v1/carts/{id}košík s novým nacenením
5POST /ucp/v1/checkout-sessions201, checkout v stave incomplete
6PUT /ucp/v1/checkout-sessions/{id}checkout v stave requires_escalation
7žiadnekupujúci otvorí continue_url v prehliadači
8GET /ucp/v1/checkout-sessions/{id}completed aj s objednávkou v order

Príprava pre ukážky

Ukážky jednotlivých krokov používajú spoločnú prípravu. V curl sú to premenné shellu (ukážky potrebujú aj jq a uuidgen). V JavaScripte a Pythone pošle hlavičky malá pomocná funkcia, ktorá vyhodí výnimku pri oboch druhoch zlyhania: pri chybe protokolu (chybový stav HTTP s poľom code) aj pri obchodnej chybe (HTTP 200 s ucp.status: "error", keď sa nevrátil žiadny zdroj). Kroky vložte za ňu v tomto poradí. Pomocné funkcie požiadavky neopakujú; kompletné skripty áno.

SHOP=https://shop.example.com
AGENT='profile="https://platform.example.com/.well-known/ucp"'

Krok 1: objavenie obchodu

Profil obchodu je verejný: nepotrebuje hlavičku UCP-Agent a obchod dovoľuje uchovať ho päť minút.

curl -sS -o profile.json -w 'HTTP %{http_code}\n' "$SHOP/.well-known/ucp"
jq . profile.json
API=$(jq -r '.ucp.services["dev.ucp.shopping"][0].endpoint' profile.json)  # https://shop.example.com/ucp/v1
JSONProfil obchodu, ktorý prijíma objednávky
{
  "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": {}
  }
}

GET https://shop.example.com/.well-known/ucp for a shop whose primary domain is shop.example.com, not in catalog mode.

Vyčítajte z neho tri veci:

  • endpoint v ucp.services["dev.ucp.shopping"] je základ každého ďalšieho volania. Jeho hostiteľ je primárna doména obchodu, ktorá sa môže líšiť od domény, ktorú ste volali. Použite ho a adresu neskladajte sami.
  • ucp.capabilities hovorí, čo obchod práve ponúka. Obchod v katalógovom režime uvádza len dev.ucp.shopping.catalog.search a dev.ucp.shopping.catalog.lookup: vyhľadávať v ňom môžete, no každé volanie košíka a checkoutu odpovie capabilities_incompatible, preto skončite po kroku 2.
  • payment_handlers je vždy {}. Cez UCP neprechádza žiadna platba; kupujúci platí v obchode.

Krok 2: vyhľadanie v katalógu

Hľadajte textom v jazyku obchodu. pagination.limit je predvolene 10 a väčšia hodnota sa zníži na 20. context.language určuje jazyk textov a tým aj menu všetkých cien. context.currency menu nemení: hovorí len, v akej mene je zadaný filter filters.price. Bez neho sa hranice ceny čítajú v mene odpovede, teda v tej, ktorú určuje jazyk.

curl -sS -o search.json -w 'HTTP %{http_code}\n' -X POST "$API/catalog/search" \
  -H "UCP-Agent: $AGENT" \
  -H 'Content-Type: application/json' \
  -H 'Accept-Language: sk' \
  -d '{"query":"marlin","pagination":{"limit":2},"context":{"language":"sk","address_country":"SK","currency":"EUR"}}'
jq '.products[] | {title, variants: [.variants[] | {id, title, price, availability}]}' search.json
VARIANT=$(jq -r '[.products[].variants[] | select(.availability.available)][0].id' search.json)

Telo požiadavky:

JSONHľadanie „marlin“, dva výsledky na stranu
{
  "query": "marlin",
  "pagination": {
    "limit": 2
  },
  "context": {
    "language": "sk",
    "address_country": "SK",
    "currency": "EUR"
  }
}

First page of a text search in Slovak. currency here only denominates a price filter (none is sent); unknown fields would be ignored.

Odpoveď:

JSONPrvá strana: dva produkty, ďalšie nasledujú
{
  "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
  }
}

The answer to CatalogSearchRequest. The listing had 3 matches (page 1 of 2), so has_next_page is true and total_count is 3 (no price filter in effect). The first available variant of Trek Marlin 5 is already first; the pre-order variant counts as available; the vendor image keeps its own URL.

Pre ďalšie kroky je dôležité:

  • variants[].id patrí do položky košíka. Vyberte variant s availability.available: true (stav in_stock alebo preorder).
  • Ceny sú celé čísla v najmenších jednotkách meny, s DPH: 69900 s EUR je 699,00 €. list_price je pôvodná alebo odporúčaná cena a uvádza sa, len keď je vyššia.
  • Stránkovanie. Kým je has_next_page true, pošlite rovnaké telo znova a do pagination.cursor dajte cursor, ktorý ste dostali. Kurzor nesie len číslo strany, preto dopyt, filtre a limit musia zostať rovnaké. Strana môže byť kratšia, dokonca prázdna, a pritom ďalšie strany nasledujú: pokračujte, kým has_next_page nie je false.
  • Poradie a zhoda. Produkty prichádzajú od najnovšieho, bez zoradenia podľa relevancie. Dopyt sa porovnáva s názvom produktu a s kódmi samotného produktu (kód produktu v obchode a identifikátory produktu od dodávateľa), nie s popisom, SKU variantov ani kódom EAN. Na veľkosti písmen nezáleží, na diakritike áno: modra nenájde Modrá. Produkty podľa SKU, handle alebo identifikátora nájdete cez POST /ucp/v1/catalog/lookup, najviac 20 naraz.
  • Zoznam, ktorý sa hýbe. Vyhľadávanie číta zoznam produktov obchodu, ktorý platforma obnovuje na pozadí: novo publikovaný produkt sa môže objaviť až o niekoľko minút a produkt publikovaný počas stránkovania posunie ďalšie strany, takže sa produkt môže zopakovať alebo vynechať. Produkty deduplikujte podľa id. Lookup a detail produktu čítajú priamo údaje produktu.

Krok 3: vytvorenie košíka

Každá položka uvádza variant v item.id: vždy variants[].id, ktorý ste vybrali z katalógu. Prijme sa aj identifikátor produktu, položka však potom kúpi ten aktívny variant produktu, ktorý vznikol najskôr — nemusí to byť variant, ktorý ukázalo vyhľadávanie, ani variant na sklade; odpoveď uvedie tento variant. quantity je predvolene 1; nula, záporné alebo desatinné číslo sa odmietne s 400. Prijme sa najviac 100 položiek a množstvo nad 999 sa bez správy zníži na 999. Dve položky toho istého variantu sa spoja do jednej so súčtom množstiev. buyer je pri košíku nepovinný. Je to prvý zápis, preto nesie Idempotency-Key.

KEY=$(uuidgen)  # one key per logical write; a retry of this request sends the same one
jq -n --arg variant "$VARIANT" '{
  line_items: [
    {item: {id: $variant}, quantity: 1},
    {item: {id: "4a6e2d9c-8b15-4f37-9c48-1e7a5b3d2f90"}}
  ],
  buyer: {email: "Jana.Novakova@Example.com"},
  context: {address_country: "SK", language: "sk"}
}' | curl -sS -o cart.json -w 'HTTP %{http_code}\n' -X POST "$API/carts" \
  -H "UCP-Agent: $AGENT" \
  -H 'Content-Type: application/json' \
  -H 'Accept-Language: sk' \
  -H "Idempotency-Key: $KEY" \
  --data-binary @-
jq . cart.json
CART=$(jq -r .id cart.json)
JSONBicykel a prilba
{
  "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"
  }
}

Step 1 of the flow. The helmet line has no quantity (1). The e-mail is lower-cased when stored.

Obchod odpovie 201 a košíkom naceneným podľa vlastných pravidiel:

JSONVytvorený košík (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"
}

The answer to CartCreateRequest: the bike (699.00 €) and the helmet (59.90 €). No messages key (none). discounts is present because the discount extension is negotiated. The helmet has no image, so no image_url. expires_at = now + 7 days.

  • Uložte si id košíka. Košík žije 7 dní od posledného zápisu; potom každé volanie naň odpovie not_found.
  • totals obsahuje subtotal, zápornú položku discount, keď platí kód, a total. Ceny položiek sú v item.price, rovnako v najmenších jednotkách.
  • messages sa objaví, len keď je čo oznámiť. Položku, ktorú obchod nemôže predať, vynechá so správou item_unavailable, out_of_stock alebo vat_mix_not_allowed; zvyšok košíka sa vytvorí.
  • Ak sa nedá pridať žiadna položka, odpoveďou je HTTP 200 s ucp.status: "error", správou out_of_stock alebo item_unavailable a bez identifikátora košíka.
  • Aj košík má continue_url, kupujúcemu však odovzdajte checkout: len checkout sa dozvie, že objednávka vznikla.

Krok 4: úprava košíka

PUT nahradí položky košíka. line_items je povinné a je to celý zoznam: variant, ktorý vynecháte, sa odstráni. Položky sa párujú podľa item.id; poslané id položky sa ignoruje. Pole, ktoré vynecháte, napríklad buyer alebo context, si ponechá uloženú hodnotu. Pole, ktoré pošlete, nahradí uloženú hodnotu ako celok a discounts.codes nahradí zoznam kódov ([] odstráni všetky).

KEY=$(uuidgen)
jq -n --arg variant "$VARIANT" '{
  line_items: [
    {item: {id: $variant}, quantity: 1},
    {item: {id: "b5f81c3d-6e49-4a2b-8d73-9c0e4f1a6b25"}, quantity: 3}
  ],
  discounts: {codes: ["JESEN10", "LETO5"]}
}' | curl -sS -o cart.json -w 'HTTP %{http_code}\n' -X PUT "$API/carts/$CART" \
  -H "UCP-Agent: $AGENT" \
  -H 'Content-Type: application/json' \
  -H 'Accept-Language: sk' \
  -H "Idempotency-Key: $KEY" \
  --data-binary @-
jq '{totals, messages}' cart.json
JSONPrilbu nahradia tri fľaše, pribudnú dva kódy
{
  "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"
    ]
  }
}

Step 2. line_items is a full replacement: the helmet is removed. The line id is accepted and ignored (lines match by variant). Only 2 bottles are in stock. buyer and context are left out, so they are kept.

JSONUpravený košík: znížené množstvo, uplatnený jeden kód
{
  "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"
}

The answer to CartUpdateRequest. The bottle line was lowered to the 2 in stock (quantity_adjusted at its index in the answer); JESEN10 (10 % of the net subtotal 584.39 € = 58.44 €) is applied, LETO5 cannot be combined. Subtotal 718.80 € − 58.44 € = 660.36 €. The kept buyer and context are echoed; expires_at moved to now + 7 days.

  • Na košík platí jeden zľavový kód. Kódy sa skúšajú v poradí, v akom ich pošlete; uplatní sa prvý, ktorý obchod prijme, a každý ďalší dostane varovanie discount_code_combination_disallowed. Kód, ktorý obchod odmietne, dostane varovanie discount_code_invalid, discount_code_expired alebo discount_code_user_ineligible; path každého varovania ukazuje na kód v discounts.codes. Percentuálny kód sa počíta zo sumy bez DPH, rovnako ako v pokladni obchodu, a odpočíta sa od sumy s DPH.
  • Množstvo nad stav skladu sa zníži a pribudne varovanie quantity_adjusted.
  • Položky sa vracajú v poradí obchodu, nie vo vašom. Párujte ich podľa item.id; path správy, napríklad $.line_items[1].quantity, ukazuje do odpovede, nie do vašej požiadavky.
  • Každé vytvorenie a úprava nastaví expires_at na 7 dní odteraz.

Krok 5: z košíka checkout

Identifikátor košíka pošlite na POST /ucp/v1/checkout-sessions. Checkout prevezme položky košíka, kupujúceho, kontext (aj s jazykom) a zľavové kódy; vlastné line_items, buyer a context z požiadavky sa nepoužijú. Spolu s cart_id môžete poslať fulfillment a discounts; tie sa uplatnia na nový checkout. Samotný košík zostane nezmenený. cart_id sa číta, len keď váš profil uvádza dev.ucp.shopping.cart; bez neho požiadavka potrebuje line_items. Bez košíka prijme tá istá cesta priamo line_items (aj ostatné polia) v tvare z kroku 3.

KEY=$(uuidgen)
curl -sS -o checkout.json -w 'HTTP %{http_code}\n' -X POST "$API/checkout-sessions" \
  -H "UCP-Agent: $AGENT" \
  -H 'Content-Type: application/json' \
  -H 'Accept-Language: sk' \
  -H "Idempotency-Key: $KEY" \
  -d "{\"cart_id\":\"$CART\"}"
jq '{id, status, messages, options: .fulfillment.methods[0].groups[0].options}' checkout.json
CHECKOUT=$(jq -r .id checkout.json)
OPTION=$(jq -r '.fulfillment.methods[0].groups[0].options[0].id' checkout.json)
JSONPremena košíka na checkout
{
  "cart_id": "9a3f6c2e-5b18-4d47-8e9c-1f2a7b4d6e83"
}

Step 3: a checkout from the cart of step 2. Its lines, buyer, context and codes come from the cart.

JSONCheckout vytvorený z košíka (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"
}

The answer to CheckoutFromCartRequest. The checkout has its own lines (new line ids), the cart’s buyer, context and codes; JESEN10 is applied again and LETO5 warned about again. No address yet, so both shipping options are offered (no country filter) and none is selected: two recoverable errors keep it incomplete, so there is no payment_required. expires_at = now + 24 h.

  • Odpoveď je 201. Tento checkout je incomplete, lebo adresa ani spôsob dopravy ešte nie sú známe; checkout, ktorému nič nechýba, je requires_escalation hneď od začiatku. Checkout má messages vždy a jeho chyby recoverable hovoria, čo ešte chýba.
  • Checkout má vlastné položky s novými identifikátormi.
  • fulfillment.methods[0].groups[0].options uvádza spôsoby dopravy obchodu s poplatkami. Kým adresa nie je známa, nefiltrujú sa podľa krajiny.
  • Checkout žije 24 hodín od posledného zápisu.
  • Košík má najviac jeden otvorený checkout. Na opätovné poslanie toho istého cart_id obchod odpovie týmto checkoutom s HTTP 200 a ignoruje fulfillment aj discounts opakovanej požiadavky; checkout meňte cez PUT.

Krok 6: kupujúci a doprava

Checkout prejde do requires_escalation, len čo žiadna správa nie je error so severity: "recoverable". Tieto správy hovoria, čo treba poslať:

codepathČo poslať
missing$.buyer.emailbuyer.email. Adresa, ktorá nie je platným e-mailom, sa hlási ako chýbajúca.
missing$.fulfillment.methods[0].destinationsadresu doručenia
invalid_address$.fulfillment.methods[0].destinations[0]adresu s street_address, address_locality, postal_code a address_country
address_undeliverable$.fulfillment.methods[0].destinations[0]inú adresu: žiadny spôsob dopravy obchodu tieto položky do tejto krajiny nedoručí
missing$.fulfillment.methods[0].groups[0].selected_option_idjedno z ponúknutých options[].id
invalid_option$.fulfillment.methods[0].groups[0].selected_option_idmožnosť z aktuálneho zoznamu
missing$.line_itemsaspoň jednu položku
item_unavailable, out_of_stock, vat_mix_not_allowedžiadna alebo $.line_items[n]iný tovar alebo line_items bez tejto položky. Nesie ich len odpoveď na požiadavku, ktorá položku poslala: vynechaná položka nemá path, odmietnutá zmena ukazuje na položku, ktorú si checkout ponechal.

Meno a telefón kupujúceho nie sú povinné.

KEY=$(uuidgen)
jq -n --arg option "$OPTION" '{
  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: $option}]
  }]},
  discounts: {codes: ["JESEN10"]}
}' | curl -sS -o checkout.json -w 'HTTP %{http_code}\n' -X PUT "$API/checkout-sessions/$CHECKOUT" \
  -H "UCP-Agent: $AGENT" \
  -H 'Content-Type: application/json' \
  -H 'Accept-Language: sk' \
  -H "Idempotency-Key: $KEY" \
  --data-binary @-
jq '{status, totals, messages}' checkout.json
JSONKupujúci, adresa, spôsob dopravy a jeden kód
{
  "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"
    ]
  }
}

Step 4. buyer replaces the stored buyer, so the e-mail is sent again with the new fields. GLS is selected with an option id from step 3. Sending only JESEN10 drops LETO5 and its warning.

JSONPripravené: kupujúci musí pokračovať v obchode
{
  "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"
}

The answer to CheckoutUpdateRequest (step 4), and to a GET before the buyer has ordered. Nothing the platform can provide is missing: status requires_escalation, payment_required (requires_buyer_input) and continue_url. GLS 4.90 € is the fulfillment total: 718.80 − 58.44 + 4.90 = 665.26 €. The destination id came back as destination. Give continue_url to the buyer now.

Ako sa polia správajú:

  • buyer nahradí uloženého kupujúceho ako celok. Posielajte zakaždým všetky polia: buyer len s menom zmaže uložený e-mail.
  • fulfillment. Obchod číta prvý spôsob s type: "shipping" (alebo bez type). Z jeho destinations vezme tú, ktorú uvádza selected_destination_id, inak prvú. Checkout má jednu adresu; vráti sa s identifikátorom "destination", nech ste jej dali akýkoľvek. Adresu možno nahradiť, nie odstrániť. address_country posielajte ako kód ISO 3166-1 alpha-2, napríklad SK (rozumie aj kódom alpha-3 a názvom krajín); krajinu, ktorú obchod nerozpozná, odmietne s 400.
  • groups[0].selected_option_id vyberie spôsob dopravy; null výber zruší.
  • Ponuka dopravy závisí od adresy. Keď je adresa uložená, ponúknu sa len spôsoby, ktoré do jej krajiny doručujú. Spôsob vybraný pred zadaním adresy môže vypadnúť, čo sa prejaví ako invalid_option so selected_option_id: null. Bezpečné poradie je poslať najprv adresu a potom vybrať z možností v tej odpovedi. Príklad robí oboje jednou požiadavkou, čo funguje, keď vybraný spôsob do krajiny adresy doručuje.
  • Každé vytvorenie a PUT nastaví expires_at na 24 hodín odteraz, kým kupujúci neotvorí continue_url.

Checkout v stave requires_escalation nesie vždy payment_required (requires_buyer_input): kupujúci platí v obchode. Ak vybraný spôsob doručuje na výdajné miesto, pickup_point_required (tiež requires_buyer_input) oznamuje, že výdajné miesto si kupujúci vyberie v obchode. Ani jedna správa nebráni ďalšiemu kroku.

Krok 7: odovzdanie kupujúcemu

Ukážte kupujúcemu zhrnutie — položky, sumy, dopravu a odkazy obchodu z links (obchodné podmienky a ochranu osobných údajov, ak ich obchod má nastavené) — a continue_url mu dajte ako odkaz alebo tlačidlo, ktoré otvorí vo vlastnom prehliadači. Každú položku pomenujte podľa vlastného záznamu o variante, ktorý ste vybrali (jeho title, napríklad Modrá / M, uložený podľa item.id): item.title položky je iba názov produktu. Odkaz dajte až teraz, pri requires_escalation: keď prvýkrát vloží položky do košíka kupujúceho, checkout sa zmrazí taký, aký je.

jq -r 'if .status == "requires_escalation" then .continue_url else "not ready: \(.status)" end' checkout.json

Keď kupujúci odkaz otvorí, krátka stránka na doméne obchodu pridá položky checkoutu a uplatnený zľavový kód do košíka, ktorý má tento prehliadač v obchode, a otvorí pokladňu obchodu. Tovar, ktorý už v košíku bol, v ňom zostane. Ak je kupujúci v tej istej karte prehliadača prihlásený do obchodu, položky pôjdu do jeho zákazníckeho košíka. Ak už ten košík obsahuje tovar v inej mene, položky sa pridajú v tej mene, ak v nej obchod predáva; inak sa nepridá nič a kupujúci skončí v košíku obchodu. Potom zadá v obchode e-mail, adresu, dopravu a platbu a objednávku odošle.

  • Odkaz patrí do prehliadača kupujúceho. Nikdy ho neotvárajte zo svojho servera. Samotné otvorenie len zobrazí stránku; položky sa presunú, až keď sa v prehliadači spustí jej skript, ktorý potrebuje JavaScript a úložisko prehliadača obchodu. Položky skončia v košíku toho prehliadača, ktorý odkaz otvoril: prehliadač vnútri aplikácie má vlastný košík, preto nech kupujúci nákup dokončí v ňom, alebo odkaz otvorí v systémovom prehliadači.
  • S odkazom zaobchádzajte ako s tajomstvom. Nie je jednorazový: každý prehliadač, ktorý ho otvorí, dostane položky raz do vlastného košíka a objednávka z takého košíka môže váš checkout dokončiť. Pošlite ho len kupujúcemu.
  • Funguje, kým je checkout otvorený, do expires_at. Potom odpovie HTTP 410 stránkou, ktorá len odkazuje na úvodnú stránku obchodu.
  • Keď ho kupujúci otvoril, checkout je zmrazený. PUT vráti posledný stav s varovaním handed_off a nič nezmení; GET vyzerá presne ako predtým. Ak chce kupujúci iný nákup, vytvorte nový checkout.

Krok 8: čakanie na objednávku

Webhooky neexistujú: checkout čítajte, kým nie je completed alebo canceled, a začnite hneď, ako kupujúcemu dáte odkaz.

for wait in 5 10 20 40 60 60 60; do
  sleep "$wait"
  curl -sS -o checkout.json "$API/checkout-sessions/$CHECKOUT" -H "UCP-Agent: $AGENT"
  STATUS=$(jq -r .status checkout.json)
  echo "$STATUS"
  [ "$STATUS" = completed ] || [ "$STATUS" = canceled ] && break
done
jq '.order' checkout.json
JSONDokončené objednávkou kupujúceho
{
  "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"
  }
}

GET after the buyer opened continue_url and placed order SK2600417 on the shop (step 6). The body is the last answer the platform received (step 4) without continue_url, with status: "completed", messages: [] and order appended. It does not reflect what the buyer changed on the shop. “Completed” means placed, not paid.

  • Jedna požiadavka naraz na jeden checkout. Čítanie checkoutu čaká za každou inou požiadavkou naň a keď je checkout zaneprázdnený, môže odpovedať 503 s Retry-After: 1. Záplava čítaní môže zdržať aj odovzdanie kupujúcemu.
  • Interval. Každých 5–10 sekúnd v prvých minútach po odovzdaní odkazu a potom raz za minútu úplne stačí. Čítania sú obmedzené na 600 za minútu na IP adresu a operáciu, spolu pre všetky obchody a relácie, ktoré z tej IP voláte; plánujte s polovicou (pozri Limity požiadaviek).
  • Termín. expires_at predlžujú len vaše zápisy a keď kupujúci odkaz otvoril, už ani tie. Prvé čítanie po expires_at zruší checkout, ktorý žiadna objednávka nedokončila, takže jedno čítanie po termíne dá konečnú odpoveď.
  • completed znamená odoslaná, nie zaplatená. order.id je identifikátor objednávky v obchode, order.label jej číslo (chýba, ak ho obchod nemá) a order.permalink_url stránka potvrdenia obchodu. Tá sa zostaví z adresy a stav objednávky neukazuje.
  • Telo je vaša posledná ponuka, nie objednávka. line_items a totals sú to, čo checkout obsahoval, keď kupujúci odišiel; čo zmenil v obchode, sa v ňom neprejaví.

Niektoré odoslané objednávky checkout nedokončia: keď sa kupujúci po odovzdaní ako hosť prihlási až v pokladni obchodu (objednávka potom vznikne z iného košíka), keď pred objednaním odstráni všetky položky vášho checkoutu, alebo keď bol checkout predtým zrušený (vami alebo po expires_at najbližšou požiadavkou, ktorá ho čítala). Dokončenie navyše nie je zaručené: ak na strane Monokaida zlyhá, chyba sa iba zapíše do logu. Váš checkout potom skončí ako canceled. Netvrďte kupujúcemu, že objednávka neexistuje; rozhodujúce je potvrdenie od obchodu.

Kompletné skripty

Dva skripty nižšie prejdú celý postup v jednom obchode: objavenie, vyhľadávanie, ktoré pokračuje po kurzore, košík a jeho úpravu, checkout, kupujúceho a adresu, prvý ponúknutý spôsob dopravy, continue_url a čakanie na objednávku. Ukazujú ošetrenie chýb, aké skutočná integrácia potrebuje:

  • Chyby protokolu (chybový stav HTTP s code a content) skript zastavia, okrem tých, ktoré má zmysel zopakovať.
  • Obchodné chyby (HTTP 200 s ucp.status: "error") ho zastavia so správou; ostatné správy sa vypíšu a postup pokračuje.
  • Jeden Idempotency-Key na logický zápis, vytvorený raz a poslaný znova pri každom opakovaní toho zápisu, takže zopakované vytvorenie nikdy nezaloží druhú reláciu.
  • Opakovanie dodržiava Retry-After pri 429 a 503 a pri 409, kým tá istá požiadavka ešte beží. 500, odpovede, ktoré nie sú JSON (vlastné stránky webového servera pri 502, 504 a pri jeho limite), aj sieťové chyby sa opakujú s exponenciálne rastúcim odstupom. 409 bez Retry-After znamená, že kľúč bol použitý pre inú požiadavku, čo je chyba v kóde, a skript skončí.
  • Čakanie predlžuje interval z POLL_SECONDS až na minútu a končí pri completed, canceled, termíne checkoutu alebo po MAX_POLL_MINUTES.
PremennáPredvolená hodnotaVýznam
SHOPhttps://shop.example.compôvod (origin) obchodu
PROFILE_URLhttps://platform.example.com/.well-known/ucpprofil vašej platformy
QUERYmarlinhľadaný text
LANGUAGEskcontext.language a Accept-Language
DISCOUNT_CODEprázdnakód, ktorý sa uplatní v kroku 4
POLL_SECONDS5prvý interval čítania v sekundách
MAX_POLL_MINUTES30po koľkých minútach čakanie skončí

Kupujúci a adresa sú ukážkové údaje: nahraďte ich údajmi svojho kupujúceho.

// ucp-quickstart.mjs — one whole UCP purchase in a Monokaido shop.
// Node 20+, no dependencies.  SHOP=https://shop.example.com node ucp-quickstart.mjs
import { randomUUID } from 'node:crypto';
import { setTimeout as sleep } from 'node:timers/promises';

const env = process.env;
const SHOP = (env.SHOP ?? 'https://shop.example.com').replace(/\/+$/, '');
const PROFILE_URL = env.PROFILE_URL ?? 'https://platform.example.com/.well-known/ucp';
const QUERY = env.QUERY ?? 'marlin';
const LANGUAGE = env.LANGUAGE ?? 'sk';
const DISCOUNT_CODE = env.DISCOUNT_CODE ?? '';
const POLL_SECONDS = Number(env.POLL_SECONDS ?? 5);
const MAX_POLL_MINUTES = Number(env.MAX_POLL_MINUTES ?? 30);
const BUYER = { first_name: 'Jana', last_name: 'Nováková', email: 'jana.novakova@example.com', phone_number: '+421 905 123 456' };
const ADDRESS = {
  id: 'home', first_name: 'Jana', last_name: 'Nováková', street_address: 'Hlavná 12',
  address_locality: 'Bratislava', postal_code: '811 01', address_country: 'SK',
};
const RETRYABLE = new Set([429, 500, 502, 503, 504]);

class ProtocolError extends Error {
  constructor(status, body) {
    super(`HTTP ${status} ${body.code ?? ''}: ${body.content ?? ''}`);
    this.status = status;
    this.body = body;
  }
}

// One logical call. A write gets ONE Idempotency-Key that every retry reuses,
// so a retried create can never make a second cart or checkout.
async function call(method, url, body, { write = false } = {}) {
  const key = write ? randomUUID() : undefined;
  for (let attempt = 1; ; attempt++) {
    let res;
    try {
      res = await fetch(url, {
        method,
        headers: {
          'UCP-Agent': `profile="${PROFILE_URL}"`,
          'Accept-Language': LANGUAGE,
          ...(body !== undefined && { 'Content-Type': 'application/json' }),
          ...(key && { 'Idempotency-Key': key }),
        },
        body: body === undefined ? undefined : JSON.stringify(body),
        signal: AbortSignal.timeout(65_000), // the web server gives up after 60 s
      });
    } catch (err) {
      // No answer: the write may or may not have run. The same key makes the retry safe.
      if (attempt >= 5) throw err;
      await sleep(2 ** attempt * 1000);
      continue;
    }
    const json = (res.headers.get('content-type') ?? '').includes('application/json');
    const data = json ? await res.json() : { content: `${res.status} page from the web server` };
    if (res.ok) return data;
    // 409 with Retry-After = "same request still running"; without it, the key was reused for another request.
    const retry = RETRYABLE.has(res.status) || (res.status === 409 && res.headers.has('retry-after'));
    if (!retry || attempt >= 5) throw new ProtocolError(res.status, data);
    const wait = Number(res.headers.get('retry-after')) || 2 ** attempt;
    console.warn(`   ${res.status} ${data.code ?? '(not JSON)'} — retrying in ${wait} s${key ? ' with the same key' : ''}`);
    await sleep(wait * 1000);
  }
}

// HTTP 200 can still mean "nothing came back": ucp.status "error" + messages.
function resource(data, what, { quiet = false } = {}) {
  if (data.ucp?.status === 'error') {
    const m = data.messages?.[0] ?? {};
    throw new Error(`${what}: ${m.code} — ${m.content}`);
  }
  if (!quiet) for (const m of data.messages ?? []) console.log(`   [${m.type}${m.severity ? '/' + m.severity : ''}] ${m.code}: ${m.content}`);
  return data;
}

const money = ({ amount, currency }) => `${(amount / 100).toFixed(2)} ${currency}`; // 2 decimals: EUR, CZK, HUF, PLN
const total = (totals, currency) => money({ amount: totals.find((t) => t.type === 'total').amount, currency });

console.log(`1. Discover ${SHOP}`);
const profile = await call('GET', `${SHOP}/.well-known/ucp`);
const API = profile.ucp.services['dev.ucp.shopping'].find((s) => s.transport === 'rest').endpoint;
if (!profile.ucp.capabilities['dev.ucp.shopping.checkout']) {
  throw new Error('This shop takes no orders over UCP (catalog mode): only search and lookup are offered.');
}

console.log(`2. Search "${QUERY}"`);
let product, variant, cursor;
for (let page = 1; page <= 5 && !variant; page++) {
  const body = { query: QUERY, pagination: { limit: 10, ...(cursor && { cursor }) }, context: { language: LANGUAGE } };
  const result = resource(await call('POST', `${API}/catalog/search`, body), 'search');
  product = result.products.find((p) => p.variants.some((v) => v.availability.available));
  variant = product?.variants.find((v) => v.availability.available);
  if (!result.pagination.has_next_page) break; // a short (even empty) page is normal: keep following the cursor
  cursor = result.pagination.cursor;
}
if (!variant) throw new Error(`Nothing available for "${QUERY}"`);
console.log(`   ${product.title} — ${variant.title}, ${money(variant.price)}`);

console.log('3. Create a cart');
let cart = resource(await call('POST', `${API}/carts`, {
  line_items: [{ item: { id: variant.id }, quantity: 1 }],
  buyer: { email: BUYER.email },
  context: { language: LANGUAGE, address_country: ADDRESS.address_country },
}, { write: true }), 'create cart');
console.log(`   cart ${cart.id}: ${total(cart.totals, cart.currency)}`);

console.log('4. Update the cart (PUT replaces line_items: send every line)');
cart = resource(await call('PUT', `${API}/carts/${cart.id}`, {
  line_items: cart.line_items.map((l) => ({ item: { id: l.item.id }, quantity: l.quantity })),
  ...(DISCOUNT_CODE && { discounts: { codes: [DISCOUNT_CODE] } }),
}, { write: true }), 'update cart');
console.log(`   ${total(cart.totals, cart.currency)}`);

console.log('5. Turn the cart into a checkout');
let checkout = resource(await call('POST', `${API}/checkout-sessions`, { cart_id: cart.id }, { write: true }), 'create checkout');
const put = async (body) =>
  resource(await call('PUT', `${API}/checkout-sessions/${checkout.id}`, body, { write: true }), 'update checkout');

console.log('6. Buyer, address and shipping until requires_escalation');
checkout = await put({ buyer: BUYER, fulfillment: { methods: [{ type: 'shipping', destinations: [ADDRESS], selected_destination_id: 'home' }] } });
for (let round = 1; checkout.status === 'incomplete'; round++) {
  const blocking = checkout.messages.filter((m) => m.type === 'error' && m.severity === 'recoverable');
  const options = checkout.fulfillment?.methods?.[0]?.groups?.[0]?.options ?? [];
  const onlyTheOption = blocking.length > 0 && blocking.every((m) => m.path?.endsWith('.selected_option_id'));
  if (round > 3 || !onlyTheOption || options.length === 0) {
    throw new Error(`The agent cannot finish this checkout: ${blocking.map((m) => `${m.code} (${m.content})`).join('; ')}`);
  }
  const option = options[0]; // the shop's own order; a real agent lets the buyer choose
  console.log(`   shipping: ${option.title}, ${total(option.totals, checkout.currency)}`);
  checkout = await put({ fulfillment: { methods: [{ type: 'shipping', groups: [{ selected_option_id: option.id }] }] } });
}
if (checkout.status !== 'requires_escalation') throw new Error(`Unexpected status ${checkout.status}`);

console.log(`7. Ready: ${total(checkout.totals, checkout.currency)}. Give the buyer this link — they pay on the shop:`);
console.log(`\n   ${checkout.continue_url}\n`);
for (const link of checkout.links) console.log(`   ${link.type}: ${link.url}`);

console.log('8. Waiting for the order (Ctrl+C stops; the checkout stays open until expires_at)');
const deadline = Math.min(Date.parse(checkout.expires_at) + 60_000, Date.now() + MAX_POLL_MINUTES * 60_000);
for (let wait = POLL_SECONDS; ; wait = Math.min(wait * 1.5, 60)) {
  await sleep(wait * 1000);
  // One request at a time per session: a GET of a checkout queues behind every other request on it.
  const now = resource(await call('GET', `${API}/checkout-sessions/${checkout.id}`), 'poll', { quiet: true });
  if (now.status === 'completed') {
    console.log(`   Order ${now.order.label ?? now.order.id} placed (placed, not necessarily paid): ${now.order.permalink_url}`);
    break;
  }
  if (now.status === 'canceled') { console.log('   The checkout was canceled or expired without an order.'); break; }
  if (Date.now() > deadline) { console.log(`   Still ${now.status}; stopped polling.`); break; }
}

Spustíte ich takto:

SHOP=https://shop.example.com PROFILE_URL=https://platform.example.com/.well-known/ucp node ucp-quickstart.mjs
SHOP=https://shop.example.com PROFILE_URL=https://platform.example.com/.well-known/ucp python3 ucp_quickstart.py

Čo sa môže pokaziť

Čo vidítePrečoČo urobiť
404 not_found, „No UCP business on this host“Hostiteľ UCP neposkytuje: nie je vlastnou doménou obchodu s overeným DNS a aktívnym certifikátom, UCP je vypnuté, vstavaný obchod je vypnutý, kľúč obchodu je neaktívny alebo mu vypršala platnosť, alebo modul E-shop nie je aktívny. Odpoveď nikdy nepovie, ktorý dôvod platí.Overte doménu s obchodníkom a volajte hostiteľa z endpoint v profile.
400 invalid_profile_urlUCP-Agent chýba alebo má zlý tvar.Pošlite profile="https://…" s úvodzovkami a profile malými písmenami.
424 profile_unreachableObchod si váš profil nestiahol: odpoveď nebola 200, bola presmerovaním, prišla príliš pomaly, mala viac ako 256 KiB alebo adresa je súkromná.Opravte hosting a počkajte 60 sekúnd: toľko si obchod zlyhanie pamätá.
422 profile_malformed alebo version_unsupportedProfil nie je profilom UCP alebo jeho ucp.version nie je 2026-04-08.Zverejnite profil uvedený vyššie.
200 s capabilities_incompatibleVáš profil schopnosť neuvádza alebo je obchod v katalógovom režime.Doplňte schopnosť do profilu; pri obchode v katalógovom režime skončite po vyhľadávaní.
200 s not_found pri košíku alebo checkouteIdentifikátor je neznámy, patrí inému obchodu alebo košík vypršal či bol zrušený.Začnite znova od kroku 3.
400 invalid_requestPole má zlý tvar; content ho pomenuje, napríklad line_items must be an array pri PUT košíka bez položiek. „Idempotency-Key is not valid“ znamená, že kľúč nemá 1 až 255 viditeľných znakov ASCII.Opravte požiadavku: nevykonala sa.
409 idempotency_conflict bez Retry-AfterKľúč bol použitý pre inú požiadavku: inú operáciu, iný identifikátor v ceste alebo iné telo.Pre každý logický zápis použite nový kľúč.
409 idempotency_conflict s Retry-After: 2Prvá požiadavka s týmto kľúčom ešte beží.Počkajte a pošlite tú istú požiadavku s tým istým kľúčom.
503 „Another request on this session is being processed“Košík alebo checkout drží iná požiadavka.Zopakujte po Retry-After (1 s) s tým istým kľúčom; na jednu reláciu posielajte jednu požiadavku naraz.
503 „This shop is temporarily unavailable“Obchod je pozastavený alebo sa ho nepodarilo nájsť.Zopakujte po Retry-After (60 s).
503 „Too many new platform profiles from this client“ alebo „Platform profile discovery is busy“Obchod sťahuje priveľa profilov, ktoré nemá uložené: z vašej IP viac ako 5 nových za minútu alebo priveľa celkovo.Zopakujte po Retry-After; používajte jednu stálu adresu profilu.
429 rate_limited (JSON)Limit operácie na IP adresu je vyčerpaný: 600 čítaní, 300 zápisov za minútu.Počkajte Retry-After (60 s).
429, 413, 502 alebo 504 ako stránka HTMLWebový server pred obchodom: viac ako 100 požiadaviek za sekundu, telo nad 1 MiB alebo API nedostupné či pomalšie ako 60 s.Spomaľte a zopakujte; pri 502 a 504 opakujte zápisy s tým istým kľúčom. Telá držte malé.
Checkout zostáva incompleteZostala chyba recoverable.Prečítajte messages a pošlite, čo uvádza path (krok 6).
handed_offKupujúci už nákup preniesol do obchodu.Prestaňte zapisovať a čakajte; pre iný nákup vytvorte nový checkout.
Checkout nikdy nie je completedKupujúci neobjednal, prihlásil sa v pokladni obchodu, odstránil položky alebo bol checkout predtým zrušený; zriedka zlyhalo samotné dokončenie (nie je zaručené).Skončite pri termíne; rozhodujúce je potvrdenie od obchodu.

Každý kód aj s tým, čo má klient urobiť, nájdete v kapitole Chyby a správy. Opakovanie požiadaviek, súbežné požiadavky, limity a životnosť relácií sú v kapitole Idempotencia, súbežnosť a limity.