Developers
Build on ParcelOps
Quote, book, track and pull documents for your shipments over a clean REST API, and let webhooks push status changes to you. The whole reference is on this page. No sales call between you and the docs.
Quick start
Your first request in under a minute
Selling on WooCommerce or Shopify? You don’t need to write code: the free WooCommerce plugin and the free Shopify app book with your courier and bring the tracking back to your store, with the same API key.
- 01Log in to your courier’s customer portal and create a key under Integrations → API Keys. The key sees your account’s shipments and invoices, nothing else, and has the permissions of the portal user who creates it. It never manages passwords, users, other keys, billing details or settings: those stay with a signed-in portal user.
- 02Send it as a Bearer token:
curl https://api.parcelops.co.za/api/v1/customer/shipments \
-H "Authorization: Bearer po_YOUR_API_KEY"No key yet? Test with a free trial
There is no shared test key. Start a free trial and you get your own workspace for 7 days, filled with sample customers, shipments and invoices. In its dashboard, open Explore → Customer portal, create a key under Integrations → API Keys, and every request below works against your sample data. Building for a courier that already runs ParcelOps? Ask them for a test customer account.
Booking from a shop
Retry safely, and tell the courier which order it is
- Idempotency key. Send
idempotencyKeyin the body of a booking. The same key with the same booking gives back the first answer, with HTTP 200 andIdempotent-Replay: true, and never books twice, even when two requests arrive at once. A retry that changes the booking counts as a different booking and is refused (409): a re-quoted, added or droppedquotedCharge, a phone written +27 21 instead of 021, changed instructions, emails, needs or collection window. Resend exactly the first body. A key is 8 to 100 ASCII characters (letters without accents, digits and printable symbols) and must be a string. Keys belong to your account and do not expire. A replay is a snapshot of the first answer, not the booking’s state today: read the current state fromGET /customer/bookings?search=<referenceNumber>orGET /customer/shipments/{id}. - Your shop order.
source(API, WOOCOMMERCE or SHOPIFY),externalOrderRefandexternalOrderUrltell the courier’s staff which order a waybill is for. Your customer never sees them; they come back on the shipment and in its webhooks. - Check an address first.
POST /customer/address-checksays whether the courier knows a suburb, how it writes it, what it might be instead, and whether the courier prices parcels there. The pricing answer is a guide, never a promise: the quote is the authority. Provinces can be names or codes such as GP or KZN. When a suburb and its postal code are in different provinces, the answer (and the booking’s) also carrieswarningswith a message to show and the likely postal code: a warning, never a refusal.
curl https://api.parcelops.co.za/api/v1/customer/book \
-H "Authorization: Bearer po_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "idempotencyKey": "myshop:order-1042", "serviceLevel": "ECONOMY", ... }'For couriers on ParcelOps
Connect your own systems with a company key
Run your courier business on ParcelOps and want your website, warehouse system or ERP to talk to it? A workspace admin creates a company key under Settings → API Keys and ticks the scopes it needs. It is sent the same way, as a Bearer token with a po_ prefix.
- Nothing beyond its scopes. Each scope opens a fixed list of endpoints, listed under Company API in the reference below. Every other endpoint is refused, including settings, users and permissions.
- Never more than its owner. A key acts as the admin who created it and needs their permission too. It stops working when it expires or is revoked, or when that admin leaves or is no longer an admin.
- Shown once, then only a hash. Work done with a key is recorded in your audit log against its owner and the key.
Want to try one first? In a free trial workspace you are the admin: open Settings → API Keys and create a company key.
Webhooks
Eight events, pushed to you
Subscribe from the portal (Integrations → Webhooks) or the API, and ParcelOps POSTs each event to your endpoint as it happens, for every shipment on your account. No polling.
shipment.createdYour booking was accepted: the shipment exists, with its waybill number and collection dayshipment.collectedThe parcel has been picked up from the sendershipment.out_for_deliveryOn a vehicle for final deliveryshipment.deliveredDelivered, with POD capturedshipment.partially_deliveredSome parcels of a multi-parcel shipment were delivered, the rest were notshipment.failed_deliveryA delivery attempt failed, with the reasonbooking.cancelledThe courier cancelled your booking before it became a shipmentinvoice.issuedAn invoice has been raised on your accountinvoice.paidA payment has been receipted against an invoiceThe delivery contract
- Every delivery is signed:
X-Parcelops-Signaturecarries an HMAC-SHA256 of the raw body, keyed with your endpoint’s secret. X-Parcelops-Delivery-Idis unique per delivery and stays the same across its retries: use it to deduplicate.- Respond with any 2xx within 15 seconds. Otherwise it is retried after 1, 2, 4, 8, 16, 32 and 64 minutes: eight attempts over about two hours.
- Shipment events carry the
shipmentId, waybill number, your reference and the new status; a new shipment also carries your booking’s reference and its collection day, a failed delivery the reason, and a delivery who signed and when. - Rotate a secret without downtime: for 24 hours after a rotation, deliveries are also signed with the old secret in
X-Parcelops-Signature-Previous.
Example payload
{
"version": "1",
"id": "f61eddff-a05a-4fbf-a8aa-60f52b093f6e",
"event": "shipment.delivered",
"timestamp": "2026-09-29T11:02:10.412Z",
"tenantSlug": "your-courier",
"data": {
"shipmentId": "c6459b89-8531-4ec5-b15d-908549caa221",
"waybillNumber": "ABC-002199",
"status": "DELIVERED",
"occurredAt": "2026-09-29T11:02:10.405Z",
"customerReference": "PO-2026-0042",
"externalOrderRef": "Woo #1042",
"trackingUrl": "https://your-courier.parcelops.co.za/online/track?q=ABC-002199",
"deliveredAt": "2026-09-29T11:02:10.000Z",
"pod": {
"recipientName": "Thandi Mokoena",
"capturedAt": "2026-09-29T11:02:10.000Z"
},
"podUrl": "https://api.parcelops.co.za/api/v1/customer/shipments/c6459b89-8531-4ec5-b15d-908549caa221/pod",
"podUrlNeedsKey": true
}
}Verifying the signature (Node)
import crypto from "crypto";
// X-Parcelops-Signature: sha256=<hex hmac of the raw body>
// During a secret rotation, also try X-Parcelops-Signature-Previous.
function verify(rawBody, header, secret) {
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return typeof header === "string" && header.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Reference
Every endpoint, in full
The customer API and, under Company API, every endpoint a company key can call: request and response schemas, try-it-out, and copy-paste examples in your language.
Every customer API response shown is a real one, from a workspace of sample data. To send a request from this page, paste your own key into Authentication first; a free trial gives you one in a few minutes.
Get started
Ship your integration this week
An API key from the portal is all you need. If you get stuck, a developer answers, not a ticket queue.