Ecoparcel API (1.0.7)

Download OpenAPI specification:

Shipping order creation, tracking, and price estimation.

Changelog

The API follows Semantic Versioning: MAJOR.MINOR.PATCH. Backwards-incompatible changes bump MAJOR, additive (backwards-compatible) features bump MINOR, and bug fixes / non-breaking improvements bump PATCH.

1.0.7 — 2026-05-07

  • Added event_code field to each tracking event in GET /api/tracking/{order_id}/. The code is a stable 4-digit identifier (see the Tracking Event Codes section below) that integrators can map to their own status taxonomy without parsing the localized event_description.

1.0.6 — 2026-05-02

  • Enforced minimum parcel weight of 1.00 kg on order create. Parcels with weight < 1.00 are bumped to 1.00 before pricing.
  • Added a per-order warning listing the affected Parcel IDs, e.g. Parcel weight below minimum (1 kg) — bumped to 1 kg for parcel(s): EPDQRZ01, EPDQRZ03.

1.0.5 — 2026-04-23

  • Shipping price calculation now applies user-specific VAT and business pricing.
  • The /api/shipping-price/ endpoint returns prices tailored to the authenticated account.

1.0.4 — 2026-04-12

  • Added optional label_format override on order create/update to force a specific label format (e.g. A4, A6) regardless of the carrier default.
  • /api/order/{order_id}/labels/ honours the per-order format override.

1.0.3 — 2026-03-27

  • New GET /api/order/ — list orders with filtering and pagination.
  • New GET /api/order/{order_id}/ — retrieve a single order.
  • New GET /api/order/{order_id}/labels/ — fetch shipping labels for an existing order.

1.0.2 — Initial public release

  • POST /api/order/ — create draft or pay-on-create orders.
  • PUT /api/order/{order_id}/ — update / confirm and pay drafts.
  • DELETE /api/order/{order_id}/ — delete draft orders.
  • GET /api/pudo/ — search PUDO points (lockers).
  • GET /api/tracking/ — public tracking endpoint.
  • JWT (Bearer) authentication via /api/access/ and /api/access/refresh/.

Authentication

The API uses Bearer (JWT) authentication.

You can find your API Token in Integrations.

Obtain a JWT token pair by exchanging your existing API Token at the /api/access/ endpoint.

Step 1 — Exchange your Token for a JWT

POST /api/access/
Authorization: Token your-api-token-here

Response:

{
  "access": "eyJhbGciOiJIUzI1NiIs...",
  "refresh": "BZssDasdfjJkd1NiIs..."
}

Step 2 — Use the Bearer token for all API requests

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

The access token is valid for 30 minutes.

Step 3 — Refresh when the access token expires

POST /api/access/refresh/
Content-Type: application/json

{"refresh": "BZssDasdfjJkd1NiIs..."}

Response:

{
  "access": "eyJhbGciOiJIUzI1NiIs...new..."
}

The refresh token is valid for 7 days and rotates on each use.

Examples

cURL:

# Get JWT tokens
curl -X POST https://www.ecoparcel.eu/api/access/ \
  -H "Authorization: Token abc123def456"

# Use Bearer token
curl -X POST https://www.ecoparcel.eu/api/order/ \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"orders": [...]}'

Python (requests):

import requests

# Exchange Token for JWT
token_response = requests.post(
    "https://www.ecoparcel.eu/api/access/",
    headers={"Authorization": "Token abc123def456"},
)
access_token = token_response.json()["access"]

# Use Bearer token
response = requests.post(
    "https://www.ecoparcel.eu/api/order/",
    headers={"Authorization": f"Bearer {access_token}"},
    json={"orders": [...]},
)

JavaScript (fetch):

// Exchange Token for JWT
const tokenRes = await fetch("https://www.ecoparcel.eu/api/access/", {
  method: "POST",
  headers: { "Authorization": "Token abc123def456" },
});
const { access } = await tokenRes.json();

// Use Bearer token
const res = await fetch("https://www.ecoparcel.eu/api/order/", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${access}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ orders: [...] }),
});

Public endpoints

The Tracking endpoint is publicly accessible without authentication. Anonymous requests are rate-limited to 10 requests per minute.

Rate Limiting

Client TypeLimit
Anonymous10 requests/min
Authenticated30 requests/min

Order Flow

Orders go through a simple lifecycle. Understanding the statuses and the available actions at each stage will help you integrate smoothly.

Order Statuses

StatusMeaning
TEMPDraft order — not yet paid or submitted to a carrier. Can be edited, confirmed, or deleted.
NEWPaid and queued for processing. The carrier will receive the shipment shortly.
IN_PROGRESSHanded over to the carrier. Tracking events will start appearing.
DELIVEREDSuccessfully delivered to the recipient.
CANCELLEDCancelled due to collection failure or request.
REFUNDEDCancelled and refunded.

Typical Flows

1. Quick order (create + pay in one call)

The simplest integration — create an order and pay for it in a single request.

POST /api/order/
{ "orders": [...], "confirm_and_pay": true }

The order is created, priced, paid, and submitted to the carrier immediately. The response includes a base64-encoded shipping label PDF in the labels field.

Recommended for: production integrations where the sender and receiver addresses are known upfront and no PUDO selection is needed (door-to-door shipments).

2. Two-step order (create draft, then confirm)

Create the order first as a draft, then confirm and pay separately. Useful when you need to review pricing or make adjustments before committing.

Step 1 — Create a draft order:

POST /api/order/
{ "orders": [...], "confirm_and_pay": false }

The order is saved with status TEMP. No charge is made.

Step 2 — Confirm and pay:

PUT /api/order/{order_id}/
{ "confirm_and_pay": true }

The order is paid and submitted. Labels are returned in the response.

Recommended for: testing, or when you want to inspect the calculated price before paying.

3. PUDO point selection flow

When using locker-based transportation types (door_to_locker, locker_to_door, locker_to_locker) and you want the user to choose a specific locker, follow this flow:

Step 1 — Create a draft order without a PUDO point:

POST /api/order/
{
  "orders": [{
    ...,
    "transportation_type": "door_to_locker"
  }],
  "confirm_and_pay": false
}

The system auto-selects the nearest locker. The order is saved as TEMP.

Step 2 — Search for nearby PUDO points:

GET /api/pudo/?order_id={order_id}&for_consignee=true

Returns a list of available lockers near the consignee address, filtered by the carrier assigned to the order.

Step 3 — Update the order with the chosen PUDO and confirm:

PUT /api/order/{order_id}/
{
  "consignee_details": { "pudo_point": "87" },
  "confirm_and_pay": true
}

Note: You can also provide pudo_point directly in the POST /api/order/ call if you already know the PUDO ID, skipping the search step entirely.

4. Editing a draft order

Any TEMP order can be updated — change addresses, parcel dimensions, transportation type, or anything else.

PUT /api/order/{order_id}/
{
  "consignee_details": { "address": "New Street 5", "city": "Tallinn", "postcode": "10115" },
  "parcels": [{ "description": "Updated", "width": 25, "height": 15, "length": 35, "weight": 3, "value": 80 }]
}

The service card and price are recalculated on every update. Only provided fields are changed — omitted fields keep their current values.

5. Cancelling an order

Draft orders (TEMP) can be deleted outright:

DELETE /api/order/{order_id}/

Orders that have already been paid (NEW, IN_PROGRESS) cannot be cancelled via the API. Use the Ecoparcel dashboard or contact support.

Payment

When confirm_and_pay is set to true (either on create or update), the system charges your account in this order:

  1. Account balance is used first.
  2. If the balance is insufficient, your saved payment card is charged for the remaining amount.

If neither balance nor card can cover the cost, the order stays in TEMP status with a warning in the response.

Transportation Types

ValueDescription
door_to_doorDoor to Door
door_to_lockerDoor to Locker
locker_to_doorLocker to Door
locker_to_lockerLocker to Locker

PUDO points (lockers)

For locker-based transportation types, a pudo_point field can be included inside shipper_details and/or consignee_details to specify an exact drop-off/pick-up locker (by ID).

Transportation Typeshipper_details.pudo_pointconsignee_details.pudo_point
door_to_doorNot allowedNot allowed
door_to_lockerNot allowedOptional (auto-selected if omitted)
locker_to_doorOptional (auto-selected if omitted)Not allowed
locker_to_lockerOptional (auto-selected if omitted)Optional (auto-selected if omitted)

When pudo_point is omitted for a locker side, the system automatically selects the nearest locker based on the provided address.

Important: sending pudo_point for a side that does not require a locker (e.g. shipper side on door_to_locker) will return a 400 validation error.

Package Types

ValueDescription
parcelStandard parcel shipment
palletPallet shipment

Tracking Event Codes

Each entry in tracking_events[] returned by GET /api/tracking/{order_id}/ includes a stable 4-digit event_code. Use the code (not the human-readable event_description) to drive your own logic — descriptions are localized and may change wording, but codes are stable.

CodeDescription
0002Prepared for collection
0101Out for collection
0102Collection successful (includes parcel locker / shop drop-off by shipper)
0103Collection problem
0105Collection problem — Wrong address
0107Collection cancelled — Wrong address
0108Collection cancelled — Shipper was absent
0109Cancelled
0110Collection cancelled — Insufficient customs declaration
0111Refunded to the balance
0112Contacted
0201Inbound
0202Outbound
0204Return
0205Non-conveyable
0301Out for delivery
0302Delivery successful — Consignee
0303Delivery successful — Parcelshop / Locker (consignee-side)
0304Delivery successful — Post Office
0305Delivery successful — Neighbour
0306Delivery problem — Consignee was absent
0307Delivery problem — Wrong address
0308Delivery problem — Consignee refused
0309Delivery problem — Not out for delivery
0310Delivery problem
0311Delivery problem — Consignee refused
0501Parcel stored in warehouse
0502Additional information
0503Customs
0505Changed collection date

Failure / problem codes

The following subset indicates the parcel hit a problem state and may need attention (failed collection, failed delivery, return):

0103, 0105, 0107, 0108, 0204, 0306, 0307, 0308, 0309, 0310

Orders

List orders

Returns a paginated list of your orders, newest first. Supports limit / offset pagination and optional status filter.

Authorizations:
jwtAuth
query Parameters
limit
integer

Number of results to return (default 100).

offset
integer

Index of the first result to return.

status
string
Enum:"CANCELED""COMPLETED""IN_PROGRESS""IN_QUEUE""NEW""PREPARED""REFUNDED""REJECTED""TEMP"

Filter by order status.

Responses

Response samples

Content type
application/json
Example
{
  • "count": 123,
  • "results": [
    ]
}

Create order(s)

Creates one or more shipment orders. Optionally confirms and initiates payment.

Authorizations:
jwtAuth
Request Body schema:
required
required
Array of objects (SingleOrder)
confirm_and_pay
required
boolean
(LabelFormatEnum (string or null)) or (NullEnum (any or null))

Override label format (A4, A5, A6). If omitted, user preference is used.

  • A4 - A4
  • A5 - A5
  • A6 - A6

Responses

Request samples

Content type
Example
{
  • "orders": [
    ],
  • "confirm_and_pay": true,
  • "label_format": "A6"
}

Response samples

Content type
application/json
Example
{
  • "orders": [
    ],
  • "labels": "JVBERi0xLjQK... (base64 PDF)"
}

Get order details

Returns the details of a single order by its ID.

Authorizations:
jwtAuth
path Parameters
order_id
required
string

Order ID.

Responses

Response samples

Content type
application/json
{
  • "id": "KR4PZK",
  • "parcels": [
    ],
  • "collection_date": "2026-03-10",
  • "total_price": "7.78",
  • "paid": true,
  • "status": "NEW",
  • "customer_reference": "REF-001",
  • "created_at": "2026-03-09T14:23:11Z"
}

Update a TEMP order

Updates an existing TEMP order. All fields are optional — only provided fields are changed. The full pricing pipeline always runs: service card and price are recalculated.

Authorizations:
jwtAuth
path Parameters
order_id
required
string

Order ID.

Request Body schema:
object (PartialOrderDetails)

OrderDetailsSerializer with all fields optional — for partial updates.

object (PartialOrderDetails)

OrderDetailsSerializer with all fields optional — for partial updates.

Array of objects (Parcel)
package_type
string (PackageTypeEnum)
Enum:"parcel""pallet""truck"
  • parcel - parcel
  • pallet - pallet
  • truck - truck
transportation_type
string (TransportationTypeEnum)
Enum:"door_to_locker""door_to_door""locker_to_locker""locker_to_door"
  • door_to_locker - door_to_locker
  • door_to_door - door_to_door
  • locker_to_locker - locker_to_locker
  • locker_to_door - locker_to_door
customer_reference
string <= 32 characters
confirm_and_pay
boolean
Default: false

Responses

Request samples

Content type
Example
{
  • "shipper_details": {
    },
  • "consignee_details": {
    }
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "customer_reference": "string",
  • "parcels": [
    ],
  • "collection_date": "2019-08-24",
  • "total_price": "string",
  • "paid": false,
  • "status": "string",
  • "warnings": [
    ]
}

Delete a TEMP order

Deletes a TEMP order and its associated parcels. Only orders in TEMP status can be deleted.

Authorizations:
jwtAuth
path Parameters
order_id
required
string

Order ID.

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Download order label

Returns the shipping label for a confirmed order. Only available for orders that have been paid and processed (not TEMP, CANCELED, or REFUNDED).

Output parameter:

  • pdf (default) — binary PDF download
  • base64_pdf — JSON response with the label as a base64-encoded string

Label format parameter:

  • A4 — A4 page size
  • A5 — A5 page size
  • A6 — A6 page size
  • If omitted, the user's preferred label format from profile settings is used.
Authorizations:
jwtAuth
path Parameters
order_id
required
string

Order ID.

query Parameters
label_format
string
Enum:"A4""A5""A6"

Override the label format. If omitted, the user's preferred format is used.

output
string
Enum:"base64_pdf""pdf"

pdf (default) returns a binary PDF file. base64_pdf returns JSON with a base64-encoded PDF string.

Responses

Response samples

Content type
No sample

PUDO

Search nearby PUDO points

Returns nearby PUDO points filtered by the partner assigned to an existing order. Provide order_id plus one of: for_shipper=true, for_consignee=true, or an explicit address/postcode/country.

Authorizations:
jwtAuth
query Parameters
address
string

Explicit address to geocode

country
string

ISO alpha-2 country code

distance_km
integer

Search radius in km (1-50, default 5)

for_consignee
boolean

Use order consignee address

for_shipper
boolean

Use order shipper address

limit
integer

Max results (1-30, default 10)

order_id
required
string

6-char order short code

postcode
string

Postcode for explicit address

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Shipping Price

Get shipping price estimate

Returns the cheapest shipping price for a given route and parcel dimensions.

Authorizations:
jwtAuthtokenAuth
query Parameters
country_from
required
string

ISO 3166-1 alpha-2 sender country code

country_to
required
string

ISO 3166-1 alpha-2 receiver country code

height
number <double>
Default: 1

Height in cm

length
number <double>
Default: 1

Length in cm

package_type
string
Default: "parcel"

parcel or pallet

transportation_type
string
Default: "door_to_door"

door_to_door, door_to_locker, locker_to_door, locker_to_locker

weight
number <double>
Default: 1

Weight in kg

width
number <double>
Default: 1

Width in cm

Responses

Response samples

Content type
application/json
{
  • "is_business": false,
  • "country_from": "DE",
  • "country_to": "ES",
  • "transportation_amount": "18.54",
  • "vat_amount": "3.52",
  • "amount": "22.06",
  • "currency": "EUR",
  • "weight": "1.00",
  • "delivery_time": 72,
  • "transportation_type": "door_to_door",
  • "package_type": "parcel"
}

List shipping options for checkout

Returns one best-value rate per available transportation type (door, locker, ...), sorted cheapest-first.

Authorizations:
jwtAuthtokenAuth
query Parameters
country_from
required
string

ISO 3166-1 alpha-2 sender country code

country_to
required
string

ISO 3166-1 alpha-2 receiver country code

height
number <double>
Default: 1

Height in cm

length
number <double>
Default: 1

Length in cm

package_type
string
Default: "parcel"

parcel or pallet

weight
number <double>
Default: 1

Weight in kg

width
number <double>
Default: 1

Width in cm

Responses

Response samples

Content type
application/json
{
  • "is_business": true,
  • "country_from": "string",
  • "country_to": "string",
  • "currency": "string",
  • "rates": [
    ]
}

Tracking

Get order tracking

Returns tracking information for an order by 6-char order code or 7-8 char parcel code.

Authorizations:
jwtAuthNone
path Parameters
order_id
required
string

6-char order code or 7-8 char parcel code

Responses

Response samples

Content type
application/json
{
  • "order_id": "KR4PZK",
  • "country_from": "DE",
  • "country_to": "EE",
  • "status": "NEW",
  • "estimated_collection_date": "2026-03-10",
  • "estimated_delivery_date": "2026-03-13",
  • "addons": [ ],
  • "parcels": [
    ]
}