# 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 Example Sparqle shipping label [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.