# Cancel order
Source: https://docs.sparqle.com/api-reference/cancellation/cancel-order
/api-reference/openapi.json patch /orders/cancel/{orderId}
Cancel an order. Allowed up to and including the `in_transit` status.
# Check order
Source: https://docs.sparqle.com/api-reference/coverage/check-order
/api-reference/openapi.json post /orders/check/{locationId}
Validate a delivery address and serviceability for a location before creating an order.
# Check postal code
Source: https://docs.sparqle.com/api-reference/coverage/check-postal-code
/api-reference/openapi.json post /orders/check/postal-code/{locationId}
Check whether a location can deliver to a country and postal code, without a full address. Use it where you only have a postal code, such as a checkout before the address is entered, or a coverage lookup.
Treat it as a pre-check: a cheap filter that rules a postal code in or out before an address exists. A `deliverable: true` here means the postal code area is served, not that a specific address in it is. `POST /orders/check/{locationId}` stays the source of truth for an exact answer.
# List postal code coverage
Source: https://docs.sparqle.com/api-reference/coverage/list-postal-code-coverage
/api-reference/openapi.json get /coverage/postal-codes/{locationId}
Fetch every postal code area this location has coverage for, grouped by ISO 3166-1 alpha-2 country code.
**Coverage changes over time.** Re-fetch this list regularly. Do not store it on your side as a permanent source of truth.
**This is not a real-time serviceability check.** Treat it as a pre-check: a filter for narrowing down which postal codes are worth offering at all. It says something about postal code areas, nothing about a specific address. For a live per-postal-code answer call `POST /orders/check/postal-code/{locationId}`, and for an exact per-address answer `POST /orders/check/{locationId}`, which stays the source of truth.
Countries without coverage are omitted rather than returned as an empty list.
# API reference
Source: https://docs.sparqle.com/api-reference/introduction
Endpoints, request bodies, and response schemas for the Sparqle API.
The endpoints in this section are defined according to Sparqle's OpenAPI specification.
Each page includes the request schema, an interactive playground, and example responses.
## Base URL
All requests go to one of the [environment](/environments) base URLs:
* **Test** — `https://staging-v2.sparqle.tech`
* **Live** — `https://v2.sparqle.com`
## Authentication
Send your [API key](/authentication) in the `api-key` header on every request.
## Endpoints
| Endpoint | Method | Purpose |
| -------------------------------------- | ------- | ----------------------------------------------------- |
| `/orders` | `POST` | [Create an order](/concepts/order-lifecycle). |
| `/orders/check/{locationId}` | `POST` | Validate an address and serviceability. |
| `/orders/label/{orderId}` | `GET` | Fetch a single [label](/concepts/labels). |
| `/orders/label/bundle/{orderBundleId}` | `GET` | Fetch all labels in a bundle. |
| `/orders/cancel/{orderId}` | `PATCH` | Cancel an order. |
| `/pickup-points` | `GET` | Find nearby [pickup points](/concepts/pickup-points). |
| `/coverage/postal-codes/{locationId}` | `GET` | List the postal code areas a location covers. |
# Get label
Source: https://docs.sparqle.com/api-reference/labels/get-label
/api-reference/openapi.json get /orders/label/{orderId}
Fetch the shipping label for an order as a base64-encoded PDF or ZPL document.
# Get label by bundle ID
Source: https://docs.sparqle.com/api-reference/labels/get-label-by-bundle-id
/api-reference/openapi.json get /orders/label/bundle/{orderBundleId}
Fetch the labels for all orders in a bundle as a merged PDF.
# Create order
Source: https://docs.sparqle.com/api-reference/orders/create-order
/api-reference/openapi.json post /orders
Create a delivery order. Returns the created order including its `orderId`, `barcode`, and `trackingUrl`.
# Search pickup points
Source: https://docs.sparqle.com/api-reference/pickup-points/search-pickup-points
/api-reference/openapi.json get /pickup-points
Find the pickup points (PUDO locations) closest to an address or coordinate, ordered by distance. Pass the `pudoId` of the point your shopper selects straight through to the order's `pudoId`.
Search by `countryCode` + `postalCode`, or by `lat` + `lng`. An address that has no pickup points returns an empty list.
# Authentication
Source: https://docs.sparqle.com/authentication
Authenticate every request with your Sparqle API key.
The Sparqle API authenticates requests with an **API key** sent in the `api-key`
HTTP header.
```bash theme={null}
curl https://staging-v2.sparqle.tech/orders/label/SPQ-10293 \
-H "api-key: YOUR_API_KEY"
```
Each key is scoped to one environment and to the companies it was issued for. It
can act on any location belonging to those companies, and no others.
Sparqle supplies the API key for your integration — it is not something you
create or retrieve from the dashboard. Contact your Sparqle account manager, or
[support@sparqle.com](mailto:support@sparqle.com), to receive a key for the test
and/or live environment. If you lose a key, ask us to issue a replacement and
revoke the old one.
Treat your API key like a password. Never embed it in client-side code, mobile
apps, or public repositories. Make all Sparqle API calls from your backend.
## Test vs. live keys
You get a separate key per environment. A test key only works against the test
base URL, and a live key only works against the live base URL. See
[Environments](/environments) for the URLs.
| Environment | Header | Base URL |
| ----------- | --------------------- | --------------------------------- |
| Test | `api-key: ` | `https://staging-v2.sparqle.tech` |
| Live | `api-key: ` | `https://v2.sparqle.com` |
## What a key can reach
A key is valid only on the endpoints listed under [API
Reference](/api-reference/introduction). Any other endpoint returns `401
Unauthorized`, even with a valid key.
## Failed authentication
A missing, malformed, or revoked key returns `401 Unauthorized`. Using a valid
key against a `locationId`, order, or label belonging to a company it wasn't
issued for returns `403 Forbidden`. See [Errors](/errors) for the full list.
# Labels
Source: https://docs.sparqle.com/concepts/labels
Fetch printable PDF or ZPL labels for an order or a whole bundle.
Sparqle generates a shipping label for every order. Labels are returned as a
base64-encoded document that you decode and print. PDF is the default.
## Example
[Download the sample PDF](/label/preview-label.pdf)
## Single order
```bash theme={null}
curl https://staging-v2.sparqle.tech/orders/label/SPQ-10293 \
-H "api-key: YOUR_API_KEY"
```
Decode the returned base64 string and write it to a `.pdf`:
```js theme={null}
import { writeFileSync } from "node:fs";
const res = await fetch(
"https://staging-v2.sparqle.tech/orders/label/SPQ-10293",
{ headers: { "api-key": process.env.SPARQLE_API_KEY } },
);
const { label } = await res.json();
writeFileSync("label.pdf", Buffer.from(label, "base64"));
```
### ZPL
For a Sparqle-generated label, add `format=zpl` to receive native ZPL for a
203-DPI printer:
```bash theme={null}
curl "https://staging-v2.sparqle.tech/orders/label/SPQ-10293?format=zpl" \
-H "api-key: YOUR_API_KEY"
```
The response shape remains `{ "label": "..." }`. Decode `label` as UTF-8 and
write it to a `.zpl` file. ZPL is always A6 at 203 DPI — `printSize` is not
supported and returns `400` if passed.
Pass `orientation=landscape` or `orientation=portrait`. When you omit
`orientation`, ZPL uses the same company **ORIENTATION** setting as PDF labels.
If that setting is portrait (common for PDF), ZPL is portrait too — not landscape
by default. Integrations that always want landscape must pass
`orientation=landscape` on every request.
Portrait ZPL uses a rotated (`^A0R` / `^BCR`) layout sized `^PW839` / `^LL1184`
for vertical thermal printing.
**Existing ZPL integrations:** before portrait support, ZPL was always
landscape regardless of company settings. After this release, omitting
`orientation` follows your company ORIENTATION setting. Merchants configured
for portrait PDF labels will receive portrait ZPL automatically. Pin
`orientation=landscape` if your printer pipeline expects the old behavior.
ZPL is only available on the single-order endpoint. Batch and bundle label
calls accept `format=pdf` (or omit `format`); `format=zpl` returns `400`.
ZPL is also unavailable when Sparqle passes through a label supplied by an
external carrier. Requests without `format=zpl` continue to return PDF.
A label is available once the order has been created. If you request a label
before it has been generated, retry shortly after.
## Bundles (multi-parcel)
When several orders ship together they share an `orderBundleId`. Fetch every
label in the bundle in one call:
```bash theme={null}
curl https://staging-v2.sparqle.tech/orders/label/bundle/BUNDLE123 \
-H "api-key: YOUR_API_KEY"
```
This returns the labels for all orders in the bundle, so you can print them in a
single pass.
## Print size
PDF labels are landscape A6 by default. For PDF, you can change orientation and
print size in your Sparqle settings (or via the `orientation` / `printSize`
query params); the label endpoints then return that configured size.
ZPL honors `orientation` and the same company **ORIENTATION** setting as PDF,
but always prints A6 at 203 DPI — there is no A4 ZPL variant. Omitting
`orientation` does not default to landscape: it follows the company setting.
Use `orientation=portrait` for vertical thermal printers, or
`orientation=landscape` to pin the previous always-landscape behavior.
## Tips
* Print at the label's native size — don't scale to fit.
* Store the `barcode` returned on create; it's printed on the label and is what
couriers scan.
# Order bundling
Source: https://docs.sparqle.com/concepts/order-bundling
Ship several parcels together by grouping orders into one delivery bundle.
In Sparqle, **every order is exactly one physical parcel** (a collo). When a
single shipment needs to travel as more than one parcel, you create one order
per parcel and group them into a **delivery bundle**. The bundle keeps the
parcels together through routing and lets you fetch all their
[labels](/concepts/labels) in a single call.
Each order in a bundle keeps its own `orderRef`, dimensions, and weight —
bundling doesn't merge them, it links them.
## How bundling works
You bundle orders by referencing a shared bundle on create. The first order
creates the bundle; every following order joins it by passing its `bundleId` as
`orderBundleId`.
Call `POST /orders` as usual, leaving `orderBundleId` out. The response
includes an `orderBundle` object — note its `bundleId`.
Call `POST /orders` again for each additional parcel, this time passing the
`bundleId` from the first response as `orderBundleId`. Each order joins the
same bundle.
Retrieve every label in the bundle at once with
`GET /orders/label/bundle/{orderBundleId}`. See [Labels](/concepts/labels).
## 1. Create the first order
Leave `orderBundleId` out. Sparqle creates a new bundle and returns it as
`orderBundle`:
```json theme={null}
{
"orderId": "SPQ-10293",
"orderRef": "PADE3JWEA-1",
"status": "draft",
"barcode": "3STBXX123456789",
"orderBundle": {
"id": 7419,
"bundleId": "VNN1UY7TMSY81P8Q4A2C7"
}
}
```
## 2. Create the next orders in the bundle
Use the `bundleId` from the first response as `orderBundleId` on every
additional parcel. Give each order its own `orderRef` and its own dimensions:
```bash theme={null}
curl -X POST https://staging-v2.sparqle.tech/orders \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"locationId": "ABC123",
"orderRef": "PADE3JWEA-2",
"orderBundleId": "VNN1UY7TMSY81P8Q4A2C7",
"deliveryName": "Jane Doe",
"deliveryEmail": "jane@example.com",
"deliveryPhone": "+31612345678",
"deliveryAddress": {
"street": "Gustav Mahlerlaan",
"houseNumber": "320",
"postalCode": "1082 ME",
"city": "Amsterdam",
"countryCode": "NL"
}
}'
```
Repeat for each remaining parcel, reusing the same `orderBundleId`.
`orderBundleId` is the bundle's `bundleId` (the string), not its numeric `id`.
Only pass it on orders that join an existing bundle — never on the first
order, which creates the bundle.
Fetch all labels for a bundle in one request with
`GET /orders/label/bundle/{orderBundleId}`, and print them in a single pass.
# Order lifecycle
Source: https://docs.sparqle.com/concepts/order-lifecycle
Every status a Sparqle order moves through, and what each one means.
Each order carries a `status`. Over the API and in [webhooks](/webhooks), the
status is the lowercase value in the **API value** column below (for example
`in_transit`). Statuses progress as the parcel is printed, scanned, and
delivered — with separate branches for returns and exceptions.
## Happy path
The order was created and accepted. No label scanned yet.
A label has been printed. Sparqle is expecting the parcel to enter the
network.
The parcel has had its first carrier scan and is moving through the network.
In the bike, en route from the local depot to the recipient.
Delivered. This is the terminal success state.
## All statuses
| API value | Meaning |
| ----------------------- | ----------------------------------------------------------- |
| `draft` | Order created and accepted. |
| `expected` | Label printed; parcel expected in the network. |
| `in_transit` | First scan received; moving through the middle mile. |
| `out_for_delivery` | En route from the local depot to the recipient. |
| `awaiting_next_attempt` | A delivery attempt was made; another is scheduled. |
| `at_pickup_point` | Available for collection at a pickup point. |
| `completed` | Delivered. Terminal success state. |
| `failed` | Delivery failed, including after second attempts. |
| `in_return_transit` | Being returned to sender. |
| `returned` | Returned and received back at the origin address. |
| `cancelled` | Cancelled before dispatch. |
| `rejected` | Rejected (for example, not serviceable). |
| `address_not_found` | The delivery address could not be resolved. |
| `inactive` | Label printed but never scanned within a reasonable window. |
| `missing` | Previously scanned but cannot currently be located. |
| `lost` | A missing parcel confirmed lost. Terminal. |
| `error` | An internal processing error occurred. |
## Cancelling an order
You can cancel an order using `PATCH /orders/cancel/{orderId}`. Succesfull cancellation depends on the order status. Up until `in_transit` every order can be cancelled. Once a parcel is `out_for_delivery` or beyond,
it may depend on the specific order if it can be cancelled. This can not be inferred from the API.
Terminal states (`completed`, `returned`, `lost`, `cancelled`) won't change
again.
# Pickup points
Source: https://docs.sparqle.com/concepts/pickup-points
Let a shopper collect their parcel from a pickup point or parcel locker instead of at home.
A pickup point (also called a PUDO — pick-up / drop-off point) is a shop, post
office, or parcel locker where a shopper collects their parcel. Sparqle looks up
the points near the shopper, and you attach the one they pick to the order.
## The flow
`GET /pickup-points` with the shopper's country and postal code, or with
coordinates. You get the closest points, ordered by distance.
Render the list at checkout. Keep the `pudoId` of the point they choose.
`POST /orders` with the same `pudoId`, alongside the shopper's own delivery
address as usual.
```mermaid theme={null}
flowchart TD
A[Checkout] --> B[GET /pickup-points]
B --> C{Any points?}
C -->|Yes| D[Shopper picks one] --> E["POST /orders with pudoId"]
C -->|No| F[Offer home delivery]
E --> G{Pickup point delivery available?}
G -->|Yes| H[Order created] --> I[GET label]
G -->|No| J[406 — order rejected]
```
## Search for points
```bash theme={null}
curl "https://staging-v2.sparqle.tech/pickup-points?countryCode=NL&postalCode=1082ME&maxResults=5" \
-H "api-key: $SPARQLE_API_KEY"
```
Search by `countryCode` + `postalCode`, or by `lat` + `lng`. Everything else is
optional: `city`, `street`, `state`, `maxResults`, `maxDistanceKm`, and `types`.
```json theme={null}
{
"points": [
{
"pudoId": "9f83c1b0a4",
"carrierPudoId": "8004567",
"type": "SERVICE_POINT",
"name": "Albert Heijn Zuidas",
"address": {
"street": "Gustav Mahlerlaan 320",
"postalCode": "1082ME",
"city": "Amsterdam",
"countryCode": "NL"
},
"geo": { "lat": 52.3382, "lng": 4.8721 },
"openingHours": {
"open24h": false,
"weekdays": { "monday": [{ "openTime": "09:00", "closeTime": "18:00" }] }
},
"facilities": { "carParking": true, "accessibility": true },
"distanceMeters": 420
}
],
"totalFound": 12
}
```
`totalFound` counts the points near that address; when it exceeds the number of
points returned, `maxResults` truncated the list.
### Which id do I send?
**Always `pudoId`** — it goes straight through to the order field of the same
name, unchanged. Treat it as an opaque string: store it as-is and don't parse or
derive anything from its shape, which can change.
`carrierPudoId` is the **delivering carrier's** own identifier for that same
physical point (a DHL or PostNL location code, say). It is informational: send
it as the order's `pudoId` and the order will not route. It's there for one job —
matching. If your webshop plugin already let the shopper pick a point and stored
the *carrier's* id, search this endpoint for that address, find the point whose
`carrierPudoId` matches, and order with its `pudoId`. Match within one carrier:
these ids are unique per carrier, not across them.
### Filtering by kind
`types` narrows the search. Repeat the parameter for several kinds, or leave it
off to search all of them:
```
?countryCode=NL&postalCode=1082ME&types=PARCEL_LOCKER&types=PICKUP_DROPOFF_POINT
```
The filter values are broader than the `type` a point comes back with:
| `types` filter | matches points of `type` |
| ---------------------- | ------------------------------ |
| `PARCEL_LOCKER` | `PARCEL_LOCKER` |
| `PICKUP_DROPOFF_POINT` | `SERVICE_POINT`, `POST_OFFICE` |
| `MAILBOX_POBOX` | `MAILBOX` |
## Create the order
Set `pudoId` on the order. The delivery address stays the **shopper's own
address** — the pickup point is identified by its id, not by using its address
as the destination.
```bash theme={null}
curl -X POST "https://staging-v2.sparqle.tech/orders" \
-H "api-key: $SPARQLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderRef": "ORDER-1001",
"locationId": "XXXXXXXXX",
"deliveryName": "Jane Doe",
"deliveryEmail": "jane@example.com",
"pudoId": "9f83c1b0a4",
"deliveryAddress": {
"street": "Gustav Mahlerlaan",
"houseNumber": "320",
"postalCode": "1082ME",
"city": "Amsterdam",
"countryCode": "NL"
}
}'
```
## What makes a point available
Two separate things decide whether pickup point delivery works, and they can
disagree — always handle the rejection below rather than assuming a searchable
point is an orderable one.
`GET /pickup-points` returns whatever points exist near the address you
searched. It is not scoped to your account, your location, or your delivery
areas — the same address returns the same points for everyone.
An order with a `pudoId` is only accepted when the delivery address falls in
a delivery area that both is served by our pickup point carrier and has the
**pickup point** capability enabled. That capability is set per delivery
area, so pickup point delivery is effectively switched on per location — the
locations linked to those areas. You can see which capabilities an area has
on the location's page in the dashboard, under its delivery areas.
To find out before you create the order, pass `pickupPoint: true` to
[`POST /orders/check/{locationId}`](/concepts/routing) — it answers the same
question the create call would, and reports `missingCapabilities.pickupPoint`
when the address can't be served.
An order that names a pickup point is never quietly delivered to the door
instead: a locker id means nothing to another carrier, so we reject the order
rather than fall back.
```json theme={null}
{
"statusCode": 406,
"message": "No carrier offering pickup point delivery is available for this address.",
"error": "Not Acceptable"
}
```
## Empty results
An empty `points` list is a normal answer, not an error. It means one of:
* there are no pickup points near that address,
* the country isn't covered by the pickup point network,
* the search radius (`maxDistanceKm`) or `types` filter excluded everything,
* the lookup itself failed — we return an empty list rather than an error so
checkout keeps working.
Offer home delivery when the list comes back empty.
# Routing & fallback
Source: https://docs.sparqle.com/concepts/routing
Check deliverability before creating, and fall back to another carrier when needed.
If Sparqle is one of several carriers and you don't route every order to us,
check deliverability first and fall back elsewhere when we can't deliver.
## Preferred: check, then create
`POST /orders/check/{locationId}` with the delivery address and any
requirements (dimensions, weight, signature). We return whether we can
deliver, plus `resolvedAddress` — the geocoded address we checked against,
which may differ from the one you submitted.
`POST /orders`, then fetch the [label](/concepts/labels).
Route the order to your own fallback method or another carrier.
```mermaid theme={null}
flowchart TD
A[New order] --> B[POST /orders/check]
B --> C{Deliverable?}
C -->|Yes| D[POST /orders] --> E[GET label]
C -->|No| F[Fallback carrier]
```
## Alternative: create directly
You can skip the check and call `POST /orders` straight away. We validate
deliverability during creation and reject the order if we can't deliver. This
works, but you only learn the outcome after attempting to create — the two-step
flow is cleaner and preferred.
## Pickup point orders
An order that names a pickup point (`pudoId`) is routed to the carrier that
serves that point, whichever carrier would otherwise have won, and is rejected
when the address has no pickup point delivery available. It never falls back to
a normal home delivery — see [pickup points](/concepts/pickup-points).
# Environments
Source: https://docs.sparqle.com/environments
Test and live base URLs for the Sparqle API.
Sparqle runs two isolated environments. Each has its own base URL and its own
[API key](/authentication). Orders, labels, and webhooks never cross between
them.
| Environment | Base URL | Purpose |
| ----------- | --------------------------------- | ------------------------------------------------------------------- |
| **Test** | `https://staging-v2.sparqle.tech` | Build and validate your integration. No real courier is dispatched. |
| **Live** | `https://v2.sparqle.com` | Production. Creating an order dispatches a real delivery. |
Confirm the live base URL with your Sparqle contact before going to
production — it is environment-specific and may differ from the value above.
## Going live checklist
Move from `staging-v2.sparqle.tech` + test key to the live URL + live key.
Register your production webhook URL against the live environment. See
[Webhooks](/webhooks).
Live `locationId` values differ from test. Use the ones issued for
production.
Create, print the label, and confirm you receive the expected status
webhooks before enabling at scale.
# Errors & rate limits
Source: https://docs.sparqle.com/errors
How the Sparqle API reports failures and throttles requests.
The Sparqle API uses conventional HTTP status codes. `2xx` means success, `4xx`
means the request was rejected (usually something you can fix), and `5xx` means
a problem on Sparqle's side.
## Status codes
| Code | Meaning |
| ------------- | ------------------------------------------------------------------ |
| `200` / `201` | Success. |
| `400` | Validation error — a field is missing, malformed, or out of range. |
| `401` | Missing or invalid API key. |
| `403` | The key isn't allowed to act on this `locationId` or order. |
| `404` | The order, label, or resource doesn't exist. |
| `429` | Too many requests — you've hit the rate limit. |
| `500` | Unexpected error on Sparqle's side. |
## Validation errors
Request bodies are validated field by field. A `400` response lists what failed,
so you can surface the specific problem:
```json theme={null}
{
"statusCode": 400,
"message": [
"deliveryName should not be empty",
"deliveryAddress.postalCode should not be empty"
],
"error": "Bad Request"
}
```
Common causes:
* Missing a required field (`locationId`)
## Rate limits
Public tracking endpoints are throttled. If you exceed the limit you'll get a
`429` — back off and retry with exponential delay. If you expect sustained high
volume on the order endpoints, coordinate limits with your Sparqle contact.
Build retries with exponential backoff for `429` and `5xx` responses, and
treat `4xx` (except `429`) as permanent — retrying won't help until you fix the
request.
# Upload orders from a spreadsheet
Source: https://docs.sparqle.com/guides/manual-order-upload
Create multiple orders from an Excel or CSV file in the Sparqle dashboard.
Use a spreadsheet upload when you need to create multiple orders without an API integration. Each row creates one order for the company currently selected in the dashboard.
Start with the XLSX template containing the supported column headers and an example order.
## Before you start
* Sign in to the Sparqle dashboard with your company account.
* Find your pickup location ID (`locationId`) under **Settings → API**.
Every `locationId` in the spreadsheet must belong to your company.
## Get labels for uploaded orders
To generate labels during an upload, go to **Settings → Company**. Under **Labels**, enable **Upload labels** and save the setting before uploading your spreadsheet.
Uploads are limited to 1,000 orders while label generation is enabled.
After the upload finishes, Sparqle creates one PDF containing the labels for all successfully created orders. Download it using the **Labels PDF** button in the upload window or under **Recent company uploads**.
The user who submitted the spreadsheet also receives a completion email with a link to the upload in the dashboard. The labels PDF is downloaded from the dashboard and is not attached to the email.
## Prepare the spreadsheet
The uploader accepts `.xlsx`, `.xls`, and `.csv` files. For Excel files, only the first worksheet is imported. Keep the column names exactly as shown below and place one order on each row.
### Required columns
| Column | Description | Example |
| -------------------- | --------------------------- | ----------------- |
| `orderRef` | Your unique order reference | `ORDER-1001` |
| `locationId` | Sparqle pickup location ID | `XXXXXXXXX` |
| `deliveryStreet` | Recipient street | `Soerapatistraat` |
| `deliveryNumber` | Recipient house number | `100` |
| `deliveryPostalCode` | Recipient postal code | `1018PN` |
| `deliveryCity` | Recipient city | `Amsterdam` |
| `deliveryCountry` | Two-letter country code | `NL` |
### Optional columns
| Column | Description |
| --------------------------- | -------------------------------------------------- |
| `name` | Parcel or product name |
| `description` | Parcel description |
| `length`, `width`, `height` | Parcel dimensions |
| `weight` | Parcel weight |
| `pickupNote` | Pickup instructions |
| `deliveryName` | Recipient name |
| `deliveryEmail` | Recipient email address |
| `deliveryPhone` | Recipient phone number |
| `deliverySuffix` | House number suffix |
| `deliveryNote` | Delivery instructions |
| `deliveryDate` | Requested delivery date, formatted as `YYYY-MM-DD` |
| `signature` | Whether a signature is required |
| `notAtNeighbour` | Whether delivery to a neighbour is prohibited |
## Upload the orders
Go to **Orders** in the dashboard and select **Upload orders**.
Drop the file into the upload area or select it from your device. The dashboard checks the file type, headers, and number of rows before submission.
Check the detected row count, then select **Upload orders**. The orders are queued and processed in the background.
The uploader shows how many rows are processed, successful, or failed. You can close the window and return later; recent uploads are shared with other users in the same company.
## Review the result
An upload finishes with one of these results:
* **Completed**: every row created an order.
* **Completed with errors**: some rows succeeded and some failed.
* **Failed**: the upload could not finish.
Download the **Report** from the completed upload to review the created orders and any failed rows. The XLSX report contains separate **Successful Orders** and **Failed Orders** worksheets.
When **Upload labels** was enabled before submission, the completed upload also provides the **Labels PDF** described above.
Reports and label PDFs are available for 30 days. The upload remains visible in company history after its downloads expire.
To correct failed rows, update them in a new spreadsheet and upload that file. Do not include rows that already succeeded, because they would create new orders again.
# Sparqle API
Source: https://docs.sparqle.com/introduction
Create deliveries, fetch shipping labels, and track orders in real time.
The Sparqle API lets you plug Sparqle delivery into your own systems. Create an
order, print the label we generate, and receive status updates over webhooks as
the parcel moves from your warehouse to your customer's door.
## What you can do
Submit a delivery and receive a Sparqle `orderId`, barcode, and tracking URL.
Use **Check order** to validate an address and serviceability before creating.
Fetch a base64-encoded PDF label for a single order or a whole bundle.
Subscribe to status webhooks across the full order lifecycle.
## How it works
Every request is authenticated with an API key sent in the `api-key` header.
See [Authentication](/authentication).
`POST /orders` with the recipient address and your `locationId`. Sparqle
returns an `orderId`, a `barcode`, and a `trackingUrl`.
Fetch the label with `GET /orders/label/{orderId}` and print the PDF.
Receive [webhooks](/webhooks) as the order moves through its
[lifecycle](/concepts/order-lifecycle), or poll the order endpoint.
## Before you start
You'll need an **API key** and at least one **`locationId`** (the pickup
location orders are dispatched from). Both are provided by your Sparqle contact —
reach out to [support@sparqle.com](mailto:support@sparqle.com) if you don't have
them yet.
Create your first order in a few minutes.
# Quickstart
Source: https://docs.sparqle.com/quickstart
Create your first Sparqle delivery and print its label.
This guide walks through the full happy path: validate an address, create an
order, and fetch a printable label. You'll use the **test** environment
throughout — nothing here dispatches a real courier.
## Prerequisites
* An **API key** for the test environment
* A **`locationId`** for the pickup location
Both are issued by your Sparqle contact. See [Authentication](/authentication)
for where the key goes, and [Environments](/environments) for base URLs.
The test base URL is `https://staging-v2.sparqle.tech`. Replace it with the
live URL only once you're ready to ship real parcels.
## 1. Check the order
Optional but recommended: validate the recipient address and serviceability
before you commit. Pass your `locationId` in the path.
```bash theme={null}
curl -X POST https://staging-v2.sparqle.tech/orders/check/ABC123 \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"deliveryAddress": {
"street": "Gustav Mahlerlaan",
"houseNumber": "320",
"postalCode": "1082 ME",
"city": "Amsterdam",
"countryCode": "NL"
}
}'
```
The response tells you whether the address is deliverable and returns
`resolvedAddress` — the geocoded address the check actually ran against.
Geocoding may correct the address you submitted, so inspect `resolvedAddress`
to confirm what was used.
```json theme={null}
{
"deliverable": true,
"resolvedAddress": {
"street": "Gustav Mahlerlaan",
"houseNumber": "320",
"suffix": "",
"postalCode": "1082 ME",
"city": "Amsterdam",
"countryCode": "NL",
"coordinates": { "lng": 4.8721, "lat": 52.3382 }
}
}
```
## 2. Create the order
```bash theme={null}
curl -X POST https://staging-v2.sparqle.tech/orders \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"locationId": "ABC123",
"orderRef": "PADE3JWEA",
"deliveryName": "Jane Doe",
"deliveryEmail": "jane@example.com",
"deliveryPhone": "+31612345678",
"deliveryAddress": {
"street": "Gustav Mahlerlaan",
"houseNumber": "320",
"postalCode": "1082 ME",
"city": "Amsterdam",
"countryCode": "NL"
}
}'
```
A successful response returns the created order, including the fields you'll need
next:
```json theme={null}
{
"orderId": "SPQ-10293",
"orderRef": "PADE3JWEA",
"status": "draft",
"barcode": "3STBXX123456789",
"trackingUrl": "https://tracking.sparqle.com/barcode/3STBXX123456789"
}
```
`orderRef` is **your** reference for the order. Keep it stable and unique so
you can reconcile webhooks and labels against your own system.
## 3. Fetch the label
Use the `orderId` from the previous step. The label comes back as a
base64-encoded PDF.
```bash theme={null}
curl https://staging-v2.sparqle.tech/orders/label/SPQ-10293 \
-H "api-key: YOUR_API_KEY"
```
Decode the base64 string to a `.pdf` and print it. For multi-parcel shipments,
fetch every label in a bundle at once with
`GET /orders/label/bundle/{orderBundleId}`. See [Labels](/concepts/labels).
## 4. Track the delivery
As the parcel moves, Sparqle sends [webhooks](/webhooks) for each status change,
or you can poll the order. Statuses run from `draft` through to `completed` —
see the [order lifecycle](/concepts/order-lifecycle).
## Next steps
Keys, headers, and test vs. live.
Every status and what it means.
Receive status updates as they happen.
Full endpoint and schema details.
# Webhooks
Source: https://docs.sparqle.com/webhooks
Receive a POST to your endpoint whenever an order changes status.
Webhooks push order updates to you in real time, so you don't have to poll. When
an order changes [status](/concepts/order-lifecycle), Sparqle sends an HTTP
`POST` to the URL you've registered for that event.
## Setup
Webhook URLs are configured per company and per event (status). Provide your
endpoint(s) to your Sparqle contact, specifying which statuses you want to
receive. You can register different URLs for different statuses, or one URL for
all of them.
Webhooks are environment-specific. Register your production endpoint against
the **live** environment separately from any test endpoint.
## The request
Sparqle sends a `POST` with a fixed JSON payload. The shape is stable — fields
won't be removed or renamed as the platform evolves.
```http theme={null}
POST https://your-app.com/webhooks/sparqle
Content-Type: application/json; charset=UTF-8
```
```json theme={null}
{
"id": 1,
"orderId": "SPQ-10293",
"orderRef": "PADE3JWEA",
"status": "out_for_delivery",
"previousStatus": "in_transit",
"attempt": 1,
"barcode": "3STBXX123456789",
"name": "iPod",
"weight": 1500,
"length": 200,
"width": 75,
"height": 50,
"isBusiness": false,
"mailbox": false,
"locationId": 7,
"orderBundle": {
"id": 7419,
"bundleId": "4OXMV95NH7YSGP3ZERJD2"
},
"deliveryName": "Jane Doe",
"deliveryEmail": "jane@example.com",
"deliveryPhone": "+31612345678",
"deliveryAddress": "Gustav Mahlerlaan 320, 1082 ME Amsterdam, NL",
"deliveryAddressObject": {
"street": "Gustav Mahlerlaan",
"houseNumber": "320",
"suffix": "",
"postalCode": "1082 ME",
"city": "Amsterdam",
"countryCode": "NL"
},
"deliveryNote": "Leave at the door",
"deliveryDate": "2026-06-27T00:00:00.000Z",
"deliveryEta": "2026-06-26T12:40:00.000Z",
"trackingUrl": "https://tracking.sparqle.com/barcode/3STBXX123456789",
"createdAt": "2026-06-25T09:00:00.000Z",
"updatedAt": "2026-06-26T10:15:00.000Z",
"sortedAt": "2026-06-26T08:30:00.000Z",
"completedAt": "2026-06-26T13:05:00.000Z",
"signee": "J. Doe",
"signatureImage": "data:image/png;base64,iVBORw0KG...",
"riderNote": "Rang doorbell twice",
"pods": ["https://cdn.sparqle.com/pod/abc.jpg"],
"neighbourAddress": "Gustav Mahlerlaan 322, 1082 ME Amsterdam"
}
```
| Field | Type | Description |
| ----------------------- | --------------- | ----------------------------------------------------------------------------------------------------- |
| `id` | number | Sparqle's internal order id. |
| `orderId` | string | Sparqle's order identifier. |
| `orderRef` | string | Your reference, as supplied on create. |
| `status` | string | The [status](/concepts/order-lifecycle) the order just entered. |
| `previousStatus` | string \| null | The status it moved from. |
| `attempt` | number | Delivery attempt count. |
| `barcode` | string \| null | Parcel barcode. |
| `name` | string \| null | Order name. |
| `weight` | number \| null | Parcel weight in grams. |
| `length` | number \| null | Parcel length in mm. |
| `width` | number \| null | Parcel width in mm. |
| `height` | number \| null | Parcel height in mm. |
| `isBusiness` | boolean \| null | Whether the delivery is to a business address. |
| `mailbox` | boolean | Whether it's a mailbox delivery. |
| `locationId` | number \| null | Pickup location the order belongs to. |
| `orderBundle` | object \| null | Bundle the order is grouped in (`id`, `bundleId`), if any. |
| `deliveryName` | string \| null | Recipient name. |
| `deliveryEmail` | string \| null | Recipient email. |
| `deliveryPhone` | string \| null | Recipient phone. |
| `deliveryAddress` | string \| null | Formatted delivery address. |
| `deliveryAddressObject` | object \| null | Structured delivery address (`street`, `houseNumber`, `suffix`, `postalCode`, `city`, `countryCode`). |
| `deliveryNote` | string \| null | Delivery instructions from the recipient. |
| `deliveryDate` | string \| null | Requested delivery date (ISO 8601). |
| `deliveryEta` | string \| null | Estimated delivery time (ISO 8601). |
| `trackingUrl` | string \| null | Public tracking page. |
| `createdAt` | string | When the order was created (ISO 8601). |
| `updatedAt` | string | When the order was updated (ISO 8601). |
| `sortedAt` | string \| null | When the parcel was scanned at central sorting (ISO 8601). |
| `completedAt` | string \| null | When the order was delivered (ISO 8601). |
| `signee` | string \| null | Name of the person who signed for delivery. |
| `signatureImage` | string \| null | Base64-encoded signature image, when captured. |
| `riderNote` | string \| null | Note left by the rider. |
| `pods` | string\[] | Proof-of-delivery photo URLs. Empty when none. |
| `neighbourAddress` | string \| null | Address it was delivered to, if left with a neighbour. |
Match incoming webhooks to your own records on `orderRef` (your reference) or
`orderId` (Sparqle's). The `status` field tells you which lifecycle stage the
order just entered; `previousStatus` lets you ignore out-of-order or duplicate
deliveries.
## Responding
Return a `2xx` status code as quickly as possible to acknowledge receipt. Do any
heavy processing asynchronously after you've responded — a slow endpoint can
cause retries or timeouts.
## Best practices
* **Be idempotent.** The same status may be delivered more than once; dedupe on
`orderId` + `status`.
* **Don't assume ordering.** Network timing means events can arrive out of
order. Trust the `status` value, and ignore transitions that move backwards.
* **Verify the source.** Restrict your endpoint to Sparqle (for example with a
shared secret path or an allowlist) and only accept expected statuses.