Shipping

Configure shipping zones, rates, methods, providers, and per-zip overrides

GET /admin/api/shipping/zones

List all shipping zones.

POST /admin/api/shipping/zones

Create a new shipping zone.

PUT /admin/api/shipping/zones/{id}

Update a shipping zone's name and country list.

DELETE /admin/api/shipping/zones/{id}

Delete a shipping zone and its associated rates.

GET /admin/api/shipping/zones/{id}/rates

List all shipping rates for a specific zone.

PUT /admin/api/shipping/zones/{id}/rates

Set (upsert) shipping rates for a zone. Each (zone, method) pair is created or updated.

GET /admin/api/shipping/methods

List all shipping methods.

POST /admin/api/shipping/methods

Create a new shipping method.

PUT /admin/api/shipping/methods/{id}

Update a shipping method.

DELETE /admin/api/shipping/methods/{id}

Delete a shipping method and its associated rates.

GET /admin/api/shipping/settings

Get the free-shipping threshold and the minimum order value. A value of 0 means the corresponding feature is disabled.

PUT /admin/api/shipping/settings

Update the free-shipping threshold and/or the minimum order value. Both fields are optional — only the fields supplied are changed, so each settings page can mutate just its own value. Pass 0 to disable a feature.

GET /admin/api/shipping/providers

List every built-in shipping provider with its current enabled flag. Providers without a row in the integrations table are returned with `enabled: false`.

PUT /admin/api/shipping/providers/{id}

Toggle the `enabled` flag for one shipping provider. Existing `sandbox` and `config` values on the underlying integrations row are preserved — only `enabled` changes. The `{id}` segment must be one of the registered provider IDs (`flat_rate`, `easyship`, `free_shipping_threshold`).

GET /admin/api/shipping/zones/{zone_id}/zip-rates

List all zip-code rate overrides for a zone, with the method name resolved for convenience.

PUT /admin/api/shipping/zones/{zone_id}/zip-rates

Replace every zip-code rate override on the zone with the supplied entries. Existing overrides not in the request are removed.

DELETE /admin/api/shipping/zones/{zone_id}/zip-rates/{id}

Delete a single zip-code rate override by primary key, scoped to the supplied zone. Cross-zone deletions return 404 even if the id exists in a different zone.

GET /admin/api/shipping-methods

List the shipping methods available for a destination country, with the resolved cost for each. Used by the admin create-order form's shipping-method picker. Only methods that have a configured rate for the country are returned.

GET /admin/api/shipping/boxnow/settings

Return the saved BoxNow integration config object used to pre-populate the settings form. Returns an empty object `{}` when no config has been saved yet.

POST /admin/api/shipping/boxnow/settings

Persist the full BoxNow integration config JSON. The body must be the complete config object the settings form holds in state — it replaces the stored `config`. This endpoint only writes config; it does not toggle the provider's `enabled` flag (use PUT /admin/api/shipping/providers/boxnow for that).

POST /admin/api/shipping/boxnow/validate-credentials

Probe BoxNow OAuth with the supplied transient credentials and, on success, fetch the account's warehouse origins in the same round-trip. Lets the settings form verify credentials and populate the warehouse picker before anything is saved to the database.

GET /admin/api/shipping/boxnow/origins

List the BoxNow warehouse origins for the settings-page dropdown, using the already-saved credentials. Used on page-load to refresh the warehouse picker of an already-configured integration.

POST /admin/api/shipments/{order_id}/boxnow/create

(Re-)create the BoxNow delivery request for an order. Reads the locker blob stored at checkout and re-issues the delivery request — useful to retry an order whose shipment is stuck in `exception` state. The body is ignored.

GET /admin/api/shipments/{order_id}/boxnow/label.pdf

Download the printable BoxNow PDF label for an order's shipment. ZeroShop fetches the label from the BoxNow Partner API server-side and streams back the raw PDF bytes (`application/pdf`) with an attachment Content-Disposition, so the OAuth token never reaches the browser. The response body is binary, not JSON.

POST /admin/api/shipments/{order_id}/econt/sync

Sync an Econt shipment after the merchant confirms the waybill in Econt's create_label.php iframe. Calls Econt's getTrace, then writes the waybill number, label PDF URL, and normalised status to the shipments row — creating the row if the original place-order callback failed. The body is ignored.

GET /admin/api/shipments/{order_id}/events

List the tracking-event log for an order's shipment, newest first. Events are recorded from provider webhooks. Returns an empty array when the shipment exists but has no events yet; returns 404 only when the order itself does not exist.

GET /admin/api/shipping/providers/speedy/services

Proxy Speedy's /services endpoint using the tenant's stored credentials and return the available shipping services. Works regardless of whether the Speedy provider is enabled, so admins can verify services before turning it on.

POST /admin/api/shipments/{order_id}/speedy/generate

Generate a Speedy waybill for an order from the destination data captured at checkout. Persists the waybill ID before finalizing so a finalize failure leaves a recoverable record. In production the waybill is finalized (status `label_purchased`); in sandbox mode finalize is skipped (status `created`). All body fields are optional.

POST /admin/api/shipments/{order_id}/speedy/cancel

Cancel a previously generated Speedy waybill for an order via Speedy's /shipment/cancel. On success the shipments row is set to `cancelled`. Speedy may reject the cancellation if the parcel has already been picked up. The body is optional.

GET /admin/api/shipments/speedy/pickup/ready

List every Speedy shipment that has a generated waybill but no pickup scheduled yet — the rows the merchant can bulk-select for a courier visit. Sorted newest-first.

GET /admin/api/shipments/speedy/pickup/scheduled

List every Speedy shipment that already has a courier pickup booked (its provider_meta carries a pickup_id), sorted by pickup date newest-first. A pickup_id of 0 means the booking was soft-confirmed.

POST /admin/api/shipments/speedy/pickup

Book a single Speedy courier visit that collects every selected shipment. Each shipment must be a Speedy shipment in `created` or `label_purchased` status with a generated waybill. On success each shipments row's provider_meta is stamped with the pickup info so it drops off the ready list.

GET /admin/api/shipping/zones

List all shipping zones.

Response 200

{
  "zones": [
    {
      "id": 1,
      "name": "Domestic",
      "countries": [
        "US"
      ],
      "created_at": "2025-11-20T14:30:00Z"
    },
    {
      "id": 2,
      "name": "EU",
      "countries": [
        "DE",
        "FR",
        "NL"
      ],
      "created_at": "2025-11-19T09:15:00Z"
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/zones" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/shipping/zones

Create a new shipping zone.

Request Body

{
  "name": "EU",
  "countries": [
    "DE",
    "FR",
    "NL"
  ]
}

Response 201

{
  "id": 2,
  "name": "EU",
  "countries": [
    "DE",
    "FR",
    "NL"
  ],
  "created_at": "2025-11-20T14:30:00Z"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipping/zones" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "EU",
    "countries": ["DE", "FR", "NL"]
  }'
PUT /admin/api/shipping/zones/{id}

Update a shipping zone's name and country list.

Request Body

{
  "name": "European Union",
  "countries": [
    "DE",
    "FR",
    "NL",
    "BE"
  ]
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/shipping/zones/2" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "European Union",
    "countries": ["DE", "FR", "NL", "BE"]
  }'
DELETE /admin/api/shipping/zones/{id}

Delete a shipping zone and its associated rates.

Response 204

Example

curl -X DELETE "https://yourshop.zeroshop.io/admin/api/shipping/zones/2" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipping/zones/{id}/rates

List all shipping rates for a specific zone.

Response 200

{
  "rates": [
    {
      "id": 1,
      "zone_id": 1,
      "method_id": 1,
      "rate": 599,
      "created_at": "2025-11-20T14:30:00Z"
    },
    {
      "id": 2,
      "zone_id": 1,
      "method_id": 2,
      "rate": 999,
      "created_at": "2025-11-20T14:30:00Z"
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/zones/1/rates" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/shipping/zones/{id}/rates

Set (upsert) shipping rates for a zone. Each (zone, method) pair is created or updated.

Request Body

{
  "rates": [
    {
      "method_id": 1,
      "rate": 599
    },
    {
      "method_id": 2,
      "rate": 999
    }
  ]
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/shipping/zones/1/rates" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "rates": [
      {"method_id": 1, "rate": 599},
      {"method_id": 2, "rate": 999}
    ]
  }'
GET /admin/api/shipping/methods

List all shipping methods.

Response 200

{
  "methods": [
    {
      "id": 1,
      "name": "Standard Shipping",
      "description": "5-7 business days",
      "enabled": true,
      "sort_order": 0,
      "created_at": "2025-11-20T14:30:00Z"
    },
    {
      "id": 2,
      "name": "Express Shipping",
      "description": "1-2 business days",
      "enabled": true,
      "sort_order": 1,
      "created_at": "2025-11-19T09:15:00Z"
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/methods" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/shipping/methods

Create a new shipping method.

Request Body

{
  "name": "Express Shipping",
  "description": "1-2 business days"
}

Response 201

{
  "id": 2,
  "name": "Express Shipping",
  "description": "1-2 business days",
  "enabled": true,
  "sort_order": 0,
  "created_at": "2025-11-20T14:30:00Z"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipping/methods" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Express Shipping",
    "description": "1-2 business days"
  }'
PUT /admin/api/shipping/methods/{id}

Update a shipping method.

Request Body

{
  "name": "Express Shipping",
  "description": "1-2 business days",
  "enabled": true,
  "sort_order": 1
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/shipping/methods/2" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Express Shipping",
    "description": "1-2 business days",
    "enabled": true,
    "sort_order": 1
  }'
DELETE /admin/api/shipping/methods/{id}

Delete a shipping method and its associated rates.

Response 204

Example

curl -X DELETE "https://yourshop.zeroshop.io/admin/api/shipping/methods/2" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipping/settings

Get the free-shipping threshold and the minimum order value. A value of 0 means the corresponding feature is disabled.

Response 200

{
  "free_shipping_threshold": 5000,
  "min_order_value": 2500
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/settings" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/shipping/settings

Update the free-shipping threshold and/or the minimum order value. Both fields are optional — only the fields supplied are changed, so each settings page can mutate just its own value. Pass 0 to disable a feature.

Request Body

{
  "free_shipping_threshold": 5000,
  "min_order_value": 2500
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/shipping/settings" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"free_shipping_threshold": 5000, "min_order_value": 2500}'
GET /admin/api/shipping/providers

List every built-in shipping provider with its current enabled flag. Providers without a row in the integrations table are returned with `enabled: false`.

Response 200

[
  {
    "id": "flat_rate",
    "name": "Flat rate",
    "description": "Charge a fixed shipping cost based on the customer's country.",
    "enabled": true
  },
  {
    "id": "easyship",
    "name": "Easyship",
    "description": "Buy real shipping labels and track parcels via the Easyship API.",
    "enabled": false
  },
  {
    "id": "free_shipping_threshold",
    "name": "Free shipping threshold",
    "description": "Waive shipping costs when the cart subtotal reaches a set amount.",
    "enabled": true
  }
]

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/providers" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/shipping/providers/{id}

Toggle the `enabled` flag for one shipping provider. Existing `sandbox` and `config` values on the underlying integrations row are preserved — only `enabled` changes. The `{id}` segment must be one of the registered provider IDs (`flat_rate`, `easyship`, `free_shipping_threshold`).

Request Body

{
  "enabled": true
}

Response 200

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/shipping/providers/easyship" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'
GET /admin/api/shipping/zones/{zone_id}/zip-rates

List all zip-code rate overrides for a zone, with the method name resolved for convenience.

Response 200

[
  {
    "id": 12,
    "method_id": 1,
    "method_name": "Standard Shipping",
    "postal_code": "1000",
    "rate": 399
  },
  {
    "id": 13,
    "method_id": 2,
    "method_name": "Express Shipping",
    "postal_code": "1000",
    "rate": 799
  }
]

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/zones/2/zip-rates" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/shipping/zones/{zone_id}/zip-rates

Replace every zip-code rate override on the zone with the supplied entries. Existing overrides not in the request are removed.

Request Body

{
  "entries": [
    {
      "postal_code": "1000",
      "method_id": 1,
      "rate": 399
    },
    {
      "postal_code": "1000",
      "method_id": 2,
      "rate": 799
    }
  ]
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/shipping/zones/2/zip-rates" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "entries": [
      {"postal_code": "1000", "method_id": 1, "rate": 399},
      {"postal_code": "1000", "method_id": 2, "rate": 799}
    ]
  }'
DELETE /admin/api/shipping/zones/{zone_id}/zip-rates/{id}

Delete a single zip-code rate override by primary key, scoped to the supplied zone. Cross-zone deletions return 404 even if the id exists in a different zone.

Response 204

Example

curl -X DELETE "https://yourshop.zeroshop.io/admin/api/shipping/zones/2/zip-rates/12" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipping-methods

List the shipping methods available for a destination country, with the resolved cost for each. Used by the admin create-order form's shipping-method picker. Only methods that have a configured rate for the country are returned.

Parameters

Name Type Required Description
country string required ISO 3166-1 alpha-2 destination country code (e.g. BG, DE) to resolve methods and rates for.

Response 200

{
  "methods": [
    {
      "id": 1,
      "name": "Standard Shipping",
      "cost_cents": 599
    },
    {
      "id": 2,
      "name": "Express Shipping",
      "cost_cents": 999
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping-methods?country=BG" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipping/boxnow/settings

Return the saved BoxNow integration config object used to pre-populate the settings form. Returns an empty object `{}` when no config has been saved yet.

Response 200

{
  "client_id": "bn_client_123",
  "client_secret": "bn_secret_***",
  "api_url": "https://api-stage.boxnow.bg/api/v1",
  "partner_id": 42,
  "default_origin_id": "w-1",
  "default_origin_label": "Main Warehouse",
  "allow_cod": false
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/boxnow/settings" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/shipping/boxnow/settings

Persist the full BoxNow integration config JSON. The body must be the complete config object the settings form holds in state — it replaces the stored `config`. This endpoint only writes config; it does not toggle the provider's `enabled` flag (use PUT /admin/api/shipping/providers/boxnow for that).

Request Body

{
  "client_id": "bn_client_123",
  "client_secret": "bn_secret_456",
  "api_url": "https://api-stage.boxnow.bg/api/v1",
  "partner_id": 42,
  "default_origin_id": "w-1",
  "default_origin_label": "Main Warehouse",
  "allow_cod": false
}

Response 200

{
  "ok": true
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipping/boxnow/settings" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "bn_client_123",
    "client_secret": "bn_secret_456",
    "api_url": "https://api-stage.boxnow.bg/api/v1",
    "partner_id": 42,
    "default_origin_id": "w-1",
    "default_origin_label": "Main Warehouse",
    "allow_cod": false
  }'
POST /admin/api/shipping/boxnow/validate-credentials

Probe BoxNow OAuth with the supplied transient credentials and, on success, fetch the account's warehouse origins in the same round-trip. Lets the settings form verify credentials and populate the warehouse picker before anything is saved to the database.

Request Body

{
  "client_id": "bn_client_123",
  "client_secret": "bn_secret_456",
  "api_url": "https://api-stage.boxnow.bg/api/v1"
}

Response 200

{
  "ok": true,
  "origins": [
    {
      "id": "w-1",
      "name": "Main Warehouse",
      "address_line_1": "ul. Vitosha 1",
      "postal_code": "1000",
      "country": "BG"
    }
  ]
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipping/boxnow/validate-credentials" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "bn_client_123",
    "client_secret": "bn_secret_456",
    "api_url": "https://api-stage.boxnow.bg/api/v1"
  }'
GET /admin/api/shipping/boxnow/origins

List the BoxNow warehouse origins for the settings-page dropdown, using the already-saved credentials. Used on page-load to refresh the warehouse picker of an already-configured integration.

Response 200

[
  {
    "id": "w-1",
    "name": "Main Warehouse",
    "address_line_1": "ul. Vitosha 1",
    "postal_code": "1000",
    "country": "BG"
  }
]

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/boxnow/origins" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/shipments/{order_id}/boxnow/create

(Re-)create the BoxNow delivery request for an order. Reads the locker blob stored at checkout and re-issues the delivery request — useful to retry an order whose shipment is stuck in `exception` state. The body is ignored.

Response 200

{
  "ok": true
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipments/123/boxnow/create" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipments/{order_id}/boxnow/label.pdf

Download the printable BoxNow PDF label for an order's shipment. ZeroShop fetches the label from the BoxNow Partner API server-side and streams back the raw PDF bytes (`application/pdf`) with an attachment Content-Disposition, so the OAuth token never reaches the browser. The response body is binary, not JSON.

Response 200

Example

curl "https://yourshop.zeroshop.io/admin/api/shipments/123/boxnow/label.pdf" \
  -H "Authorization: Bearer zspat_..." \
  -o boxnow-label.pdf
POST /admin/api/shipments/{order_id}/econt/sync

Sync an Econt shipment after the merchant confirms the waybill in Econt's create_label.php iframe. Calls Econt's getTrace, then writes the waybill number, label PDF URL, and normalised status to the shipments row — creating the row if the original place-order callback failed. The body is ignored.

Response 200

{
  "shipment_id": 55,
  "tracking_number": "1051234567890",
  "tracking_url": "https://www.econt.com/services/track-shipment/1051234567890",
  "label_url": "https://delivery.econt.com/label/1051234567890.pdf",
  "status": "label_purchased",
  "provider_status": "label_generated"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipments/123/econt/sync" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipments/{order_id}/events

List the tracking-event log for an order's shipment, newest first. Events are recorded from provider webhooks. Returns an empty array when the shipment exists but has no events yet; returns 404 only when the order itself does not exist.

Response 200

[
  {
    "id": 91,
    "shipment_id": 55,
    "provider": "boxnow",
    "event_type": "delivered",
    "occurred_at": "2026-05-28T13:00:00Z",
    "received_at": "2026-05-28T13:00:05Z",
    "location_display": "APM Sofia",
    "location_postal": "1000",
    "raw_payload": "{\"parcelState\":\"delivered\"}"
  },
  {
    "id": 90,
    "shipment_id": 55,
    "provider": "boxnow",
    "event_type": "in-depot",
    "occurred_at": "2026-05-28T09:00:00Z",
    "received_at": "2026-05-28T09:00:04Z",
    "location_display": "APM Sofia",
    "location_postal": "1000",
    "raw_payload": "{\"parcelState\":\"in-depot\"}"
  }
]

Example

curl "https://yourshop.zeroshop.io/admin/api/shipments/123/events" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipping/providers/speedy/services

Proxy Speedy's /services endpoint using the tenant's stored credentials and return the available shipping services. Works regardless of whether the Speedy provider is enabled, so admins can verify services before turning it on.

Response 200

{
  "services": [
    {
      "id": 505,
      "name": "Стандарт 24 часа",
      "nameEn": "Standard 24 hours",
      "description": "Office-to-office and office-to-door standard delivery."
    },
    {
      "id": 202,
      "name": "Експресна доставка",
      "nameEn": "Express delivery",
      "description": null
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipping/providers/speedy/services" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/shipments/{order_id}/speedy/generate

Generate a Speedy waybill for an order from the destination data captured at checkout. Persists the waybill ID before finalizing so a finalize failure leaves a recoverable record. In production the waybill is finalized (status `label_purchased`); in sandbox mode finalize is skipped (status `created`). All body fields are optional.

Request Body

{
  "contents": "Order #123",
  "recipient_phone": "+359881234567"
}

Response 200

{
  "shipment_id": 55,
  "speedy_shipment_id": 1000123456789,
  "tracking_number": "1000123456789",
  "tracking_url": "https://www.speedy.bg/en/track-shipment?shipmentNumber=1000123456789",
  "label_url": "https://api.speedy.bg/label/1000123456789.pdf",
  "status": "label_purchased",
  "sandbox": false
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipments/123/speedy/generate" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"contents": "Order #123"}'
POST /admin/api/shipments/{order_id}/speedy/cancel

Cancel a previously generated Speedy waybill for an order via Speedy's /shipment/cancel. On success the shipments row is set to `cancelled`. Speedy may reject the cancellation if the parcel has already been picked up. The body is optional.

Request Body

{
  "reason": "Customer cancelled the order"
}

Response 200

{
  "shipment_id": 55,
  "speedy_shipment_id": 1000123456789,
  "status": "cancelled"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipments/123/speedy/cancel" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"reason": "Customer cancelled the order"}'
GET /admin/api/shipments/speedy/pickup/ready

List every Speedy shipment that has a generated waybill but no pickup scheduled yet — the rows the merchant can bulk-select for a courier visit. Sorted newest-first.

Response 200

{
  "shipments": [
    {
      "shipment_id": 55,
      "order_id": 123,
      "provider_shipment_id": "1000123456789",
      "tracking_number": "1000123456789",
      "status": "label_purchased",
      "customer_name": "Ivan Petrov",
      "total_cents": 4599
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipments/speedy/pickup/ready" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/shipments/speedy/pickup/scheduled

List every Speedy shipment that already has a courier pickup booked (its provider_meta carries a pickup_id), sorted by pickup date newest-first. A pickup_id of 0 means the booking was soft-confirmed.

Response 200

{
  "shipments": [
    {
      "shipment_id": 55,
      "order_id": 123,
      "provider_shipment_id": "1000123456789",
      "tracking_number": "1000123456789",
      "pickup_id": 778899,
      "pickup_date": "2026-05-31",
      "visit_end_time": "18:00",
      "customer_name": "Ivan Petrov"
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shipments/speedy/pickup/scheduled" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/shipments/speedy/pickup

Book a single Speedy courier visit that collects every selected shipment. Each shipment must be a Speedy shipment in `created` or `label_purchased` status with a generated waybill. On success each shipments row's provider_meta is stamped with the pickup info so it drops off the ready list.

Request Body

{
  "shipment_ids": [
    55,
    56
  ],
  "date": "2026-05-31",
  "visit_end_time": "18:00",
  "contact_name": "Ivan Petrov",
  "phone_number": "+359881234567"
}

Response 200

{
  "pickup_id": 778899,
  "shipment_count": 2,
  "date": "2026-05-31"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/shipments/speedy/pickup" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "shipment_ids": [55, 56],
    "date": "2026-05-31",
    "visit_end_time": "18:00",
    "contact_name": "Ivan Petrov",
    "phone_number": "+359881234567"
  }'

We value your privacy

We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.