API Issues
Errors and fixes for integrations using the DPD Label Print REST API.
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:
| Check | Expected |
|---|---|
| Header name | Authorization (capital A) |
| Header value prefix | Bearer (with a space after Bearer) |
| Token source | Response token field from /api/v1/login |
| Token not expired | Check expireAt via /api/v1/login-extended |
| No extra characters | Trim 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?
- Confirm you are calling the correct environment URL (dev vs staging vs production)
- Verify your account is active — contact DPD support
- 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:
emailis required for all non-CH/LI destinations (except GB)phoneis required for GB destinations (international format:+44...)houseNumberis required for NL destinationsstateCodeis 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:
| Format | Value | Use 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
Empty results from address search
Cause: No shops found within the default 5 km search radius, or the address could not be resolved.
Fix:
- Try providing both
zipCodeandcity— the API falls back to postal code if the full address cannot be geocoded - Remove service/type filters to widen the search
- Set
hideClosed: falseto 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:
- Verify the parcel number from the shipment creation response (
success[].parcelNumber) - Allow a few minutes after shipment creation for tracking to become available
- 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.