Download OpenAPI specification:
Shipping order creation, tracking, and price estimation.
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.
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.00 kg on order create. Parcels with weight < 1.00 are bumped to 1.00 before pricing.Parcel weight below minimum (1 kg) — bumped to 1 kg for parcel(s): EPDQRZ01, EPDQRZ03./api/shipping-price/ endpoint returns prices tailored to the authenticated account.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.GET /api/order/ — list orders with filtering and pagination.GET /api/order/{order_id}/ — retrieve a single order.GET /api/order/{order_id}/labels/ — fetch shipping labels for an existing order.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./api/access/ and /api/access/refresh/.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.
POST /api/access/
Authorization: Token your-api-token-here
Response:
{
"access": "eyJhbGciOiJIUzI1NiIs...",
"refresh": "BZssDasdfjJkd1NiIs..."
}
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
The access token is valid for 30 minutes.
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.
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: [...] }),
});
The Tracking endpoint is publicly accessible without authentication. Anonymous requests are rate-limited to 10 requests per minute.
Orders go through a simple lifecycle. Understanding the statuses and the available actions at each stage will help you integrate smoothly.
| Status | Meaning |
|---|---|
TEMP | Draft order — not yet paid or submitted to a carrier. Can be edited, confirmed, or deleted. |
NEW | Paid and queued for processing. The carrier will receive the shipment shortly. |
IN_PROGRESS | Handed over to the carrier. Tracking events will start appearing. |
DELIVERED | Successfully delivered to the recipient. |
CANCELLED | Cancelled due to collection failure or request. |
REFUNDED | Cancelled and refunded. |
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).
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.
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_pointdirectly in thePOST /api/order/call if you already know the PUDO ID, skipping the search step entirely.
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.
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.
When confirm_and_pay is set to true (either on create or update), the system charges your account in this order:
If neither balance nor card can cover the cost, the order stays in TEMP status with a warning in the response.
| Value | Description |
|---|---|
door_to_door | Door to Door |
door_to_locker | Door to Locker |
locker_to_door | Locker to Door |
locker_to_locker | Locker to Locker |
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 Type | shipper_details.pudo_point | consignee_details.pudo_point |
|---|---|---|
door_to_door | Not allowed | Not allowed |
door_to_locker | Not allowed | Optional (auto-selected if omitted) |
locker_to_door | Optional (auto-selected if omitted) | Not allowed |
locker_to_locker | Optional (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.
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.
| Code | Description |
|---|---|
0002 | Prepared for collection |
0101 | Out for collection |
0102 | Collection successful (includes parcel locker / shop drop-off by shipper) |
0103 | Collection problem |
0105 | Collection problem — Wrong address |
0107 | Collection cancelled — Wrong address |
0108 | Collection cancelled — Shipper was absent |
0109 | Cancelled |
0110 | Collection cancelled — Insufficient customs declaration |
0111 | Refunded to the balance |
0112 | Contacted |
0201 | Inbound |
0202 | Outbound |
0204 | Return |
0205 | Non-conveyable |
0301 | Out for delivery |
0302 | Delivery successful — Consignee |
0303 | Delivery successful — Parcelshop / Locker (consignee-side) |
0304 | Delivery successful — Post Office |
0305 | Delivery successful — Neighbour |
0306 | Delivery problem — Consignee was absent |
0307 | Delivery problem — Wrong address |
0308 | Delivery problem — Consignee refused |
0309 | Delivery problem — Not out for delivery |
0310 | Delivery problem |
0311 | Delivery problem — Consignee refused |
0501 | Parcel stored in warehouse |
0502 | Additional information |
0503 | Customs |
0505 | Changed collection date |
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
Returns a paginated list of your orders, newest first. Supports limit / offset pagination and optional status filter.
| 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. |
{- "count": 123,
- "results": [
- {
- "count": 42,
- "previous": null,
- "results": [
- {
- "id": "KR4PZK",
- "parcels": [
- {
- "description": "Electronics",
- "width": "30.00",
- "height": "20.00",
- "length": "40.00",
- "weight": "5.00",
- "value": "150.00"
}
], - "collection_date": "2026-03-10",
- "total_price": "7.78",
- "paid": true,
- "status": "NEW",
- "created_at": "2026-03-09T14:23:11Z"
}, - {
- "id": "7AKXY7",
- "parcels": [
- {
- "description": "Books",
- "width": "20.00",
- "height": "10.00",
- "length": "30.00",
- "weight": "3.00",
- "value": "25.00"
}
], - "collection_date": "2026-03-08",
- "total_price": "5.50",
- "paid": true,
- "status": "COMPLETED",
- "customer_reference": "REF-002",
- "created_at": "2026-03-07T09:15:00Z"
}
]
}
]
}Creates one or more shipment orders. Optionally confirms and initiates payment.
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.
|
{- "orders": [
- {
- "shipper_details": {
- "country": "LT",
- "contact_name": "John Sender",
- "address": "Gedimino pr. 1",
- "city": "Vilnius",
- "postcode": "01103",
- "phone": "+37060000000",
- "email_address": "sender@example.com"
}, - "consignee_details": {
- "country": "EE",
- "contact_name": "Jane Receiver",
- "address": "Narva mnt 10",
- "city": "Tallinn",
- "postcode": "10117",
- "phone": "+3725000001",
- "email_address": "receiver@example.com"
}, - "parcels": [
- {
- "description": "Electronics",
- "width": 30,
- "height": 20,
- "length": 40,
- "weight": 5,
- "value": 150
}
], - "package_type": "parcel",
- "transportation_type": "door_to_door",
- "customer_reference": "REF-001"
}
], - "confirm_and_pay": true,
- "label_format": "A6"
}{- "orders": [
- {
- "id": "KR4PZK",
- "parcels": [
- {
- "description": "Electronics",
- "width": "30.00",
- "height": "20.00",
- "length": "40.00",
- "weight": "5.00",
- "value": "150.00"
}
], - "collection_date": "2026-03-10",
- "total_price": "7.78",
- "paid": true,
- "status": "NEW",
- "customer_reference": "REF-001"
}
], - "labels": "JVBERi0xLjQK... (base64 PDF)"
}Returns the details of a single order by its ID.
| order_id required | string Order ID. |
{- "id": "KR4PZK",
- "parcels": [
- {
- "description": "Electronics",
- "width": "30.00",
- "height": "20.00",
- "length": "40.00",
- "weight": "5.00",
- "value": "150.00"
}
], - "collection_date": "2026-03-10",
- "total_price": "7.78",
- "paid": true,
- "status": "NEW",
- "customer_reference": "REF-001",
- "created_at": "2026-03-09T14:23:11Z"
}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.
| order_id required | string Order ID. |
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"
|
| transportation_type | string (TransportationTypeEnum) Enum:"door_to_locker""door_to_door""locker_to_locker""locker_to_door"
|
| customer_reference | string <= 32 characters |
| confirm_and_pay | boolean Default: false |
{- "shipper_details": {
- "phone": "+37060000000"
}, - "consignee_details": {
- "phone": "++37060000000"
}
}{- "id": "string",
- "customer_reference": "string",
- "parcels": [
- {
- "description": "string",
- "width": "string",
- "height": "string",
- "length": "string",
- "weight": "string",
- "value": "string"
}
], - "collection_date": "2019-08-24",
- "total_price": "string",
- "paid": false,
- "status": "string",
- "warnings": [
- "string"
]
}Deletes a TEMP order and its associated parcels. Only orders in TEMP status can be deleted.
| order_id required | string Order ID. |
{- "error": {
- "code": "order_not_found",
- "message": "Order not found.",
- "details": { }
}
}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 downloadbase64_pdf — JSON response with the label as a base64-encoded stringLabel format parameter:
A4 — A4 page sizeA5 — A5 page sizeA6 — A6 page size| order_id required | string Order ID. |
| 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"
|
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.
| 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 |
[- {
- "pudo_points": [
- {
- "id": "42",
- "name": "InPost Locker Vilnius Akropolis",
- "address": "Ozo g. 25",
- "city": "Vilnius",
- "postcode": "07150",
- "country": "LT",
- "point_type": "LOCKER",
- "latitude": "54.7100000",
- "longitude": "25.2700000",
- "distance_km": 1.23
}
]
}
]Returns the cheapest shipping price for a given route and parcel dimensions.
| 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 |
{- "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"
}Returns one best-value rate per available transportation type (door, locker, ...), sorted cheapest-first.
| 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 |
{- "is_business": true,
- "country_from": "string",
- "country_to": "string",
- "currency": "string",
- "rates": [
- {
- "transportation_type": "string",
- "transportation_amount": "string",
- "vat_amount": "string",
- "amount": "string",
- "delivery_time": 0
}
]
}Returns tracking information for an order by 6-char order code or 7-8 char parcel code.
| order_id required | string 6-char order code or 7-8 char parcel code |
{- "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": [
- {
- "parcel_id": "KR4PZKA",
- "status": "NEW",
- "tracking_events": [
- {
- "tracked_on_time": "2026-03-09T14:30:00Z",
- "event_code": "0002",
- "event_description": "Prepared for collection",
- "event_comment": "",
- "event_country": "DE"
}
]
}
]
}