Developers
Get Started
OpenAPI ↗
REST API v1 · Developer previewLive API credentials are not yet generally available. Hosted test access is not enabled.

Getting started

Your first test booking

A complete local integration in minutes. Every label is a void test artifact.

1. Get test credentials

From the repository, open two terminals. Install the isolated platform dependencies and start the API in the first. Node 22.14 or newer is required; SQLite currently emits an experimental warning on Node 22.

cd developer-platform
npm ci
npm run sandbox

The server listens only on 127.0.0.1:4010. A 256-bit test key is saved in the gitignored .sandbox/key file. Keep this file private. The fixture account contains one synthetic imported order. Credentials for a hosted test environment are not yet available.

2. Set your environment

PowerShell:

$env:PX_API_KEY = Get-Content .sandbox/key -Raw
$env:PX_BASE_URL = "http://127.0.0.1:4010/v1"

macOS / Linux:

export PX_API_KEY="$(cat .sandbox/key)"
export PX_BASE_URL="http://127.0.0.1:4010/v1"

3. Request a quote and select a service

The complete Node.js example below requests a domestic quote and selects the drop-off service. Collection services require a date, ready time and closing time. Prices are fixture values, quote validity is 15 minutes, and no money moves.

4–7. Create, label and track

Save this as first-booking.mjs in a temporary working directory, then run node first-booking.mjs. Retain the idempotency key with your order; the example creates one per execution. Reuse that exact key and body if your request times out.

import { randomUUID } from 'node:crypto';
import { writeFile } from 'node:fs/promises';
const base = process.env.PX_BASE_URL;
const key = process.env.PX_API_KEY;
if (!base || !key) throw new Error('Set PX_BASE_URL and PX_API_KEY');
const headers = { Authorization: 'Bearer ' + key, 'Content-Type': 'application/json' };
async function request(path, method = 'GET', body, idempotencyKey) {
  const response = await fetch(base + path, {
    method, headers: { ...headers, ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}) },
    ...(body ? { body: JSON.stringify(body) } : {})
  });
  if (!response.ok) throw new Error(JSON.stringify(await response.json()));
  return response;
}
const parcels = [{ weight: 2, length: 30, width: 20, height: 15 }];
const quotes = await (await request('/quotes', 'POST', {
  collection_country: 'GB', collection_postcode: 'NN1 1AA',
  delivery_country: 'GB', delivery_postcode: 'SW1A 1AA', parcels
})).json();
const selected = quotes.data.find(q => q.handover === 'drop_off');
if (!selected) throw new Error('No drop-off quote available');
const sender = { name: 'Test Sender', line1: '1 Test Street', city: 'Northampton',
  postcode: 'NN1 1AA', country: 'GB', email: 'sender@example.com', phone: '+441234567890' };
const idempotencyKey = 'order-' + randomUUID();
console.log('Retain this key for retries:', idempotencyKey);
const shipment = await (await request('/shipments', 'POST', {
  quote_id: selected.id, sender,
  recipient: { ...sender, name: 'Test Recipient', city: 'London', postcode: 'SW1A 1AA' },
  parcels, contents: 'Cotton T-shirts', merchant_reference: 'ORDER-1001'
}, idempotencyKey)).json();
const label = await request('/shipments/' + shipment.id + '/label');
await writeFile('VOID-test-label.pdf', Buffer.from(await label.arrayBuffer()));
console.log(shipment.reference);
console.log(await (await request('/shipments/' + shipment.id + '/tracking')).json());
const webhook = await (await request('/webhook-endpoints', 'POST', {
  url: 'https://merchant.example.com/webhooks/px', event_types: ['shipment.booked', 'tracking.updated']
}, 'webhook-' + randomUUID())).json();
console.log('Webhook registered:', webhook.id); // save webhook.signing_secret in a secret manager

8. Configure webhook verification

Registration returns a signing secret once. The sandbox queues events for subsequently created shipments; it sends no HTTP deliveries. Verify the HMAC using the guide and test fixtures. Register before booking if you want shipment events in the simulated outbox. The repeat registration response is available to the same account through its idempotency key, so protect those records.

9. Prepare for live mode

Live mode is unavailable. Before live credentials can be issued, PX must validate tenant mapping, billing authorization, purchase fencing, distributed limits and outbound delivery in staging. When released, use a separate px_live key, the HTTPS API host and real account funding. Test keys will never authorize a live label. Do not send actual customer data to the local sandbox.