# Štítky dopravcu

Služba štítkov kupuje prepravné štítky od dopravcov pripojených k účtu (napríklad UPS a Canada Post) a každý štítok uchováva ako objednávku. Integrácia načíta spôsoby odoslania účtu, ocení balík, vytvorí objednávku štítku, kúpi štítok pri zvolenej službe dopravcu, vytlačí PDF, sleduje balík a ruší nepoužité štítky. Je určená pre internetové obchody, skladové systémy a systémy na správu objednávok, ktoré odosielajú balíky cez dopravcov, a nie cez vlastných vodičov.

## 1. Čo môžete vytvoriť

Príklady v tejto príručke sledujú jednu firmu: **Northbound Outfitters**, internetový obchod s outdoorovým vybavením, ktorý odosiela zo svojho skladu na adrese 1200 Eglinton Ave E, Toronto. Jeho účet má spôsob odoslania Canada Post a spôsob odoslania UPS. Typickou objednávkou je krabica so stanom s hmotnosťou 4,2 kg a rozmermi 60 × 30 × 25 cm smerujúca do Calgary; objednávky do Spojených štátov idú cez UPS.

- **Výber dopravcu v pokladni.** Obchod ocení košík zákazníka u Canada Post, zobrazí služby s cenou a dňami prepravy a odošle službou, ktorú zákazník zaplatil.
- **Tlač štítku jedným kliknutím v sklade.** Baliace pracovisko vytvorí objednávku štítku po zabalení krabice, kúpi štítok pri zvolenej službe a vytlačí PDF dopravcu na termálnej tlačiarni.
- **Cezhraničné zásielky s colnými údajmi.** Objednávky do Spojených štátov nesú položky (popis, množstvo, hodnota, kód HS), aby bol štítok UPS vystavený s obchodnými údajmi.
- **Automatické aktualizácie stavu pre zákazníka.** Obchod uloží sledovacie číslo dopravcu, na stránke objednávky zobrazí časovú os verejného sledovania a aktualizuje objednávku, keď webhook `tracking.event` oznámi doručenie balíka.

## 2. Čo táto príručka pokrýva

Táto príručka pokrýva službu štítkov v1 (`/api/v1/labelservice/...`): jeden spôsob odoslania (jeden účet dopravcu) na volanie. Použite ju, keď integrácia už vie, ktorým spôsobom odoslania posiela, alebo keď udržiava existujúcu integráciu služby štítkov.

Pre nové integrácie je odporúčaným jediným vstupným bodom Uniorder (`/api/v1/uniorder/...`). Príručky Uniorder, „Uniorder: jedno API pre každú zásielku“ a „Cenová ponuka a objednávka v jednom toku“, ocenia všetky služby dopravcov na účte naraz (spolu s vlastným doručením firmy, ak sa uplatní) a kúpia štítok pri službe zvolenej odoslaním jej `rate_id`. Rovnaké volania potom tlačia, sledujú a rušia každú objednávku.

Ostatné druhy zásielok pokrývajú iné príručky:

- Doručenie vlastnými vodičmi firmy: „Vyzdvihnutie a doručenie (vlastná flotila)“.
- Zákaznícky účet, ktorý odosiela cez služby ponúkané jeho firmou: „Prepravné služby“.
- Tovar uložený v sklade a odosielaný na požiadanie: „Skladovanie a výdaj“.

## 3. Skôr než začnete

- **Účet.** Použite firemný (klientsky) účet, zamestnanca tohto účtu alebo zákaznícky účet firmy. Firemný účet vidí vlastné spôsoby odoslania. Zákaznícky účet vidí iba spôsoby, ktoré mu firma priradila, a každý kúpený štítok sa účtuje z jeho zostatku; ak firma pre tohto zákazníka zapla nastavenie „Automaticky pozastaviť službu štítkov“, štítok sa odmietne, kým zostatok spolu s úverom nepokrýva jeho cenu.
- **Oprávnenie na API.** Účet musí mať zapnutý prístup k API. Bez neho každé volanie služby štítkov vráti `401` s `Unauthorized`.
- **Spôsoby odoslania.** Na účte musí byť aktívny aspoň jeden spôsob odoslania (pri zákazníkoch: priradený zákazníkovi). Identifikátory spôsobov sa líšia podľa účtu a nesmú byť napevno v kóde; načítajte ich v kroku 5.
- **Testovacie údaje.** Použite testovací spôsob odoslania alebo sandbox dopravcu, ak je nastavený (sadzby potom nesú `test_mode: true`), a cieľovú adresu, ktorú máte pod kontrolou. Každý testovací štítok kúpený na produkčnom spôsobe zrušte.
- **Zaobchádzanie s tokenom.** Prihlasujte sa zo svojho servera a token uchovávajte tam. Token ani heslo neumiestňujte do prehliadača ani do mobilnej aplikácie.
- **Zástupné hodnoty.** Nahraďte `YOUR_HOST` hostiteľom vašej platformy a `ACCESS_TOKEN` tokenom z kroku 4. Hodnoty `shipping_method` nahraďte id vášho účtu.

## 4. Prihlásenie

Každé volanie služby štítkov vyžaduje token typu bearer. Integrácia sa prihlási raz, uloží `access_token` a `expires_at` na serveri a pred vypršaním tokenu sa prihlási znova.

**REST:** `POST /api/v1/user/login` — [Príručka REST](/api/documentation#/paths/v1-user-login/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/user/login \
  -H "Content-Type: application/json" \
  -d '{"email":"shipping@northbound-outfitters.ca","password":"your_password"}'
```

```json
{
  "result": true,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
  "token_type": "Bearer",
  "expires_at": "2027-09-28 10:15:00"
}
```

- `access_token`: posielajte ho pri každom ďalšom volaní v hlavičke uvedenej nižšie.
- `expires_at`: pred týmto časom sa prihláste znova.

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL používa rovnakú hlavičku na `POST /api/graphql`.

**GraphQL:** `userLogin` ([Príručka GraphQL](/api/graphql/documentation#/user/userLogin))

**Overenie:** prihlásenie vráti `access_token`. Následujúce požiadavky bez tohto tokenu vrátia `401`.

## 5. Zoznam spôsobov odoslania

Zoznam spôsobov informuje integráciu, cez ktoré účty dopravcov môže odosielať a aké možnosti každý z nich prijíma. Uložte `id` každého spôsobu, ktorý používate; je to `shipping_method` každého ďalšieho volania.

**REST:** `POST /api/v1/labelservice/getShippingMethodList` — [Príručka REST](/api/documentation#/paths/v1-labelservice-getShippingMethodList/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingMethodList \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"detail": true}'
```

```json
[
  {
    "id": 59,
    "name": "Canada Post",
    "unique_identifier": "CPC-TOR",
    "options": {
      "signature_option": true,
      "insurance_option": true,
      "insurance_value": true,
      "multi_package": true
    },
    "package_type": {
      "parcel": {
        "name": "Parcel",
        "options": { "weight_options": true, "dimension_options": true }
      }
    },
    "from_contry_limit": ["CA"],
    "isUploadMethod": false
  },
  {
    "id": 61,
    "name": "UPS",
    "unique_identifier": "UPS-TOR",
    "options": {
      "signature_option": true,
      "insurance_option": true,
      "insurance_value": true,
      "multi_package": true
    }
  }
]
```

Každý riadok obsahuje:

| Pole | Použitie |
|---|---|
| `id` | `shipping_method` v každom ďalšom volaní |
| `name` | Zobrazovaný názov |
| `unique_identifier` | Stabilný kód |
| `options.signature_option` | Podpis je dostupný |
| `options.insurance_option` | Poistenie je dostupné |
| `options.multi_package` | Viac ako jeden kus |
| `package_type` | Prijímané kódy `package_type` a informácia, či každý vyžaduje hmotnosť a rozmery |
| `from_contry_limit` | Krajiny, v ktorých môže byť adresa odosielateľa |
| `services` | Dopravcovia a služby za spôsobom odoslania; kódy môžu obmedziť cenovú ponuku cez `carriers` / `services` |

Pošlite `"id": 59` na načítanie iba jedného spôsobu alebo `"detail": false` na získanie iba `id`, `name` a `unique_identifier`.

**GraphQL:** `labelserviceGetShippingMethodList` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingMethodList)) (JSON skalár).

**Overenie:** zoznam nie je prázdny. Vybrali ste jedno `id` a viete, či tento spôsob povoľuje podpis, poistenie a viac balíkov. Prázdny zoznam znamená, že na účte nie je zapnutý žiadny spôsob.

## 6. Cenová ponuka

Cenová ponuka si vyžiada ceny od dopravcu bez toho, aby niečo vytvorila: dočasná objednávka použitá pre požiadavku sa odstráni a nič sa neúčtuje. Northbound Outfitters ju volá v pokladni, aby zobrazil služby Canada Post pre košík. Telo má rovnaký tvar ako v kroku 7. `shipping_method` je povinné.

**REST:** `POST /api/v1/labelservice/rate` — [Príručka REST](/api/documentation#/paths/v1-labelservice-rate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/rate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "shipping_method": 59,
    "name": "Emily Tremblay",
    "telephone": "4035550182",
    "email": "emily.tremblay@example.com",
    "address_1": "1415 17 Ave SW",
    "city": "Calgary",
    "province": "AB",
    "postcode": "T2T0C8",
    "country": "CA",
    "weight": 4.2,
    "length": 60,
    "width": 30,
    "height": 25,
    "dimension_unit": 2,
    "weight_unit": 2,
    "signature_option": 0,
    "insurance_option": 0,
    "package_type": "parcel",
    "ref": "NB-10482",
    "sender_name": "Northbound Outfitters",
    "sender_telephone": "4165550140",
    "sender_address_1": "1200 Eglinton Ave E",
    "sender_city": "Toronto",
    "sender_province": "ON",
    "sender_postcode": "M3C1H9",
    "sender_country": "CA"
  }'
```

```json
{
  "result": true,
  "shipping_price": "24.86",
  "price_details": { "shipping_fee": "24.86" },
  "rates": [
    {
      "carrier_name": "canadapost",
      "currency": "CAD",
      "service_code": "canadapost_expedited_parcel",
      "service_name": "CANADAPOST EXPEDITED PARCEL",
      "price": 24.86,
      "transit_days": 3,
      "test_mode": false
    },
    {
      "carrier_name": "canadapost",
      "currency": "CAD",
      "service_code": "canadapost_xpresspost",
      "service_name": "CANADAPOST XPRESSPOST",
      "price": 38.12,
      "transit_days": 2,
      "test_mode": false
    }
  ],
  "best_rate": {
    "service_code": "canadapost_expedited_parcel",
    "price": 24.86,
    "transit_days": 3
  }
}
```

- `rates[]`: jedna položka na službu dopravcu s `price`, `currency`, `transit_days` a `price_detail` (základný poplatok, palivový príplatok, dane). Tieto údaje zobrazte zákazníkovi.
- `best_rate` / `shipping_price`: prvá sadzba vrátená spôsobom odoslania.
- Cenová ponuka nenesie `rate_id` ani `id` objednávky. Štítky sa kupujú zo sadzieb objednávky vytvorenej v kroku 7.

`weight_unit`: `1` g, `2` kg, `3` oz, `4` lb. `dimension_unit`: `1` mm, `2` cm, `3` m, `4` in.

Pre viac ako jeden kus pošlite `packages: [{ ref, weight, length, width, height, weight_unit, dimension_unit }]`. `shipping_from` (id z adresára) alebo `shipping_from_code` môže nahradiť blok `sender_*`. `carriers` a `services` obmedzia cenovú ponuku na uvedené kódy.

**GraphQL:** `labelserviceRate` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceRate)).

**Overenie:** `result` je true a máte cenu (a dni prepravy, ak ich dopravca posiela). Ak sadzba nie je, opravte cieľ / balík / spôsob **pred** vytvorením.

## 7. Vytvorenie objednávky štítku

Toto volanie vytvorí objednávku štítku a vyžiada si od dopravcu sadzby pre túto zásielku. Vráti `id` objednávky a jedno `rate_id` na službu. Štítok v tomto okamihu ešte nie je kúpený a nič sa neúčtuje; kúpi ho krok 8. Northbound Outfitters toto volanie používa po zabalení krabice a `id` uloží k svojej objednávke `NB-10482`.

**REST:** `POST /api/v1/labelservice/submitOrder` — [Príručka REST](/api/documentation#/paths/v1-labelservice-submitOrder/post)

Rovnaké telo ako v kroku 6. Pošlite `Idempotency-Key`: opakovanie s rovnakým kľúčom a rovnakým telom vráti prvú odpoveď namiesto vytvorenia druhej objednávky.

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/submitOrder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nb-10482-label" \
  -d '{
    "shipping_method": 59,
    "name": "Emily Tremblay",
    "telephone": "4035550182",
    "email": "emily.tremblay@example.com",
    "address_1": "1415 17 Ave SW",
    "city": "Calgary",
    "province": "AB",
    "postcode": "T2T0C8",
    "country": "CA",
    "weight": 4.2,
    "length": 60,
    "width": 30,
    "height": 25,
    "dimension_unit": 2,
    "weight_unit": 2,
    "signature_option": 0,
    "insurance_option": 0,
    "package_type": "parcel",
    "ref": "NB-10482",
    "sender_name": "Northbound Outfitters",
    "sender_telephone": "4165550140",
    "sender_address_1": "1200 Eglinton Ave E",
    "sender_city": "Toronto",
    "sender_province": "ON",
    "sender_postcode": "M3C1H9",
    "sender_country": "CA"
  }'
```

```json
{
  "result": true,
  "id": 128455,
  "shipping_price": "24.86",
  "price_details": { "shipping_fee": "24.86" },
  "rates": [
    {
      "rate_id": "rat_5f0c2a9e41d84b7c9a3e6d21b8f47c10",
      "carrier_name": "canadapost",
      "service_code": "canadapost_expedited_parcel",
      "service_name": "CANADAPOST EXPEDITED PARCEL",
      "price": 24.86,
      "currency": "CAD",
      "transit_days": 3
    },
    {
      "rate_id": "rat_5f0c2a9e41d84b7c9a3e6d21b8f47c11",
      "carrier_name": "canadapost",
      "service_code": "canadapost_xpresspost",
      "service_name": "CANADAPOST XPRESSPOST",
      "price": 38.12,
      "currency": "CAD",
      "transit_days": 2
    }
  ],
  "best_rate": {
    "rate_id": "rat_5f0c2a9e41d84b7c9a3e6d21b8f47c10",
    "service_code": "canadapost_expedited_parcel",
    "price": 24.86
  }
}
```

| Pole | Použitie |
|---|---|
| `id` | Id objednávky Superroute — nákup, stiahnutie a zrušenie |
| `rates[].rate_id` | Služba na nákup v kroku 8; platí iba pre túto objednávku |
| `rates[].price` | Cena tejto služby |
| `shipping_price` | Cena `best_rate` |

Zásielka do Spojených štátov ide cez spôsob odoslania UPS s položkami potrebnými na colné konanie. Tento príklad používa tvar `packages`, ktorý nesie položky pre každú krabicu:

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/submitOrder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nb-10497-label" \
  -d '{
    "shipping_method": 61,
    "name": "Daniel Price",
    "telephone": "2065550117",
    "email": "daniel.price@example.com",
    "address_1": "500 Mercer St",
    "city": "Seattle",
    "province": "WA",
    "postcode": "98109",
    "country": "US",
    "package_type": "parcel",
    "paid_by": 1,
    "ref": "NB-10497",
    "sender_name": "Northbound Outfitters",
    "sender_telephone": "4165550140",
    "sender_address_1": "1200 Eglinton Ave E",
    "sender_city": "Toronto",
    "sender_province": "ON",
    "sender_postcode": "M3C1H9",
    "sender_country": "CA",
    "packages": [
      {
        "ref": "NB-10497-1",
        "weight": 2.6,
        "length": 45,
        "width": 30,
        "height": 20,
        "weight_unit": 2,
        "dimension_unit": 2,
        "items": [
          {
            "name": "Down sleeping bag",
            "description": "Down-filled sleeping bag, -7 C rating",
            "quantity": 1,
            "unit_price": 289.00,
            "currency": "CAD",
            "weight": 1.6,
            "hscode": "9404400000",
            "sku": "NB-SB-7C",
            "unit": "PCS"
          },
          {
            "name": "Camp stove",
            "description": "Canister camp stove",
            "quantity": 1,
            "unit_price": 79.00,
            "currency": "CAD",
            "weight": 1.0,
            "hscode": "7321111000",
            "sku": "NB-ST-01",
            "unit": "PCS"
          }
        ]
      }
    ]
  }'
```

**GraphQL:** `labelserviceSubmitOrder` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceSubmitOrder)). Telo REST sa vkladá do `input`:

```graphql
mutation {
  labelserviceSubmitOrder(input: {
    shipping_method: 59
    name: "Emily Tremblay"
    telephone: "4035550182"
    address_1: "1415 17 Ave SW"
    city: "Calgary"
    province: "AB"
    postcode: "T2T0C8"
    country: "CA"
    weight: 4.2
    length: 60
    width: 30
    height: 25
    dimension_unit: 2
    weight_unit: 2
    package_type: "parcel"
    ref: "NB-10482"
    sender_name: "Northbound Outfitters"
    sender_telephone: "4165550140"
    sender_address_1: "1200 Eglinton Ave E"
    sender_city: "Toronto"
    sender_province: "ON"
    sender_postcode: "M3C1H9"
    sender_country: "CA"
  })
}
```

**Overenie:** odpoveď obsahuje `id` a aspoň jedno `rates[].rate_id`. Uložte oboje. Rovnaký `Idempotency-Key` s rovnakým telom vráti rovnaké `id` a nevytvorí druhú objednávku.

## 8. Nákup štítku a načítanie detailu zásielky

Toto volanie kúpi štítok pri zvolenej službe, zaúčtuje ho a vráti sledovacie čísla dopravcu. Ak je štítok už kúpený, iba načíta detail, takže opakované volanie nikdy nekúpi štítok dvakrát. Northbound Outfitters posiela `rate_id` služby, ktorú zákazník zaplatil.

**REST:** `POST /api/v1/labelservice/getShippingDetail` — [Príručka REST](/api/documentation#/paths/v1-labelservice-getShippingDetail/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingDetail \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "128455",
    "type": "ORDER_ID",
    "rate_id": "rat_5f0c2a9e41d84b7c9a3e6d21b8f47c10"
  }'
```

```json
{
  "id": 128455,
  "shippingPrice": "24.86",
  "mainTrackingNumber": "7023210039414604",
  "trackingNumber": "7023210039414604",
  "needSubmitShippingInformation": false,
  "rate": {
    "carrier_name": "canadapost",
    "price": 24.86,
    "price_detail": [
      { "name": "Base charge", "amount": 18.40 },
      { "name": "Fuel surcharge", "amount": 3.60 },
      { "name": "GST", "amount": 1.10 }
    ],
    "tax_items": ["HST", "GST", "PST", "QST"]
  },
  "labelStatus": "ready",
  "shippingLabel": "JVBERi0xLjQKMS... (base64 encoded)"
}
```

| Pole | Použitie |
|---|---|
| `mainTrackingNumber` | Sledovacie číslo dopravcu pre prvý balík; odovzdajte ho zákazníkovi |
| `trackingNumber` | Sledovacie čísla dopravcu pre všetky balíky, oddelené čiarkami |
| `shippingPrice` | Zaúčtovaná suma |
| `labelStatus` | `ready`: `shippingLabel` obsahuje PDF. `pending`: kúpené a zaúčtované, dopravca ešte súbor nevytvoril; zavolajte znova neskôr. `failed`: načítanie na pozadí sa vzdalo; opätovné volanie ho spustí znova |
| `needSubmitShippingInformation` | `true`, keď tento spôsob vyžaduje odoslanie informácií o zásielke (krok 13) |

`type` môže byť `ORDER_ID` (predvolené), `TRACKING_NUMBER` (číslo balíka Superroute) alebo `THIRD_PARTY_TRACKING_NUMBER` (číslo dopravcu). Pošlite `rate_id`, aby sa štítok kúpil pri zvolenej službe; bez neho spôsob odoslania kúpi štítok za predvolenú sadzbu.

**GraphQL:** `labelserviceGetShippingDetail` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingDetail))

```graphql
mutation {
  labelserviceGetShippingDetail(
    id: "128455"
    type: "ORDER_ID"
    rate_id: "rat_5f0c2a9e41d84b7c9a3e6d21b8f47c10"
  )
}
```

**Overenie:** `mainTrackingNumber` nie je prázdne a `labelStatus` je `ready` (alebo `pending`, ktoré sa pri neskoršom volaní zmení na `ready`). Druhé volanie vráti rovnaké sledovacie číslo a rovnaké `shippingPrice`.

## 9. Stiahnutie PDF

Sklad tlačí štítok dopravcu z tohto volania. Ak štítok ešte nebol kúpený, prvé volanie ho kúpi za predvolenú sadzbu ako v kroku 8; na určenie služby zavolajte najprv krok 8.

**REST:** `POST /api/v1/labelservice/getShippingLabel` — [Príručka REST](/api/documentation#/paths/v1-labelservice-getShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "128455",
    "type": "ORDER_ID",
    "base64": 1
  }'
```

```json
"JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+..."
```

- S `base64: 1` je telo PDF ako jeden reťazec base64; dekódujte ho a pošlite do tlačiarne.
- S `base64: 0` je odpoveďou priamo súbor PDF (`application/pdf`).

`type` môže byť `ORDER_ID` (predvolené), `TRACKING_NUMBER` alebo `THIRD_PARTY_TRACKING_NUMBER` (číslo dopravcu). Ide o **oficiálny štítok dopravcu**. Počet kusov je daný rezerváciou.

**GraphQL:** `labelserviceGetShippingLabel` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingLabel)). GraphQL vždy vracia reťazec base64.

**Overenie:** PDF sa otvorí a ukazuje čiarový kód / sledovacie číslo dopravcu z kroku 8. Vytlačte jednu testovaciu kópiu a potom ju zlikvidujte — testovací štítok neodovzdávajte dopravcovi.

## 10. Sledovanie

Obchod zobrazuje priebeh prepravy balíka na stránke objednávky zákazníka. Verejný endpoint sledovania nevyžaduje token a prijíma číslo dopravcu z kroku 8.

**REST:** `GET /api/v1/tracking/{trackingNumber}` — [Príručka REST](/api/documentation#/paths/v1-tracking-trackingNumber/get)

```bash
curl https://YOUR_HOST/api/v1/tracking/7023210039414604
```

```json
{
  "result": true,
  "is_third_party_tracking": true,
  "deliveried": false,
  "data": [
    {
      "tracking_event_status_id": 430,
      "otep_status": "in_transit",
      "description": "Item in transit",
      "location_city": "Mississauga",
      "updated_at_localized": "2026-09-29 18:42"
    }
  ]
}
```

**GraphQL:** `trackingPublic` ([Príručka GraphQL](/api/graphql/documentation#/tracking/trackingPublic))

```graphql
query {
  trackingPublic(trackingNumber: "7023210039414604") {
    result
    deliveried
    is_third_party_tracking
    data {
      tracking_event_status_id
      otep_status
      description
      updated_at_localized
    }
    third_party_info { tracking_number status carrier_tracking_link }
    proofs { file_id type full_url signed_url }
  }
}
```

- `is_third_party_tracking` je true, keď udalosti pochádzajú od dopravcu.
- `data`: od najnovšej udalosti. Rozhodujte podľa `tracking_event_status_id` / `otep_status`, nie podľa `description`. Prvé udalosti môžu zostať na stave „informácie odoslané“, kým dopravca balík nenaskenuje.
- `deliveried` je true a `500` znamená doručené; `proofs[]` potom môže obsahovať podpis (`type` `1`) alebo fotografiu (`type` `2`).

**Overenie:** vyhľadanie vráti zásielku, ktorú ste práve vytvorili. Neznáme alebo zrušené číslo vráti `404` s `result: false`.

## 11. Konfigurácia oznámení o udalostiach

Webhooky nahrádzajú opakované dopytovanie: server obchodu prijíma každé skenovanie dopravcu a aktualizuje objednávku bez pravidelného volania kroku 10.

| Nastavenie | Udalosť | Kedy |
|---|---|---|
| `tracking_event_webhook_url` | `tracking.event` | Skenovania dopravcu, na ceste k príjemcovi, doručené |
| `order_status_change_webhook_url` | `order.status_change` | Stav vo vašom systéme |
| `order_create_webhook_url` | `order.created` | Bola vytvorená objednávka štítku (krok 7); pre objednávky štítkov sa posiela iba vtedy, keď je `order_created_webhook_all_types` `1` |

**REST:** `PUT /api/v1/webhook-settings` — [Príručka REST](/api/documentation#/paths/v1-webhook-settings/put)

```bash
curl -X PUT https://YOUR_HOST/api/v1/webhook-settings \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_event_webhook_url": "https://shop.northbound-outfitters.ca/hooks/superroute",
    "order_create_webhook_url": "https://shop.northbound-outfitters.ca/hooks/superroute",
    "order_created_webhook_all_types": 1,
    "webhook_sign_secret": "YOUR_LONG_RANDOM_SECRET",
    "webhook_verify_ssl": 1
  }'
```

```json
{
  "result": true,
  "changed_keys": [
    "tracking_event_webhook_url",
    "order_create_webhook_url",
    "order_created_webhook_all_types",
    "webhook_sign_secret",
    "webhook_verify_ssl"
  ],
  "recipient_type": "business",
  "settings": {
    "webhook_sign_secret": "************CRET",
    "tracking_event_webhook_url": "https://shop.northbound-outfitters.ca/hooks/superroute",
    "order_create_webhook_url": "https://shop.northbound-outfitters.ca/hooks/superroute",
    "order_created_webhook_all_types": 1,
    "webhook_verify_ssl": 1
  }
}
```

- Menia sa iba kľúče, ktoré pošlete; neznámy kľúč sa odmietne s `400`.
- `changed_keys` uvádza, čo sa uložilo. Tajomstvo sa vždy vracia zamaskované.

**GraphQL:** `webhookSettingsUpdate` ([Príručka GraphQL](/api/graphql/documentation#/webhooks/webhookSettingsUpdate)).

Každý `tracking.event` nesie `order_id`, `tracking_event_status_id`, `tracking_event_key`, `tracking_number` a `external_tracking_number`; priraďte ho k svojej objednávke podľa `order_id` (`id` z kroku 7).

Overte **v2** nad surovým telom: `HMAC_SHA256(timestamp + "." + raw_body, secret)` voči `X-Webhook-Signature-V2`. Duplicity odstraňujte podľa `X-Webhook-Event-Id`. Odpovedzte **2xx do 3 sekúnd**.

```php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_V2'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, $sharedSecret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
```

`order_cancel_failed_webhook_url` (`order.cancel_failed`) sa pri zrušení štítkov v kroku 12 neposiela; odmietnuté zrušenie štítku sa oznamuje v odpovedi na toto volanie.

**Overenie:** jeden testovací `submitOrder` vyvolá `order.created` s `id` objednávky a prvé skenovanie dopravcu vyvolá `tracking.event`. Neplatný podpis musí príjemca odmietnuť kódom `401`.

## 12. Zrušenie

Štítok, ktorý sa nepoužije na odoslanie, sa zruší, aby ho dopravca nevyúčtoval; poplatok sa vráti na účet. Zrušenie je možné iba dovtedy, kým ho dopravca ešte povoľuje (zvyčajne pred vyzdvihnutím).

**REST:** `POST /api/v1/labelservice/cancelShippingLabel` — [Príručka REST](/api/documentation#/paths/v1-labelservice-cancelShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/cancelShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nb-10482-cancel" \
  -d '{"id": 128455}'
```

```json
{
  "result": true,
  "message": "Shipping Label cancelled successfully"
}
```

- Pošlite práve jedno z `id` (id objednávky) alebo `tracking_number` (sledovacie číslo Superroute alebo dopravcu). Odoslanie oboch vráti `400`.
- `result: true`: dopravca zrušenie prijal a poplatok za štítok bol vrátený.

**GraphQL:** `labelserviceCancelShippingLabel` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceCancelShippingLabel)).

Dopravca, ktorý už balík má, zrušenie odmietne: odpoveďou je `400` s `result: false` a správou dopravcu. Objednávku, ktorej štítok nebol nikdy kúpený, nemožno cez toto volanie zrušiť.

**Overenie:** odpoveď je `result: true` a verejné sledovanie pre toto číslo vráti `404`. Opakovanie s rovnakým `Idempotency-Key` vráti uloženú odpoveď; nová požiadavka na zrušenie tej istej objednávky vráti `400` `This order already cancelled`.

## 13. Odoslanie informácií o zásielke a uzavretie dňa (iba ak to tento spôsob vyžaduje)

Niektorí dopravcovia potrebujú pred vyzdvihnutím odovzdať zásielky dňa (manifest). Krok 8 to pre každú objednávku uvádza v `needSubmitShippingInformation`. Počas dňa zbierajte id týchto objednávok a odošlite ich po poslednom štítku.

**REST:** `POST /api/v1/labelservice/submitShippingInformation` — [Príručka REST](/api/documentation#/paths/v1-labelservice-submitShippingInformation/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/submitShippingInformation \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": [128455, 128461, 128470]}'
```

```json
{
  "result": true,
  "message": "Processed 3 orders. Success: 3, Failed: 0",
  "data": {
    "total_processed": 3,
    "success_count": 3,
    "failure_count": 0,
    "details": [
      { "order_id": 128455, "result": true, "message": "Successful" },
      { "order_id": 128461, "result": true, "message": "Successful" },
      { "order_id": 128470, "result": true, "message": "Successful" }
    ]
  }
}
```

- `details[]`: jeden riadok na objednávku; objednávky s `result: false` odošlite znova po odstránení príčiny uvedenej v `message`.
- `404` `No eligible orders found for shipping information submission`: žiadne z id nemá kúpený štítok, ktorý ešte vyžaduje odoslanie.

**GraphQL:** `labelserviceSubmitShippingInformation` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceSubmitShippingInformation)).

Potom uzavrite deň. Volanie nemá telo a pokrýva všetky objednávky štítkov volajúceho.

**REST:** `POST /api/v1/labelservice/endofday` — [Príručka REST](/api/documentation#/paths/v1-labelservice-endofday/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/endofday \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

```json
{
  "result": true,
  "success": 0,
  "failed": 0,
  "success_ids": [],
  "failed_ids": []
}
```

- `400` so správou `There are orders need to submit shipping information`: niektoré kúpené štítky ešte vyžadujú odoslanie; odošlite ich cez `submitShippingInformation` a zavolajte znova.

**GraphQL:** `labelserviceEndofday` ([Príručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceEndofday)).

Tento krok vynechajte, ak žiadna objednávka dňa nehlásila `needSubmitShippingInformation: true`.

**Overenie:** `submitShippingInformation` hlási `failure_count: 0` a `endofday` odpovie `200`. Najprv to spustite na testovacom spôsobe.

## 14. Spracovanie chýb

Chyby služby štítkov nesú `message`; `code` je prítomný iba tam, kde ho uvádza tabuľka.

| Situácia | Stav HTTP | Kód | Čo integrácia urobí |
|---|---|---|---|
| Chýbajúci alebo expirovaný token, alebo nezapnutý prístup k API | `401` | — (`Unauthorized`) | Prihláste sa znova; ak problém pretrváva, požiadajte firmu o zapnutie prístupu k API |
| `shipping_method` chýba alebo nie je volajúcemu dostupný | `400` | — | Znova načítajte zoznam spôsobov (krok 5) a použite `id` z neho |
| Spôsob odoslania neponúka `package_type` | `400` | — | Použite kľúč z `package_type` z kroku 5 |
| Neplatná adresa alebo balík, alebo dopravca nevráti žiadnu sadzbu | `400` | — (správa dopravcu) | Zobrazte správu, opravte údaje a vyžiadajte cenovú ponuku znova |
| `auto_deduplication` je `1` a `ref` už existuje | `400` | — (`exist_order_ids`) | Namiesto vytvorenia novej objednávky použite existujúcu z `exist_order_ids` |
| Rovnaký `Idempotency-Key` s iným telom | `409` | `IDEMPOTENCY_CONFLICT` | Pre inú požiadavku použite nový kľúč |
| Rovnaký `Idempotency-Key`, kým prvá požiadavka ešte prebieha | `409` | `IDEMPOTENCY_IN_PROGRESS` | Počkajte počet sekúnd z `Retry-After` a zopakujte s rovnakým kľúčom a telom |
| Zostatok zákazníka spolu s úverom nepokrýva štítok | `400` | `INSUFFICIENT_BALANCE` | Dobite zostatok podľa údajov `insufficient_balance` (`shortfall`, `add_funds_url`) a zavolajte krok 8 znova |
| Štítok je kúpený, súbor dopravcu ešte nie je pripravený | `400` pri prvom nákupe, neskôr `200` | `shipment_label_not_ready` | Čakajte, kým je `labelStatus` `pending`; keď je `failed`, zavolajte krok 8 znova |
| Id objednávky alebo číslo nepatrí volajúcemu | `401` | — (`Not Auth`) | Skontrolujte id a `type`; použite účet, ktorý objednávku vytvoril |
| Dopravca zrušenie odmietol alebo je objednávka už zrušená | `400` | — | Štítok považujte za odoslaný (alebo už zrušený); neopakujte |
| Sledovacie číslo je neznáme alebo zrušené | `404` | — | Prestaňte pre toto číslo zobrazovať časovú os |
| `endofday` so zásielkami, ktoré ešte neboli odoslané | `400` | — | Spustite `submitShippingInformation` pre tieto objednávky a potom zavolajte znova |

## Zoznam testov

Použite cieľovú adresu, ktorú máte pod kontrolou, a spôsob odoslania, ktorý možno zrušiť:

- [ ] Zoznam spôsobov nie je prázdny; zaznamenali ste jedno `id`.
- [ ] Cenová ponuka vráti cenu pre tento spôsob a cieľ.
- [ ] Vytvorenie vráti `id` objednávky a `rates[].rate_id`; rovnaký `Idempotency-Key` nevytvorí druhú objednávku.
- [ ] `getShippingDetail` so zvoleným `rate_id` vráti `mainTrackingNumber`; druhé volanie znova neúčtuje.
- [ ] PDF štítku sa otvorí a ukazuje sledovacie číslo dopravcu.
- [ ] Verejné sledovanie nájde zásielku podľa tohto čísla.
- [ ] Príde `tracking.event` (a `order.created`, ak je zapnuté); podpis v2 sa overí.
- [ ] Dopravca prijme cezhraničnú testovaciu zásielku s `items`.
- [ ] Zrušenie uspeje, **alebo** ste overili, že tento spôsob po rezervácii nemožno zrušiť.
- [ ] Ak spôsob vyžaduje uzavretie dňa, testovací beh skončí bez chyby.
