Shipping

Three-level model:

shipping_zones    countries + zip patterns, optional market scope
  └── shipping_methods    carrier × service (PPL Home, Zásilkovna pickup)
        └── shipping_rates    tiered per (weight × subtotal × quantity)

Plus a global cache of pickup points (carrier_pickup_points).

Zones

A zone is a destination filter — country codes + optional zip-pattern matching. Zones can be account-wide or scoped to one market.

curl -X POST "$API/shipping/zones.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "handle": "cz",
    "name": "Česká republika",
    "country_codes": ["CZ"],
    "zip_patterns": ["1*", "2*"]
  }'

zip_patterns accept prefix wildcards (12* matches 12000) or exact strings.

Methods

A method is one shipping option (PPL Home, Zásilkovna pickup, GLS Express). It binds to a zone and configures carrier behavior.

curl -X POST "$API/shipping/methods.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "handle": "zasilkovna-pickup",
    "name": "Zásilkovna výdejní místo",
    "zone_id": 308...,
    "carrier": "zasilkovna",
    "service_code": "Z_BOX",
    "rate_mode": "tiered",
    "requires_pickup_point": true,
    "is_cod_compatible": true,
    "estimated_days_min": 1,
    "estimated_days_max": 2,
    "cod_fee_cents": 3000,
    "carrier_config": { "shop_id": "..." }
  }'

rate_mode is flat | tiered | carrier_api | free. carrier_api calls the carrier in real time at quote — Phase 1 supports static tiered rates only; carrier_api is a slot for future iterations.

Rates

A rate is one tier on a method. Bind it to a method and add weight / subtotal / quantity bounds:

# Standard tier
curl -X POST "$API/shipping/methods/{method_id}/rates.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "price_cents": 9900,
    "currency": "CZK",
    "max_weight_grams": 5000,
    "display_name": "Standard"
  }'

# Free shipping above 2000 CZK
curl -X POST "$API/shipping/methods/{method_id}/rates.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{"price_cents":0,"currency":"CZK","min_subtotal_cents":200000}'

is_free is a Postgres GENERATED ALWAYS column (price_cents = 0) — don't set it in the request, it's computed.

Quote

The quote endpoint returns all rate options for an order's destination

  • cart shape:
curl -X POST "$API/shipping/quote.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "country_code": "CZ",
    "zip": "15000",
    "weight_grams": 1500,
    "subtotal_cents": 50000,
    "currency": "CZK"
  }'
{
  "quotes": [
    {
      "method": { "handle": "ppl-home", "name": "PPL doručení", ... },
      "rate_id": "308...",
      "price_cents": "9900",
      "currency": "CZK",
      "is_free": false,
      "display_name": "Standard"
    },
    ...
  ]
}

The storefront calls GET /storefront/2026-01/checkouts/{token}/shipping_quote.json for the same data computed against the checkout's known shipping address and cart contents.

Pickup points

Methods with requires_pickup_point=true need a chosen pickup point at checkout. The system maintains a global cache at carrier_pickup_points (no RLS — public reference data per carrier).

curl "/admin/2026-01/shipping/pickup-points.json?carrier=zasilkovna&country_code=CZ&postcode_prefix=110&limit=10" \
  -H "Authorization: Bearer $NEVIOS_KEY"

The storefront passes pickup_point_id when calling PUT /storefront/2026-01/checkouts/{token}/shipping.json.

Endpoints

POST   /admin/2026-01/{handle}/shipping/zones.json
POST   /admin/2026-01/{handle}/shipping/methods.json
POST   /admin/2026-01/{handle}/shipping/methods/{id}/rates.json
DELETE /admin/2026-01/{handle}/shipping/methods/{id}.json
DELETE /admin/2026-01/{handle}/shipping/rates/{id}.json
POST   /admin/2026-01/{handle}/shipping/quote.json
GET    /admin/2026-01/shipping/pickup-points.json   (global)

Next: Tax.