Conventions & Errors
Requests
- Send
Accept: application/jsonon every request andContent-Type: application/jsonwhen the request has a body. - Path parameters
{ldv}are tracking numbers (LDV),{id}are numeric identifiers,{bda}a BDA. - Boolean query parameters accept
true/falseand1/0. - Dates in query parameters and request bodies use
YYYY-MM-DD.
Responses
Responses are always JSON (Content-Type: application/json), including errors.
- A single resource is returned as a bare object.
- A collection is returned as
{ "data": [...] }. Large collections are paginated:
{
"data": [ ... ],
"links": { "first": "...?page=1", "last": "...?page=8", "prev": null, "next": "...?page=2" },
"meta": { "current_page": 1, "from": 1, "last_page": 8, "per_page": 50, "to": 50, "total": 391 }
}| Query parameter | Default | Max | Description |
|---|---|---|---|
per_page | 50 | 200 | Items per page |
page | 1 | Page number |
Lists are ordered from the most recent item.
Value objects
The same structures are used everywhere, in requests and responses.
| Object | Shape | Notes |
|---|---|---|
| Date / time | "2026-09-03T10:26:54+02:00" | ISO 8601 with time zone offset |
| Money | { "amount": 12.5, "currency": "EUR" } | currency is optional in requests (default EUR) |
| Weight | { "value": 1.5, "units": "KG" } | Requests accept KG or G (default KG); responses always use KG |
| Dimensions | { "length": 30, "width": 20, "height": 10, "units": "CM" } | Requests accept CM or MM (default CM); responses always use CM |
| Address | { "street", "street2", "city", "province", "postalCode", "countryCode" } | countryCode ISO 3166-1 alpha-2; province is the 2-letter code for Italy |
| Party | { "name", "company", "address": Address, "phone", "email", "reference" } | Shipper, recipient (in requests), pickup address |
| Destination | { "address": { "city", "province", "countryCode" } } | The recipient as returned to integrations — see Recipient personal data |
| Carrier | { "code": "fedex", "name": "FedEx", "contractCode": "...", "contractName": "..." } | code is the lowercase carrier identifier |
| Status | { "code": 5, "key": "delivered", "label": "Consegnata" } | See below; label is the Italian label shown in the web UI |
Shipment statuses
code | key | Meaning |
|---|---|---|
0, 1 | processing | Created, not yet handed to the carrier |
2 | shipped | Shipped |
3 | in_transit | In transit |
4 | out_for_delivery | Out for delivery |
5 | delivered | Delivered |
6 | held_at_depot | Held at depot (a stock / giacenza is open) |
7 | returned_to_sender | Returned to sender |
8 | undelivered | Not delivered |
9 | awaiting_instructions | Waiting for the client’s instructions |
10 | awaiting_collection | Waiting for collection at a pickup point |
Where a filter accepts a status (for example GET /shipments?status=), you can pass a key, a numeric code or a comma-separated mix: status=delivered,held_at_depot or status=5,6.
Recipient personal data
The API serves the Spedisci.online apps and the integrations of the clients (ERPs, shops, connectors). Only the Spedisci.online apps receive the personal data of the people shipments and orders are addressed to. An integration reads the same resources, but of the recipient it sees only where the parcel goes:
"recipient": { "address": { "city": "Roma", "province": "RM", "countryCode": "IT" } }- The recipient’s
name,street,street2,postalCode,phone,emailandreferenceare not returned. You still send them in full when creating shipments and orders; they are stored and printed on the label. - Free text that may quote the person is replaced by flags: on shipments
notesandwarningbecomehasWarning; on ordersnotesanderror.messagebecomehasError. - The customs declaration is returned without its
importerblock (the recipient’s EORI, VAT number and tax id). - The
shipperis your own address and is returned in full. - The filters that would look a person up are closed:
recipientNameandpostalCodeonGET /shipmentsanswer422 validation_failed, andqonGET /orderssearches only order numbers and tracking numbers.
This applies to every response of the API, including the 201 of POST /shipments and POST /orders. Webhook payloads never carried personal data.
Errors
Every error uses the same envelope. code is stable and machine-readable, message is for humans (and may be localized), details is optional (field errors, carrier response, plan limits…).
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"recipient.address.postalCode": ["The recipient.address.postal code field is required."]
}
}
}| HTTP | code | When |
|---|---|---|
401 | unauthenticated | Token missing, expired or revoked |
402 | subscription_required · subscription_payment_failed · plan_limit_reached | No subscription, unpaid subscription or monthly plan limit reached. Only on endpoints that generate costs (POST /shipments, POST /pickups) and on POST /webhooks. details carries plan_limit and shipments_this_month |
403 | insufficient_scope · account_disabled · client_role_required · forbidden | Token or user not authorized; forbidden also when a contract code was issued to another account |
404 | not_found | Resource does not exist or belongs to another client; also no contract available for a shipment |
405 | method_not_allowed | |
409 | invalid_state | The operation is no longer possible (shipment already in a manifest or in transit, stock already instructed) |
422 | validation_failed | Invalid payload; details maps each field (dot notation) to its messages. Also a filter reserved to the Spedisci.online apps (recipientName, postalCode on GET /shipments) |
422 | shipment_rejected | The shipment or pickup was refused by the business rules or by the carrier; the reason is in message |
429 | rate_limited | Limit of the endpoint exceeded — retry after the seconds given in Retry-After; see the limits below |
502 | carrier_error | The carrier or an external service did not answer |
503 | http_error | Service temporarily unavailable |
500 | internal_error | Unexpected error — retry later, contact support if it persists |
A 404 on a resource you just created almost always means you are calling with a token of a different client account (a different App or user). Resources are never shared across accounts.
Rate limits
Limits are per App, per minute. Exceeding one answers 429 rate_limited with the standard Retry-After / X-RateLimit-* headers.
| Endpoint | Limit |
|---|---|
POST /rates | 60 / min |
POST /shipments | 30 / min |
POST /pickups | 10 / min |
POST /shipments/{ldv}/return | 30 / min |
POST /orders | 30 / min |
POST /manifests | 5 / min |
GET /shipments/{ldv}/tracking, GET /tracking/bda/{bda} | 20 / min in total across the two tracking endpoints (platform setting) |
GET /delivery-points | 30 / min |
GET /taric | 60 / min |
POST /webhooks/{id}/test | 5 / min |
For status updates prefer webhooks over polling the tracking endpoints.