Shipping Labels (DPD)
Create DPD Slovenia parcels for the orders that reach Space Invoices from your store, print their labels and track delivery. Shipments are created through the API; each one belongs to an order or stands alone.
You need a DPD EasyShip web-service account (a username and password). Orders come from any order integration or from POST /orders.
How It Works
- Connect your DPD account once.
- For an order, get a shipment draft: the recipient, parcel type and cash on delivery derived from the order.
- Review the draft, then create the shipment. Space Invoices registers the parcel with DPD, prints the label and stores it.
- Download the label, merge several labels into one PDF, or cancel the parcel.
Connect a DPD Account
// The credentials are verified with DPD before they are saved. The password is never returned.
const connection = await sdk.shippingConnections.create({
carrier: "dpd_si",
username: "shop-demo",
password: "your-dpd-password",
settings: {
label_format: "pdf_a6",
default_weight_kg: 2.5,
cod_payment_methods: ["cod", "cash"],
},
});
console.log("Connection:", connection.id);The username and password are checked with DPD before anything is saved. DPD refusing them answers 422 with code invalid_carrier_credentials; DPD not answering returns 502 with code carrier_unavailable. The password is stored encrypted and never returned.
An entity has one connection per carrier; a second one answers 409 with code shipping_connection_exists. Delete the connection (DELETE /shipping-connections/{id}) to connect a different account. Existing shipments stay available.
Reading connections needs read access to the entity. Connecting, changing or deleting one needs permission to manage entity settings.
Settings
All settings are optional. Update them later with PATCH /shipping-connections/{id}; omitted settings keep their value.
| Setting | Default | What it means |
|---|---|---|
label_format | pdf_a6 | Label page format: pdf_a6 or pdf_a4. |
default_parcel_count | 1 | Parcels per shipment, from 1 to 30. |
default_weight_kg | none | Weight sent to DPD when a shipment has none. Send null to clear it. |
cod_payment_methods | ["cod", "cash"] | Order payment methods or gateways (not case-sensitive) that mean cash on delivery. |
push_tracking_to_store | true | Add the tracking number to the store order as a customer note. WooCommerce orders only. |
sender | none | Sender address sent with each parcel. Without it DPD uses the sender registered on your account. Send null to clear it. |
With is_active: false a connection stops being used for new shipments without being deleted.
Get a Draft for an Order
const draft = await sdk.shipments.getOrderShipmentDraft("ord_123");
console.log(draft.recipient, draft.parcel_type, draft.cod);
// Review anything that needs attention before creating the shipment.
for (const warning of draft.warnings) {
console.log(`${warning.field}: ${warning.code}`);
}The draft is built from the order:
- Recipient: the shipping address, or the billing address when the order has no shipping address. The street and house number are split from the address line (
Slovenska cesta 12a,Ulica 5/a,12 Main Street). A second address line is appended to the street only when it still fits in 35 characters. - Parcel type:
businesswhen the order has a company name, otherwisehome. For a company, the person’s name becomes the contact. - Phone: the address phone, else the customer’s phone, normalized to international digits (
040 123 456for a Slovenian recipient becomes38640123456). - Cash on delivery: set when the order is unpaid and its payment method or gateway is in
cod_payment_methods. The amount is the order total with tax. Cash on delivery is available for EUR orders only. - Parcel count, weight, reference: the connection’s defaults and the order number.
The draft never shortens a value to fit DPD’s limits. Problems are listed in warnings, each with a field and a code:
| Code | Meaning |
|---|---|
house_number_missing | No house number was found in the address line. |
address_2_dropped | The second address line did not fit and was left out. |
phone_invalid | The phone number could not be normalized and was left out. |
contact_missing | A home delivery has neither a phone number nor an email. |
cod_currency_unsupported | The order is unpaid cash on delivery but not in EUR, so the draft has no COD. |
name_missing, street_missing, city_missing, post_code_missing, country_missing | A required recipient field is empty. |
name_too_long, street_too_long, and so on | A value exceeds DPD’s limit. |
With one active connection the draft uses it; pass shipping_connection_id to choose another. Without an active connection the answer is 422 with code shipping_connection_required.
Create a Shipment
// Every field you leave out comes from the order's shipment draft.
const shipment = await sdk.shipments.create({
order_id: "ord_123",
parcel_count: 1,
weight_kg: 2.5,
});
console.log("Status:", shipment.status);
console.log("Tracking numbers:", shipment.tracking_numbers);
console.log("Label ready:", shipment.label_available);With order_id, every field you omit comes from the order’s draft. Send a field to override it:
recipientreplaces the whole address.cod: nullships without cash on delivery even when the draft has it. An explicitcodof{ amount }collects that amount in EUR; the reference defaults to the order number.reference(the order number printed on the label),remark(a note for the courier),parcel_type,parcel_countandweight_kgoverride their draft values.
Without order_id, recipient and parcel_type are required:
// Without an order_id, recipient and parcel_type are required.
const shipment = await sdk.shipments.create({
recipient: {
name: "Ana Novak",
street: "Slovenska cesta",
house_number: "12a",
city: "Ljubljana",
post_code: "1000",
country: "SI",
phone: "38640123456",
email: "ana@example.com",
},
parcel_type: "home",
parcel_count: 1,
weight_kg: 1.2,
cod: { amount: 49.9, reference: "1001" },
reference: "1001",
});
console.log(shipment.id, shipment.tracking_urls);Values are checked against DPD’s limits before DPD is contacted. A value that does not fit is answered with 422 and the field in the error:
| Field | Limit |
|---|---|
recipient.name, contact, street, city | 35 characters |
recipient.house_number | 8 characters |
recipient.post_code | 9 characters |
recipient.email, remark | 50 characters |
recipient.phone | 30 digits |
reference | 20 characters |
cod.reference | 14 characters |
parcel_count | 1 to 30 |
name, street, house_number, city, post_code and a two-letter country are required.
The response is 201 with the shipment. It has tracking_numbers, tracking_urls (a tracking link for each parcel) and label_available. If the label cannot be printed or stored, the shipment is still created: label_available is false and error says why. Downloading the label tries again.
For a WooCommerce order on a connection with push_tracking_to_store, the tracking number is added to the store order as a customer note, in Slovenian when the order’s document language is Slovenian and in English otherwise. If that fails, tracking_push_error records why; the shipment is unaffected.
An order that already has a shipment in status creating, created or unknown answers 409 with code shipment_exists and the existing shipment_id. Send allow_additional: true to create another one, for example for a second parcel.
When DPD rejects the parcel or does not answer
| Answer | Meaning |
|---|---|
422 with DPD’s code, for example dpd_es_327, and a field when known | DPD refused the parcel. Nothing was created; fix the value and try again. |
422 invalid_carrier_credentials | DPD no longer accepts the saved credentials. Update the connection. |
502 carrier_outcome_unknown with a shipment_id | DPD did not answer, so a parcel may exist. The shipment is kept with status unknown. Check the DPD portal before creating it again. |
Shipment statuses
| Status | Meaning |
|---|---|
creating | Being registered with DPD. A shipment still creating after five minutes is treated as unknown. |
created | The parcel is registered with DPD. |
unknown | DPD never confirmed the parcel. It blocks a new shipment for the order like a live one. |
cancelled | The parcel was cancelled. |
carrier_status holds DPD’s latest parcel status, for example DELIVERED. It is refreshed periodically (a shipment is checked at most every four hours) for 30 days after creation, until DPD reports the parcel delivered, cancelled or returned.
Download a Label
curl https://eu.spaceinvoices.com/shipments/shp_123/label \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-entity-id: ent_123" \
-o label.pdfThe response is a PDF. When the label was never stored, it is printed at DPD again. If DPD will not print it a second time, the answer is 409 with code label_unavailable; download it from the DPD portal instead. A cancelled shipment, or one whose outcome is unknown, answers 409.
Download Several Labels as One PDF
curl -X POST https://eu.spaceinvoices.com/shipments/labels \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-entity-id: ent_123" \
-H "Content-Type: application/json" \
-d '{ "shipment_ids": ["shp_123", "shp_456"] }' \
-o labels.pdfSend 1 to 50 shipment IDs. The pages follow the order you give. If any shipment has no label that can be downloaded, the answer is 409 with code labels_unavailable and the shipment_ids concerned. An unknown ID answers 404.
Cancel a Shipment
const shipment = await sdk.shipments.cancelShipment("shp_123");
console.log(shipment.status); // __PROTECTED_1__A parcel whose label was never printed is deleted at DPD; one that was printed is cancelled. If DPD refuses, the answer is 422 and the shipment keeps its status. A cancelled shipment answers 409 with code shipment_already_cancelled. A cancelled shipment no longer blocks a new one for the order.
A shipment in status unknown is refused with 409 and code shipment_outcome_unknown. After checking in the DPD portal that no parcel exists, cancel it without contacting DPD:
// Only after checking in the DPD portal that no parcel exists.
await sdk.shipments.cancelShipment("shp_123", { local_only: true });Webhooks
Subscribe to these webhook events:
| Event | Trigger |
|---|---|
shipment.created | A parcel was registered with DPD. |
shipment.cancelled | A shipment was cancelled. |
Both carry id, order_id, shipping_connection_id, carrier, status, tracking_numbers and tracking_urls.
Permissions
Reading shipments, drafts and labels needs read access to the entity. Creating and cancelling shipments and merging labels needs permission to make changes (the same as creating orders).
Related Guides and API
- WooCommerce Invoicing — connect the store the orders come from
- Webhooks — event delivery and signatures
- Shipments API — shipments, drafts and labels
- Shipping Connections API — carrier accounts