← Back to Transfers Between Depots Livra Partner API · partner_transfer

Moving parcels between your depots

Load the truck in as many calls as you need, check the load, send it once, and check the parcels in when they arrive. The transfer closes by itself when the last parcel is accepted.

Depot 3source
transferId
Depot 7destination
Overview

Load

ready Parcels can still be added, removed or the whole transfer cancelled.
1

Check each parcel in at the source depot

partner_accept_in_depot

A parcel has to be recorded as sitting in the source depot before it can go on a truck. Skip this and the parcel comes back as needs_repair. The route also needs a configured driver, unless you name one of your drivers with driverId on the first add.

2

Put parcels on the truck, up to 200 per call

add

Same source, destination and type every time. Every call lands on the same open transfer ("reused": true), so a large truck is simply several calls. Leave dispatch out while you are still loading, and keep the transferId: every later step takes it.

{ "action": "add",
  "sourceDepotId": 3, "destinationDepotId": 7,
  "type": "delivery",
  "orderIds": [1234, 1235, 1236] }
Repeat until the truck is full
added · already_addedIt is on the truck. Nothing to do.
needs_repairCheck it in at the source depot (step 1), then send it again in the next add.
refusedLeave it off this truck. reason says why; for wrong_destination_depot, correctDestinationDepot names the right depot.
3

Check what is on the truck

status

parcels[] is exactly what will leave. This is the last point where you can change the load.

{ "action": "status", "transferId": "4f1c8a02-…" }
some parcels should not goremove with their orderIds, then check again. Their leg is closed, so they can go on a later truck with a new add.
the whole truck is offcancel the transfer. Every parcel on it is released.

Send

ready → in_progress One call. After it, nothing can be taken off.
4

Dispatch once, when the truck leaves

dispatch

Send the transferId. Every parcel on the transfer leaves together and goes inTransit, however many there are.

{ "action": "dispatch", "transferId": "4f1c8a02-…" }
the call timed outSend the same request again. A repeat answers "dispatched": 0 and changes nothing.

Arrive

in_progress → completed Nothing to close by hand.
5

Check each parcel in at the destination

partner_accept_in_depot

One per parcel, as it comes off the truck at the destination depot.

✓

The transfer completes by itself

completed

When the last parcel is accepted, the transfer becomes completed. If one stays in_progress for a long time, a parcel on it was never accepted; status shows which.

Technical reference

Every check, in the order the server runs it

One flowchart per action, with each condition and the exact response it produces. The first failing check answers, and nothing after it runs.

success (200) error, fix the request or the data 409, retry after a pause per-parcel outcome inside a 200
Every request

Authentication and the envelope

flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef retry fill:#E3EDF8,stroke:#1D5FA8,color:#12345c
  R(["POST /partner_transfer"]) --> H{"Headers present,<br/>api key valid,<br/>signature matches the raw body?"}
  H -- "no headers" --> E1["401 Missing authentication headers"]:::err
  H -- "unknown key" --> E2["401 Invalid api key"]:::err
  H -- "bad signature" --> E3["401 Invalid signature"]:::err
  H -- yes --> P{"Key enabled for<br/>partner routes?"}
  P -- no --> E4["403 partner_scope_not_configured"]:::err
  P -- yes --> J{"Body is a JSON object?"}
  J -- no --> E5["400 Invalid payload"]:::err
  J -- yes --> A{"action is add, dispatch,<br/>status, list, remove or cancel?"}
  A -- no --> E6["400 Missing or invalid field: action"]:::err
  A -- yes --> EM{"employeeId a positive integer,<br/>employeeName a string?<br/>(only checked when sent)"}
  EM -- no --> E7["400 Invalid field: employeeId / employeeName"]:::err
  EM -- yes --> RUN["Run the action<br/>(flowcharts below)"]
  RUN --> OUT{"How it ended"}
  OUT -- "done" --> OK["200 ok: true + the action's body"]:::ok
  OUT -- "a field is wrong" --> V["400 · category request · the message names the field"]:::err
  OUT -- "business rule" --> T["400 or 403 · error code + category + retryable"]:::err
  OUT -- "another call is working on the same transfer" --> C["409 transfer_in_progress · retryable: true"]:::retry
  OUT -- "unexpected" --> I["500 internal_error · quote x-request-id"]:::err
Every response carries an x-request-id header. Error bodies are { ok: false, error, category, retryable }, plus reason and orderIds where they apply. Unknown top-level fields are ignored, not rejected.
actionadd

Open or reuse the transfer, then load the parcels

flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef retry fill:#E3EDF8,stroke:#1D5FA8,color:#12345c
  S(["add"]) --> V1{"sourceDepotId and<br/>destinationDepotId<br/>positive integers?"}
  V1 -- no --> X1["400 Missing or invalid field"]:::err
  V1 -- yes --> V2{"Same depot?"}
  V2 -- yes --> X2["400 same_depot"]:::err
  V2 -- no --> V3{"type is delivery or returned,<br/>orderIds 1 to 200 positive integers,<br/>dispatch and blockWrongDestination booleans,<br/>driverId a positive integer?"}
  V3 -- no --> X3["400 Missing or invalid field"]:::err
  V3 -- yes --> O1{"Any orderId belongs<br/>to another partner?"}
  O1 -- yes --> X4["403 unauthorizedPartnerForRequest<br/>+ orderIds · nothing created or loaded"]:::err
  O1 -- no --> L{"Route free?<br/>(no other add opening it)"}
  L -- no --> X5["409 transfer_in_progress"]:::retry
  L -- yes --> D{"Both depots are yours?"}
  D -- no --> X6["400 depot_not_found"]:::err
  D -- yes --> DR{"driverId sent?"}
  DR -- yes --> DR2{"Driver exists and<br/>is one of yours?"}
  DR2 -- no --> X7["400 driver_not_found"]:::err
  DR2 -- yes --> EX
  DR -- no --> EX{"A ready transfer already<br/>open on this route and type?"}
  EX -- yes --> Z{"Everything on it<br/>already handled?"}
  Z -- no --> RE["Reuse it · reused: true<br/>(its driver is kept)"]
  Z -- yes --> SET["It closes;<br/>a new one is opened"]
  EX -- no --> NEW
  SET --> NEW{"Driver = driverId,<br/>else the route's configured driver"}
  NEW -- none --> X8["400 no_route_driver · nothing created"]:::err
  NEW -- found --> CR["Create transfer · status ready · reused: false"]
  RE --> LOOP["Each orderId in order, one at a time<br/>(per-parcel chart below)"]
  CR --> LOOP
  LOOP -- "transfer dispatched or cancelled meanwhile" --> X9["400 transfer_closed<br/>(parcels already done stay loaded)"]:::err
  LOOP -- "lock busy mid-batch" --> X10["409 transfer_in_progress<br/>(re-send the same call: done ones answer already_added)"]:::retry
  LOOP --> DP{"dispatch: true?"}
  DP -- yes --> DIS["Dispatch the whole transfer<br/>(dispatch chart)"]
  DP -- no --> RESP["200 · transferId, status, reused,<br/>added, alreadyAdded, needsRepair, refused,<br/>wrongDestination, dispatched, parcels[]"]:::ok
  DIS --> RESP
Only one transfer per route and type can be in ready. That's why repeating add with the same route loads more parcels onto the same truck, and why a timed-out add is safe to repeat. add has no transferId field; the route picks the transfer.
inside addparcels[]

What decides each parcel's outcome

flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef warn fill:#FBF1DF,stroke:#9A6412,color:#4d320a
  S(["one orderId"]) --> F1{"Order exists?"}
  F1 -- no --> R1["refused · order_not_found"]:::err
  F1 -- yes --> F2{"Yours?"}
  F2 -- no --> R2["refused · unauthorizedPartnerForRequest"]:::err
  F2 -- yes --> F3{"Already delivered<br/>or returned?"}
  F3 -- yes --> R3["refused · already_delivered_or_returned"]:::err
  F3 -- no --> F4{"type returned and<br/>an open QC ticket?"}
  F4 -- yes --> R4["refused · unresolved_qc_ticket<br/>repairable: close the ticket, re-send"]:::warn
  F4 -- no --> F5{"Out for final delivery?"}
  F5 -- yes --> R5["refused · active_last_mile_mission"]:::err
  F5 -- no --> F6{"Already on this transfer?"}
  F6 -- yes --> A1["already_added"]:::ok
  F6 -- no --> F7{"Heading the way this type goes?<br/>delivery: to the customer<br/>returned: to the merchant"}
  F7 -- no --> R6["refused · wrong_final_destination"]:::err
  F7 -- yes --> F8{"Zone routing sends it<br/>to another depot?"}
  F8 -- "yes, blockWrongDestination true (default)" --> R7["refused · wrong_destination_depot<br/>+ correctDestinationDepot"]:::err
  F8 -- "yes, blockWrongDestination false" --> F9
  F8 -- "no, or no routing configured" --> F9{"On a last-mile runsheet<br/>still being loaded?"}
  F9 -- yes --> R8["refused · active_last_mile_runsheet"]:::err
  F9 -- no --> F10{"Record out of step?<br/>not_in_depot · wrong_depot<br/>unresolved_missions · active_ready_transfer"}
  F10 -- "any of them" --> N1["needs_repair + repairs[]<br/>nothing loaded"]:::warn
  F10 -- none --> A2["added · new leg, missionStatus ready<br/>+ warning wrong_destination_depot<br/>when loaded despite routing"]:::ok
Every refused parcel carries error, category, retryable and a reason you can show the operator. A parcel still on another transfer that hasn't left, or with any other open leg, comes back needs_repair. Take it off the other transfer with remove, then add it again.
actiondispatch

The truck leaves with everything on it

flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef retry fill:#E3EDF8,stroke:#1D5FA8,color:#12345c
  S(["dispatch"]) --> V{"transferId is a uuid?"}
  V -- no --> X1["400 Missing or invalid field: transferId"]:::err
  V -- yes --> O{"orderIds in the body?"}
  O -- yes --> X2["400 Invalid field: orderIds"]:::err
  O -- no --> L{"Transfer free?"}
  L -- no --> X3["409 transfer_in_progress"]:::retry
  L -- "no such transfer" --> X4["400 transfer_not_found"]:::err
  L -- yes --> OW{"Yours?"}
  OW -- "another partner's" --> X5["403 unauthorizedPartnerForRequest"]:::err
  OW -- yes --> C{"completed or cancelled?"}
  C -- yes --> X6["400 transfer_closed"]:::err
  C -- no --> DRV{"Transfer has a driver?"}
  DRV -- no --> X7["400 no_driver_assigned"]:::err
  DRV -- yes --> RT{"Destination depot<br/>still resolves?"}
  RT -- no --> X8["400 destination_depot_missing"]:::err
  RT -- yes --> RD{"Any legs still ready?"}
  RD -- no --> Z["200 dispatched: 0<br/>(a repeat, or an empty truck)"]:::ok
  RD -- yes --> MV["Every waiting parcel → inTransit,<br/>handed to the transfer's driver<br/>Transfer → in_progress<br/>Each parcel's history shows the pickup"]
  MV --> OK["200 dispatched: n · orderIds · status in_progress"]:::ok
Legs already inTransit are left alone. Calling dispatch again on an in_progress transfer only sends legs that are still ready, which is dispatched: 0 after a normal dispatch.
actionsstatuslist

Reading transfers

flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef retry fill:#E3EDF8,stroke:#1D5FA8,color:#12345c
  S(["status"]) --> V{"transferId is a uuid?"}
  V -- no --> X1["400 Missing or invalid field: transferId"]:::err
  V -- yes --> L{"Transfer free?"}
  L -- no --> X2["409 transfer_in_progress"]:::retry
  L -- "no such transfer" --> X3["400 transfer_not_found"]:::err
  L -- yes --> OW{"Yours?"}
  OW -- "another partner's" --> X4["403 unauthorizedPartnerForRequest"]:::err
  OW -- yes --> RT{"Destination depot<br/>still resolves?"}
  RT -- no --> X5["400 destination_depot_missing"]:::err
  RT -- yes --> PR{"Transfer still ready and<br/>a leg on it moved on elsewhere?"}
  PR -- yes --> P1["Drop those lines · prunedLines: n"]
  PR -- no --> ST
  P1 --> ST{"Every leg now closed?"}
  ST -- yes --> DONE["Transfer → completed"]
  ST -- no --> OK
  DONE --> OK["200 · transfer header, prunedLines, parcels[]"]:::ok
status takes transferId only. It is the read that completes a transfer once its last parcel has been accepted at the destination. list never changes anything.
flowchart LR
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  S(["list"]) --> V{"type delivery or returned?<br/>sourceDepotId, limit positive integers?<br/>status ready, in_progress,<br/>completed or cancelled?"}
  V -- no --> X1["400 Missing or invalid field"]:::err
  V -- yes --> OK["200 transfers[] newest first<br/>limit default 50, max 200<br/>each with parcelCount"]:::ok
list shows the stored status. A transfer whose parcels were all accepted can still show in_progress there until a status call on it completes it.
actionsremovecancel

Changing the load before it leaves

flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef retry fill:#E3EDF8,stroke:#1D5FA8,color:#12345c
  S(["remove"]) --> V{"transferId a uuid and<br/>orderIds 1 to 200 positive integers?"}
  V -- no --> X1["400 Missing or invalid field"]:::err
  V -- yes --> L{"Transfer free?"}
  L -- no --> X2["409 transfer_in_progress"]:::retry
  L -- "no such transfer" --> X3["400 transfer_not_found"]:::err
  L -- yes --> OW{"Yours?"}
  OW -- "another partner's" --> X4["403 unauthorizedPartnerForRequest"]:::err
  OW -- yes --> RD{"Transfer status ready?"}
  RD -- no --> X5["400 transfer_closed<br/>(already left, completed or cancelled)"]:::err
  RD -- yes --> OO{"Any orderId belongs<br/>to another partner?"}
  OO -- yes --> X6["403 unauthorizedPartnerForRequest + orderIds"]:::err
  OO -- no --> F{"Named parcels with a<br/>ready leg on this transfer?"}
  F -- none --> Z["200 removed: 0"]:::ok
  F -- some --> MV["Each parcel is taken off, its leg closed (fail)<br/>and the change recorded in its history"]
  MV --> OK["200 removed: n · orderIds<br/>(they can be added to another transfer)"]:::ok
flowchart TD
  classDef ok fill:#E2F2E8,stroke:#2B7148,color:#173d27
  classDef err fill:#F8E6E6,stroke:#A23A3A,color:#5c1b1b
  classDef retry fill:#E3EDF8,stroke:#1D5FA8,color:#12345c
  S(["cancel"]) --> V{"transferId is a uuid?"}
  V -- no --> X1["400 Missing or invalid field: transferId"]:::err
  V -- yes --> L{"Transfer free?"}
  L -- no --> X2["409 transfer_in_progress"]:::retry
  L -- "no such transfer" --> X3["400 transfer_not_found"]:::err
  L -- yes --> OW{"Yours?"}
  OW -- "another partner's" --> X4["403 unauthorizedPartnerForRequest"]:::err
  OW -- yes --> AC{"Already cancelled?"}
  AC -- yes --> Z["200 released: 0"]:::ok
  AC -- no --> RD{"Status ready?"}
  RD -- no --> X5["400 transfer_closed"]:::err
  RD -- yes --> MV["Each waiting parcel is released, its leg closed (fail)<br/>Transfer → cancelled"]
  MV --> OK["200 status cancelled · released: n"]:::ok
Nothing is deleted. Each released parcel's history records the change, and it can go on another transfer.
states

Transfer and parcel lifecycle

stateDiagram-v2
  direction LR
  [*] --> ready: first add on a route
  ready --> ready: add · remove
  ready --> in_progress: dispatch, or add with dispatch true
  ready --> cancelled: cancel
  ready --> completed: every leg closed elsewhere, seen by add or status
  in_progress --> completed: last parcel accepted at destination, seen by status
  completed --> [*]
  cancelled --> [*]
The transfer. Only ready accepts remove and cancel; add and dispatch are refused once it is completed or cancelled.
stateDiagram-v2
  direction LR
  [*] --> ready: add
  ready --> inTransit: dispatch
  ready --> fail: remove · cancel
  inTransit --> success: partner_accept_in_depot at the destination
  success --> [*]
  fail --> [*]
One parcel's leg on a transfer (missionStatus in parcels[]). A parcel whose leg failed has no open leg left, so it can be added to another transfer.
reference

Error codes

Call-level: the whole call failed

HTTPerrorcategoryretryRaised byWhat to do
400Missing or invalid field: …requestnoany actionThe message names the field. Fix the request.
400same_depotrequestnoaddSource and destination must differ.
400depot_not_foundrequestnoaddA depot id isn't one of yours. Check your mapping.
400driver_not_foundrequestnoaddThe driverId doesn't exist or isn't yours. Omit it to use the route driver.
400no_route_driverconfigurationnoaddNo driver configured for the route, and none named. Nothing was created.
400no_driver_assignedconfigurationnodispatchThe transfer has no driver. Contact us.
400transfer_not_foundrequestnodispatch, status, remove, cancel, addCheck the transferId.
400transfer_closedterminalnoadd, dispatch, remove, cancelAlready left, completed or cancelled. Start a new transfer.
400destination_depot_missingconfigurationnodispatch, statusMisconfigured on our side. Contact us with the x-request-id.
401Missing authentication headers · Invalid api key · Invalid signature—noany actionCheck the headers and sign the exact bytes you send.
403partner_scope_not_configured—noany actionYour key isn't enabled for partner routes. Contact us.
403unauthorizedPartnerForRequestauthnoadd, remove (orders) · dispatch, status, remove, cancel (transfer)Another partner's transfer or orders. reason says which; drop the foreign orderIds.
409transfer_in_progressconflictyesadd, dispatch, status, remove, cancelAnother call holds the transfer or the route. Retry after a short pause.
500internal_errorconfigurationnoany actionRetry once, then contact us with the x-request-id.

Per parcel: inside a 200 from add

statuserror / repairscategoryretryWhat to do
added—, or warning: wrong_destination_depot——On the truck. With the warning, it was loaded although routing sends it to correctDestinationDepot.
already_added———Already on this truck. Success.
refusedorder_not_foundterminalnoCheck the id.
refusedunauthorizedPartnerForRequestterminalnoNot your order. Normally the whole call is a 403 before this.
refusedalready_delivered_or_returnedterminalnoThe journey is finished.
refusedunresolved_qc_ticketrepairableyesReturns only. Close the QC ticket, then send it again.
refusedactive_last_mile_missionterminalnoOut for delivery. Recall that mission first.
refusedwrong_final_destinationterminalnoUse the other type.
refusedwrong_destination_depotterminalnoPut it on a transfer to correctDestinationDepot, or send blockWrongDestination: false.
refusedactive_last_mile_runsheetterminalnoTake it off that last-mile runsheet first.
needs_repairnot_in_depot—yesCheck it in at the source depot with partner_accept_in_depot.
needs_repairwrong_depot—yesIt's recorded at another depot. Check it in at this source depot.
needs_repairunresolved_missions—yesAn older leg is still open. Close it (for example with remove on the other transfer).
needs_repairactive_ready_transfer—yesIt's on another transfer that hasn't left. remove it there first.
Reading the answers

Three things that are not errors

ok: true with added: 0

ok describes the call, not the parcels. Always read added, refused and parcels[].

already_added, dispatched: 0, released: 0

A repeated call. The first one landed, and nothing changed.

409 transfer_in_progress

Another call is working on the same transfer. Retry after a short pause. This is the only failure worth retrying.