Skip to main content

API Issues

Errors and fixes for integrations using the DPD Label Print REST API.

note

If you are using Online Label Print or the Label Print App rather than the API, see Login Issues, File Import Issues or Printer Issues.


Authentication

HTTP 401 Unauthorized

{
"status": 401,
"error": "Unauthorized",
"message": "JWT token is missing or invalid"
}

Checklist:

CheckExpected
Header nameAuthorization (capital A)
Header value prefixBearer (with a space after Bearer)
Token sourceResponse token field from /api/v1/login
Token not expiredCheck expireAt via /api/v1/login-extended
No extra charactersTrim whitespace before/after the token string

HTTP 401 — Token expired

Tokens have a finite TTL configured on the server. Once expired, every request returns 401.

Solution: Re-authenticate:

curl -X POST "https://label-print-shipments.dpd.ch/api/v1/login" \
-H "Content-Type: application/json" \
-d '{"username": "your_user", "password": "your_pass"}'

Prevention — proactively refresh before expiry:

// Store expiry time from /login-extended
const tokenExpiry = new Date(loginResponse.expireAt);

async function getValidToken() {
const fiveMinuteBuffer = 5 * 60 * 1000;
if (Date.now() > tokenExpiry.getTime() - fiveMinuteBuffer) {
await reAuthenticate();
}
return currentToken;
}

Token expires too quickly

Cause: Token TTL is configured server-side.

Fix: Contact your DPD account manager to adjust the token TTL for your account. In the meantime, implement token refresh logic that re-authenticates before expiry.


HTTP 400 — Missing username or password

{
"status": 400,
"errors": [{ "field": "username", "message": "must not be blank" }]
}

Fix: Ensure both username and password are present and non-empty in the request body.


HTTP 403 — Forbidden (login-extended)

{
"status": 403,
"message": "Auto-login context is missing"
}

Cause: /api/v1/login-extended requires an auto-login context that is not set up for your account.

Fix: Use /api/v1/login instead, or contact your DPD account manager to enable the extended login for your account.


Credentials correct but still 401?

  1. Confirm you are calling the correct environment URL (dev vs staging vs production)
  2. Verify your account is active — contact DPD support
  3. Check whether your IP address needs to be whitelisted for API access

Shipments

400 Bad Request — validation error

Cause: A required field is missing or fails validation.

Fix: Check the fieldErrors array in the response — it's a flat list, one entry per violation, each with its own path, code and message (the same code can appear at more than one path, e.g. both sender.email and receiver.email):

{
"fieldErrors": [
{ "path": "receiver.email", "code": "SHP-VAL-EMAIL-REQUIRED", "message": "Email address is required." },
{ "path": "receiver.countryCode", "code": "SHP-VAL-COUNTRYCODE-ALLOWED-CHARACTERS", "message": "Country code contains invalid characters." }
]
}

Common field requirements:

  • email is required for all non-CH/LI destinations (except GB)
  • phone is required for GB destinations (international format: +44...)
  • houseNumber is required for NL destinations
  • stateCode is required for US/CA destinations

207 Multi-Status — partial batch failure

Cause: In a batch request some shipments succeeded and others failed.

Fix: Check the failed array in the response for per-item fieldErrors. Use each item's identificationNumber (echoes the client-supplied value, when the request item had one) to map a failure back to the input. The success array contains created shipments with their parcel numbers and labels. If instead every item failed, the response is always 400 Bad Request, whatever the cause — including a routing-engine outage, which is still reported per item via errorCode: SHP-ROUTING-ENGINE-UNAVAILABLE.


Labels

Labels in the API response

A successful shipment creation returns each label as a Base64-encoded PDF string in success[].label.

{
"success": [
{
"id": 1,
"parcelNumber": "05305000123456",
"label": "JVBERi0xLjQKJeLjz9MK..."
}
]
}

Decoding and saving the label

Shell:

echo "JVBERi0xLjQK..." | base64 --decode > label.pdf

JavaScript (browser):

function downloadLabel(base64Label, parcelNumber) {
const bytes = atob(base64Label);
const array = new Uint8Array(bytes.length);
for (let i = 0; i < bytes.length; i++) array[i] = bytes.charCodeAt(i);

const blob = new Blob([array], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);

const a = document.createElement('a');
a.href = url;
a.download = `label-${parcelNumber}.pdf`;
a.click();
URL.revokeObjectURL(url);
}

Java:

byte[] pdfBytes = Base64.getDecoder().decode(labelBase64);
Files.write(Path.of("label-" + parcelNumber + ".pdf"), pdfBytes);

Choosing the right label format

Set printOptions.paperSize in your shipment request:

FormatValueUse case
A4"A4"Standard office printers. Up to 4 labels per sheet — control position with startPosition (UPPER_LEFT, UPPER_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT)
Thermal / A6"A6"Direct thermal label printers (Zebra, Citizen, etc.)
"printOptions": {
"paperSize": "A6"
}

Multiple labels on one A4 sheet

When printing multiple shipments, pack them onto A4 sheets to save paper:

[
{ ..., "printOptions": { "paperSize": "A4", "startPosition": "UPPER_LEFT" } },
{ ..., "printOptions": { "paperSize": "A4", "startPosition": "UPPER_RIGHT" } },
{ ..., "printOptions": { "paperSize": "A4", "startPosition": "BOTTOM_LEFT" } },
{ ..., "printOptions": { "paperSize": "A4", "startPosition": "BOTTOM_RIGHT" } }
]

Label is empty, blank or corrupted

Cause 1: printOptions.paperSize mismatch with your printer.

Fix: Set paperSize explicitly — "A4" for full sheets (with startPosition), "A6" for thermal label printers.

Cause 2: Base64 decoding error — extra whitespace or line breaks in the string.

Fix: Strip all whitespace before decoding:

const cleanBase64 = base64String.replace(/\s+/g, '');

Cause 3: PDF viewer compatibility.

Fix: Try opening the PDF in a different viewer. Labels are valid PDF 1.4+ files.

Cause 4: Thermal printer DPI mismatch.

Fix: Configure your printer to 203 or 300 DPI (check your printer's manual). The label format is designed for standard DPD thermal printers.


Parcel Shops

Cause: No shops found within the default 5 km search radius, or the address could not be resolved.

Fix:

  1. Try providing both zipCode and city — the API falls back to postal code if the full address cannot be geocoded
  2. Remove service/type filters to widen the search
  3. Set hideClosed: false to include shops that may be temporarily closed

404 Not Found for parcel shop ID

Cause: The shop ID does not exist or is no longer active.

Fix: Re-search by address or coordinates to get current shop IDs. Parcel shop IDs can change when shops close or reopen.


Tracking

404 Not Found for parcel number

Cause: Tracking data is not yet available or the parcel number is incorrect.

Fix:

  1. Verify the parcel number from the shipment creation response (success[].parcelNumber)
  2. Allow a few minutes after shipment creation for tracking to become available
  3. Ensure the parcel number format is correct (e.g., 05305000123456)

Collection Requests & Pickup Orders

pickupDate rejected

Cause: The date is in the past or incorrectly formatted.

Fix: Use yyyy-MM-dd format and ensure the date is at least tomorrow's date in the European timezone.


General

HTTPS / SSL errors

All API calls must use HTTPS. Self-signed certificates are not accepted in production.

Rate limiting (429 Too Many Requests)

Implement exponential backoff in your application. Consider caching parcel shop data (it changes infrequently) to reduce API calls.