Skip to Content
Client API 2026-09Conventions & Errors

Conventions & Errors

Requests

  • Send Accept: application/json on every request and Content-Type: application/json when the request has a body.
  • Path parameters {ldv} are tracking numbers (LDV), {id} are numeric identifiers, {bda} a BDA.
  • Boolean query parameters accept true/false and 1/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 parameterDefaultMaxDescription
per_page50200Items per page
page1Page number

Lists are ordered from the most recent item.

Value objects

The same structures are used everywhere, in requests and responses.

ObjectShapeNotes
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

codekeyMeaning
0, 1processingCreated, not yet handed to the carrier
2shippedShipped
3in_transitIn transit
4out_for_deliveryOut for delivery
5deliveredDelivered
6held_at_depotHeld at depot (a stock / giacenza is open)
7returned_to_senderReturned to sender
8undeliveredNot delivered
9awaiting_instructionsWaiting for the client’s instructions
10awaiting_collectionWaiting 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, email and reference are 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 notes and warning become hasWarning; on orders notes and error.message become hasError.
  • The customs declaration is returned without its importer block (the recipient’s EORI, VAT number and tax id).
  • The shipper is your own address and is returned in full.
  • The filters that would look a person up are closed: recipientName and postalCode on GET /shipments answer 422 validation_failed, and q on GET /orders searches 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."] } } }
HTTPcodeWhen
401unauthenticatedToken missing, expired or revoked
402subscription_required · subscription_payment_failed · plan_limit_reachedNo 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
403insufficient_scope · account_disabled · client_role_required · forbiddenToken or user not authorized; forbidden also when a contract code was issued to another account
404not_foundResource does not exist or belongs to another client; also no contract available for a shipment
405method_not_allowed
409invalid_stateThe operation is no longer possible (shipment already in a manifest or in transit, stock already instructed)
422validation_failedInvalid payload; details maps each field (dot notation) to its messages. Also a filter reserved to the Spedisci.online apps (recipientName, postalCode on GET /shipments)
422shipment_rejectedThe shipment or pickup was refused by the business rules or by the carrier; the reason is in message
429rate_limitedLimit of the endpoint exceeded — retry after the seconds given in Retry-After; see the limits below
502carrier_errorThe carrier or an external service did not answer
503http_errorService temporarily unavailable
500internal_errorUnexpected 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.

EndpointLimit
POST /rates60 / min
POST /shipments30 / min
POST /pickups10 / min
POST /shipments/{ldv}/return30 / min
POST /orders30 / min
POST /manifests5 / min
GET /shipments/{ldv}/tracking, GET /tracking/bda/{bda}20 / min in total across the two tracking endpoints (platform setting)
GET /delivery-points30 / min
GET /taric60 / min
POST /webhooks/{id}/test5 / min

For status updates prefer webhooks over polling the tracking endpoints.

Last updated on