Skip to content

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

  1. Connect your DPD account once.
  2. For an order, get a shipment draft: the recipient, parcel type and cash on delivery derived from the order.
  3. Review the draft, then create the shipment. Space Invoices registers the parcel with DPD, prints the label and stores it.
  4. Download the label, merge several labels into one PDF, or cancel the parcel.

Connect a DPD Account

Connect a DPD accounttypescript
// 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.

SettingDefaultWhat it means
label_formatpdf_a6Label page format: pdf_a6 or pdf_a4.
default_parcel_count1Parcels per shipment, from 1 to 30.
default_weight_kgnoneWeight 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_storetrueAdd the tracking number to the store order as a customer note. WooCommerce orders only.
sendernoneSender 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

Get a shipment draft for an ordertypescript
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: business when the order has a company name, otherwise home. 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 456 for a Slovenian recipient becomes 38640123456).
  • 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:

CodeMeaning
house_number_missingNo house number was found in the address line.
address_2_droppedThe second address line did not fit and was left out.
phone_invalidThe phone number could not be normalized and was left out.
contact_missingA home delivery has neither a phone number nor an email.
cod_currency_unsupportedThe 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_missingA required recipient field is empty.
name_too_long, street_too_long, and so onA 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

Create a shipment for an ordertypescript
// 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:

  • recipient replaces the whole address.
  • cod: null ships without cash on delivery even when the draft has it. An explicit cod of { 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_count and weight_kg override their draft values.

Without order_id, recipient and parcel_type are required:

Create a shipment with your own valuestypescript
// 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:

FieldLimit
recipient.name, contact, street, city35 characters
recipient.house_number8 characters
recipient.post_code9 characters
recipient.email, remark50 characters
recipient.phone30 digits
reference20 characters
cod.reference14 characters
parcel_count1 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

AnswerMeaning
422 with DPD’s code, for example dpd_es_327, and a field when knownDPD refused the parcel. Nothing was created; fix the value and try again.
422 invalid_carrier_credentialsDPD no longer accepts the saved credentials. Update the connection.
502 carrier_outcome_unknown with a shipment_idDPD 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

StatusMeaning
creatingBeing registered with DPD. A shipment still creating after five minutes is treated as unknown.
createdThe parcel is registered with DPD.
unknownDPD never confirmed the parcel. It blocks a new shipment for the order like a live one.
cancelledThe 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

Download a labelbash
curl https://eu.spaceinvoices.com/shipments/shp_123/label \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "x-entity-id: ent_123" \
  -o label.pdf

The 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

Download several labels as one PDFbash
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.pdf

Send 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

Cancel a shipmenttypescript
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:

Cancel a shipment whose outcome is unknowntypescript
// 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:

EventTrigger
shipment.createdA parcel was registered with DPD.
shipment.cancelledA 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).