# Štítky dopravce

Služba štítků kupuje přepravní štítky od dopravců připojených k účtu (například UPS a Canada Post) a každý štítek uchovává jako objednávku. Integrace načte způsoby odeslání účtu, ocení balík, vytvoří objednávku štítku, koupí štítek u zvolené služby dopravce, vytiskne PDF, sleduje balík a ruší nepoužité štítky. Je určena pro internetové obchody, skladové systémy a systémy pro správu objednávek, které odesílají balíky přes dopravce, a nikoli přes vlastní řidiče.

## 1. Co můžete vytvořit

Příklady v této příručce sledují jednu firmu: **Northbound Outfitters**, internetový obchod s outdoorovým vybavením, který odesílá ze svého skladu na adrese 1200 Eglinton Ave E, Toronto. Jeho účet má způsob odeslání Canada Post a způsob odeslání UPS. Typickou objednávkou je krabice se stanem o hmotnosti 4,2 kg a rozměrech 60 × 30 × 25 cm směřující do Calgary; objednávky do Spojených států jdou přes UPS.

- **Výběr dopravce v pokladně.** Obchod ocení košík zákazníka u Canada Post, zobrazí služby s cenou a dny přepravy a odešle službou, kterou zákazník zaplatil.
- **Tisk štítku jedním kliknutím ve skladu.** Balicí pracoviště vytvoří objednávku štítku po zabalení krabice, koupí štítek u zvolené služby a vytiskne PDF dopravce na termální tiskárně.
- **Přeshraniční zásilky s celními údaji.** Objednávky do Spojených států nesou položky (popis, množství, hodnota, kód HS), aby byl štítek UPS vystaven s obchodními údaji.
- **Automatické aktualizace stavu pro zákazníka.** Obchod uloží sledovací číslo dopravce, na stránce objednávky zobrazí časovou osu veřejného sledování a aktualizuje objednávku, když webhook `tracking.event` oznámí doručení balíku.

## 2. Co tato příručka pokrývá

Tato příručka pokrývá službu štítků v1 (`/api/v1/labelservice/...`): jeden způsob odeslání (jeden účet dopravce) na volání. Použijte ji, když integrace již ví, kterým způsobem odeslání posílá, nebo když udržuje stávající integraci služby štítků.

Pro nové integrace je doporučeným jediným vstupním bodem Uniorder (`/api/v1/uniorder/...`). Příručky Uniorder, „Uniorder: jedno API pro každou zásilku“ a „Nabídka a objednávka v jednom toku“, ocení všechny služby dopravců na účtu najednou (spolu s vlastním doručením firmy, pokud se uplatní) a koupí štítek u služby zvolené odesláním jejího `rate_id`. Stejná volání pak tisknou, sledují a ruší každou objednávku.

Ostatní druhy zásilek pokrývají jiné příručky:

- Doručení vlastními řidiči firmy: „Vyzvednutí a doručení (vlastní flotila)“.
- Zákaznický účet, který odesílá přes služby nabízené jeho firmou: „Přepravní služby“.
- Zboží uložené ve skladu a odesílané na požádání: „Skladování a výdej“.

## 3. Než začnete

- **Účet.** Použijte firemní (klientský) účet, zaměstnance tohoto účtu nebo zákaznický účet firmy. Firemní účet vidí vlastní způsoby odeslání. Zákaznický účet vidí pouze způsoby, které mu firma přiřadila, a každý koupený štítek se účtuje z jeho zůstatku; pokud firma pro tohoto zákazníka zapnula nastavení „automatické pozastavení služby štítků“, štítek se odmítne, dokud zůstatek spolu s úvěrem nepokrývá jeho cenu.
- **Oprávnění k API.** Účet musí mít zapnutý přístup k API. Bez něj každé volání služby štítků vrátí `401` s `Unauthorized`.
- **Způsoby odeslání.** Na účtu musí být aktivní alespoň jeden způsob odeslání (u zákazníků: přiřazený zákazníkovi). Identifikátory způsobů se liší podle účtu a nesmí být pevně v kódu; načtěte je v kroku 5.
- **Testovací data.** Použijte testovací způsob odeslání nebo sandbox dopravce, pokud je nastaven (sazby pak nesou `test_mode: true`), a cílovou adresu, kterou máte pod kontrolou. Každý testovací štítek koupený na produkčním způsobu zrušte.
- **Zacházení s tokenem.** Přihlašujte se ze svého serveru a token uchovávejte tam. Token ani heslo neumisťujte do prohlížeče ani do mobilní aplikace.
- **Zástupné hodnoty.** Nahraďte `YOUR_HOST` hostitelem vaší platformy a `ACCESS_TOKEN` tokenem z kroku 4. Hodnoty `shipping_method` nahraďte id vašeho účtu.

## 4. Přihlášení

Každé volání služby štítků vyžaduje token typu bearer. Integrace se přihlásí jednou, uloží `access_token` a `expires_at` na serveru a před vypršením tokenu se přihlásí znovu.

**REST:** `POST /api/v1/user/login` — [Pří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`: posílejte ho při každém dalším volání v hlavičce uvedené níže.
- `expires_at`: před tímto časem se přihlaste znovu.

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL používá stejnou hlavičku na `POST /api/graphql`.

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

**Ověření:** přihlášení vrátí `access_token`. Následující požadavky bez tohoto tokenu vrátí `401`.

## 5. Seznam způsobů odeslání

Seznam způsobů informuje integraci, přes které účty dopravců může odesílat a jaké možnosti každý z nich přijímá. Uložte `id` každého způsobu, který používáte; je to `shipping_method` každého dalšího volání.

**REST:** `POST /api/v1/labelservice/getShippingMethodList` — [Pří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ý řádek obsahuje:

| Pole | Použití |
|---|---|
| `id` | `shipping_method` v každém dalším volání |
| `name` | Zobrazovaný název |
| `unique_identifier` | Stabilní kód |
| `options.signature_option` | Podpis je dostupný |
| `options.insurance_option` | Pojištění je dostupné |
| `options.multi_package` | Více než jeden kus |
| `package_type` | Přijímané kódy `package_type` a informace, zda každý vyžaduje hmotnost a rozměry |
| `from_contry_limit` | Země, ve kterých může být adresa odesílatele |
| `services` | Dopravci a služby za způsobem odeslání; kódy mohou omezit cenovou nabídku přes `carriers` / `services` |

Pošlete `"id": 59` pro načtení pouze jednoho způsobu nebo `"detail": false` pro získání pouze `id`, `name` a `unique_identifier`.

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

**Ověření:** seznam není prázdný. Vybrali jste jedno `id` a víte, zda tento způsob povoluje podpis, pojištění a více balíků. Prázdný seznam znamená, že na účtu není zapnutý žádný způsob.

## 6. Cenová nabídka

Cenová nabídka si vyžádá ceny od dopravce, aniž by cokoli vytvořila: dočasná objednávka použitá pro požadavek se odstraní a nic se neúčtuje. Northbound Outfitters ji volá v pokladně, aby zobrazil služby Canada Post pro košík. Tělo má stejný tvar jako v kroku 7. `shipping_method` je povinné.

**REST:** `POST /api/v1/labelservice/rate` — [Pří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 dopravce s `price`, `currency`, `transit_days` a `price_detail` (základní poplatek, palivový příplatek, daně). Tyto údaje zobrazte zákazníkovi.
- `best_rate` / `shipping_price`: první sazba vrácená způsobem odeslání.
- Cenová nabídka nenese `rate_id` ani `id` objednávky. Štítky se kupují ze sazeb objednávky vytvořené v kroku 7.

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

Pro více než jeden kus pošlete `packages: [{ ref, weight, length, width, height, weight_unit, dimension_unit }]`. `shipping_from` (id z adresáře) nebo `shipping_from_code` může nahradit blok `sender_*`. `carriers` a `services` omezí cenovou nabídku na uvedené kódy.

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

**Ověření:** `result` je true a máte cenu (a dny přepravy, pokud je dopravce posílá). Pokud sazba není, opravte cíl / balík / způsob **před** vytvořením.

## 7. Vytvoření objednávky štítku

Toto volání vytvoří objednávku štítku a vyžádá si od dopravce sazby pro tuto zásilku. Vrátí `id` objednávky a jedno `rate_id` na službu. Štítek v tomto okamžiku ještě není koupen a nic se neúčtuje; koupí ho krok 8. Northbound Outfitters toto volání používá po zabalení krabice a `id` uloží ke své objednávce `NB-10482`.

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

Stejné tělo jako v kroku 6. Pošlete `Idempotency-Key`: opakování se stejným klíčem a stejným tělem vrátí první odpověď místo vytvoření druhé 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žití |
|---|---|
| `id` | Id objednávky Superroute — nákup, stažení a zrušení |
| `rates[].rate_id` | Služba k nákupu v kroku 8; platí pouze pro tuto objednávku |
| `rates[].price` | Cena této služby |
| `shipping_price` | Cena `best_rate` |

Zásilka do Spojených států jde přes způsob odeslání UPS s položkami potřebnými pro celní řízení. Tento příklad používá tvar `packages`, který nese položky pro každou krabici:

```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` ([Příručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceSubmitOrder)). Tělo REST se vkládá 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"
  })
}
```

**Ověření:** odpověď obsahuje `id` a alespoň jedno `rates[].rate_id`. Uložte obojí. Stejný `Idempotency-Key` se stejným tělem vrátí stejné `id` a nevytvoří druhou objednávku.

## 8. Nákup štítku a načtení detailu zásilky

Toto volání koupí štítek u zvolené služby, zaúčtuje ho a vrátí sledovací čísla dopravce. Pokud je štítek již koupen, pouze načte detail, takže opakované volání nikdy nekoupí štítek dvakrát. Northbound Outfitters posílá `rate_id` služby, kterou zákazník zaplatil.

**REST:** `POST /api/v1/labelservice/getShippingDetail` — [Pří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žití |
|---|---|
| `mainTrackingNumber` | Sledovací číslo dopravce pro první balík; předejte ho zákazníkovi |
| `trackingNumber` | Sledovací čísla dopravce pro všechny balíky, oddělená čárkami |
| `shippingPrice` | Zaúčtovaná částka |
| `labelStatus` | `ready`: `shippingLabel` obsahuje PDF. `pending`: koupeno a zaúčtováno, dopravce ještě soubor nevytvořil; zavolejte znovu později. `failed`: načítání na pozadí bylo ukončeno; opětovné volání ho spustí znovu |
| `needSubmitShippingInformation` | `true`, když tento způsob vyžaduje odeslání informací o zásilce (krok 13) |

`type` může být `ORDER_ID` (výchozí), `TRACKING_NUMBER` (číslo balíku Superroute) nebo `THIRD_PARTY_TRACKING_NUMBER` (číslo dopravce). Pošlete `rate_id`, aby se štítek koupil u zvolené služby; bez něj způsob odeslání koupí štítek za výchozí sazbu.

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

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

**Ověření:** `mainTrackingNumber` není prázdné a `labelStatus` je `ready` (nebo `pending`, které se při pozdějším volání změní na `ready`). Druhé volání vrátí stejné sledovací číslo a stejné `shippingPrice`.

## 9. Stažení PDF

Sklad tiskne štítek dopravce z tohoto volání. Pokud štítek ještě nebyl koupen, první volání ho koupí za výchozí sazbu jako v kroku 8; pro určení služby zavolejte nejprve krok 8.

**REST:** `POST /api/v1/labelservice/getShippingLabel` — [Pří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 tělo PDF jako jeden řetězec base64; dekódujte ho a pošlete do tiskárny.
- S `base64: 0` je odpovědí přímo soubor PDF (`application/pdf`).

`type` může být `ORDER_ID` (výchozí), `TRACKING_NUMBER` nebo `THIRD_PARTY_TRACKING_NUMBER` (číslo dopravce). Jde o **oficiální štítek dopravce**. Počet kusů je dán rezervací.

**GraphQL:** `labelserviceGetShippingLabel` ([Příručka GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingLabel)). GraphQL vždy vrací řetězec base64.

**Ověření:** PDF se otevře a ukazuje čárový kód / sledovací číslo dopravce z kroku 8. Vytiskněte jednu testovací kopii a poté ji zlikvidujte — testovací štítek nepředávejte dopravci.

## 10. Sledování

Obchod zobrazuje průběh přepravy balíku na stránce objednávky zákazníka. Veřejný endpoint sledování nevyžaduje token a přijímá číslo dopravce z kroku 8.

**REST:** `GET /api/v1/tracking/{trackingNumber}` — [Pří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` ([Pří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, když události pocházejí od dopravce.
- `data`: od nejnovější události. Rozhodujte podle `tracking_event_status_id` / `otep_status`, nikoli podle `description`. První události mohou zůstat ve stavu „informace odeslány“, dokud dopravce balík nenaskenuje.
- `deliveried` je true a `500` znamená doručeno; `proofs[]` pak může obsahovat podpis (`type` `1`) nebo fotografii (`type` `2`).

**Ověření:** vyhledání vrátí zásilku, kterou jste právě vytvořili. Neznámé nebo zrušené číslo vrátí `404` s `result: false`.

## 11. Konfigurace oznámení o událostech

Webhooky nahrazují opakované dotazování: server obchodu přijímá každé skenování dopravce a aktualizuje objednávku bez pravidelného volání kroku 10.

| Nastavení | Událost | Kdy |
|---|---|---|
| `tracking_event_webhook_url` | `tracking.event` | Skenování dopravce, na cestě k příjemci, doručeno |
| `order_status_change_webhook_url` | `order.status_change` | Stav ve vašem systému |
| `order_create_webhook_url` | `order.created` | Byla vytvořena objednávka štítku (krok 7); pro objednávky štítků se posílá pouze tehdy, když je `order_created_webhook_all_types` `1` |

**REST:** `PUT /api/v1/webhook-settings` — [Pří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
  }
}
```

- Mění se pouze klíče, které pošlete; neznámý klíč se odmítne s `400`.
- `changed_keys` uvádí, co bylo uloženo. Tajemství se vždy vrací zamaskované.

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

Každý `tracking.event` nese `order_id`, `tracking_event_status_id`, `tracking_event_key`, `tracking_number` a `external_tracking_number`; přiřaďte ho ke své objednávce podle `order_id` (`id` z kroku 7).

Ověřte **v2** nad nezpracovaným tělem: `HMAC_SHA256(timestamp + "." + raw_body, secret)` proti `X-Webhook-Signature-V2`. Duplicity odstraňujte podle `X-Webhook-Event-Id`. Odpovězte **2xx do 3 sekund**.

```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`) se při zrušení štítků v kroku 12 neposílá; odmítnuté zrušení štítku se oznamuje v odpovědi na toto volání.

**Ověření:** jeden testovací `submitOrder` vyvolá `order.created` s `id` objednávky a první skenování dopravce vyvolá `tracking.event`. Neplatný podpis musí příjemce odmítnout kódem `401`.

## 12. Zrušení

Štítek, který se nepoužije k odeslání, se zruší, aby ho dopravce nevyúčtoval; poplatek se vrátí na účet. Zrušení je možné pouze tehdy, dokud ho dopravce ještě povoluje (obvykle před vyzvednutím).

**REST:** `POST /api/v1/labelservice/cancelShippingLabel` — [Pří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šlete právě jedno z `id` (id objednávky) nebo `tracking_number` (sledovací číslo Superroute nebo dopravce). Odeslání obou vrátí `400`.
- `result: true`: dopravce zrušení přijal a poplatek za štítek byl vrácen.

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

Dopravce, který již balík má, zrušení odmítne: odpovědí je `400` s `result: false` a zprávou dopravce. Objednávku, jejíž štítek nebyl nikdy koupen, nelze přes toto volání zrušit.

**Ověření:** odpověď je `result: true` a veřejné sledování pro toto číslo vrátí `404`. Opakování se stejným `Idempotency-Key` vrátí uloženou odpověď; nový požadavek na zrušení téže objednávky vrátí `400` `This order already cancelled`.

## 13. Odeslání informací o zásilce a uzavření dne (pouze pokud to tento způsob vyžaduje)

Někteří dopravci potřebují před vyzvednutím předat zásilky dne (manifest). Krok 8 to pro každou objednávku uvádí v `needSubmitShippingInformation`. Během dne sbírejte id těchto objednávek a odešlete je po posledním štítku.

**REST:** `POST /api/v1/labelservice/submitShippingInformation` — [Pří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 řádek na objednávku; objednávky s `result: false` odešlete znovu po odstranění příčiny uvedené v `message`.
- `404` `No eligible orders found for shipping information submission`: žádné z id nemá koupený štítek, který ještě vyžaduje odeslání.

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

Poté uzavřete den. Volání nemá tělo a pokrývá všechny objednávky štítků volajícího.

**REST:** `POST /api/v1/labelservice/endofday` — [Pří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` se zprávou `There are orders need to submit shipping information`: některé koupené štítky ještě vyžadují odeslání; odešlete je přes `submitShippingInformation` a zavolejte znovu.

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

Tento krok vynechte, pokud žádná objednávka dne nehlásila `needSubmitShippingInformation: true`.

**Ověření:** `submitShippingInformation` hlásí `failure_count: 0` a `endofday` odpoví `200`. Nejprve to spusťte na testovacím způsobu.

## 14. Zpracování chyb

Chyby služby štítků nesou `message`; `code` je přítomen pouze tam, kde ho uvádí tabulka.

| Situace | Stav HTTP | Kód | Co integrace udělá |
|---|---|---|---|
| Chybějící nebo prošlý token, nebo nezapnutý přístup k API | `401` | — (`Unauthorized`) | Přihlaste se znovu; pokud problém přetrvává, požádejte firmu o zapnutí přístupu k API |
| `shipping_method` chybí nebo není volajícímu dostupný | `400` | — | Znovu načtěte seznam způsobů (krok 5) a použijte `id` z něj |
| Způsob odeslání nenabízí `package_type` | `400` | — | Použijte klíč z `package_type` z kroku 5 |
| Neplatná adresa nebo balík, nebo dopravce nevrátí žádnou sazbu | `400` | — (zpráva dopravce) | Zobrazte zprávu, opravte údaje a vyžádejte cenovou nabídku znovu |
| `auto_deduplication` je `1` a `ref` již existuje | `400` | — (`exist_order_ids`) | Místo vytvoření nové objednávky použijte existující z `exist_order_ids` |
| Stejný `Idempotency-Key` s jiným tělem | `409` | `IDEMPOTENCY_CONFLICT` | Pro jiný požadavek použijte nový klíč |
| Stejný `Idempotency-Key`, zatímco první požadavek ještě probíhá | `409` | `IDEMPOTENCY_IN_PROGRESS` | Počkejte počet sekund z `Retry-After` a opakujte se stejným klíčem a tělem |
| Zůstatek zákazníka spolu s úvěrem nepokrývá štítek | `400` | `INSUFFICIENT_BALANCE` | Dobijte zůstatek podle údajů `insufficient_balance` (`shortfall`, `add_funds_url`) a zavolejte krok 8 znovu |
| Štítek je koupen, soubor dopravce ještě není připraven | `400` při prvním nákupu, později `200` | `shipment_label_not_ready` | Čekejte, dokud je `labelStatus` `pending`; když je `failed`, zavolejte krok 8 znovu |
| Id objednávky nebo číslo nepatří volajícímu | `401` | — (`Not Auth`) | Zkontrolujte id a `type`; použijte účet, který objednávku vytvořil |
| Dopravce zrušení odmítl nebo je objednávka již zrušena | `400` | — | Štítek považujte za odeslaný (nebo již zrušený); neopakujte |
| Sledovací číslo je neznámé nebo zrušené | `404` | — | Přestaňte pro toto číslo zobrazovat časovou osu |
| `endofday` se zásilkami, které ještě nebyly odeslány | `400` | — | Spusťte `submitShippingInformation` pro tyto objednávky a poté zavolejte znovu |

## Seznam testů

Použijte cílovou adresu, kterou máte pod kontrolou, a způsob odeslání, který lze zrušit:

- [ ] Seznam způsobů není prázdný; zaznamenali jste jedno `id`.
- [ ] Cenová nabídka vrátí cenu pro tento způsob a cíl.
- [ ] Vytvoření vrátí `id` objednávky a `rates[].rate_id`; stejný `Idempotency-Key` nevytvoří druhou objednávku.
- [ ] `getShippingDetail` se zvoleným `rate_id` vrátí `mainTrackingNumber`; druhé volání znovu neúčtuje.
- [ ] PDF štítku se otevře a ukazuje sledovací číslo dopravce.
- [ ] Veřejné sledování najde zásilku podle tohoto čísla.
- [ ] Přijde `tracking.event` (a `order.created`, pokud je zapnuto); podpis v2 se ověří.
- [ ] Dopravce přijme přeshraniční testovací zásilku s `items`.
- [ ] Zrušení uspěje, **nebo** jste ověřili, že tento způsob po rezervaci nelze zrušit.
- [ ] Pokud způsob vyžaduje uzavření dne, testovací běh skončí bez chyby.
