Orders end to end
From a buyer paying to a parcel leaving your warehouse.
1. Hear about the order#
Register a webhook for order.created and order.status_changed. Polling
GET /v1/orders?updated_since=… works, but you will hear minutes later instead of
seconds, and you will spend your rate limit on nothing.
2. Read it#
curl https://sandbox-api.biklo.com/v1/orders/711 -H "Authorization: Bearer $KEY"
Line items carry your external_id and sku, so you can match them to your
catalog without a lookup. can_accept tells you whether the next step is available
without you having to learn our status model.
3. Accept it#
curl -X POST https://sandbox-api.biklo.com/v1/orders/711/accept -H "Authorization: Bearer $KEY"
Tell us how it is packed#
Accepting is also where you declare how many boxes the order ships in. Send
nothing and we assume one box per order line — what has always happened. Send
parcels to combine lines:
curl -X POST https://sandbox-api.biklo.com/v1/orders/711/accept \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"parcels": [{
"items": [
{"shipping_item_id": 1123, "quantity": 2},
{"shipping_item_id": 1124, "quantity": 1}
],
"weight_kg": 2.4, "length_cm": 40, "width_cm": 30, "height_cm": 20
}]
}'
This is worth doing. The courier prices per parcel, so three products declared as one box is one parcel charge rather than three.
Three rules:
shipping_item_idis theidon each entry ofitemsfromGET /v1/orders/{id}— the order LINE, not the product.- Every piece that ships must sit in exactly one box. None left over, none counted twice.
- Lines can be combined, and a line's pieces split across boxes — but never more boxes than pieces.
Ship fewer pieces than ordered#
The buyer ordered 3 and you have 2? Send items with what you ship:
curl -X POST https://sandbox-api.biklo.com/v1/orders/711/accept \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "items": [{ "id": 1123, "quantity": 2 }] }'
The courier is booked for those 2. Only once it returned an AWB is the line reduced and
the buyer refunded the product price of the missing piece (delivery and fees stay); if
the booking fails, nothing changed and you can call again. Afterwards the line reads
quantity: 2, quantity_ordered: 3, quantity_short: 1, the order has the refund under
refunded / refunds, order.items_changed is sent, and the listing goes to stock 0.
With parcels as well, pack the planned quantities. 0 drops a line while others still
ship; to ship nothing, reject the order.
Only a pending order can be accepted; anything else returns 409 with the current
status. Accepting is what starts the seller side of the flow — the buyer is notified
and the money moves into your wallet lot.
4. Get the waybill#
curl -X POST https://sandbox-api.biklo.com/v1/orders/711/awb -H "Authorization: Bearer $KEY"
{ "awb": { "number": "SBX0000007112", "tracking_url": "https://…" }, "already_existed": false }
This is idempotent. An order that already has an AWB returns the same one with
already_existed: true rather than booking a second shipment — the failure that costs
real money. Retry freely.
5. Print the label#
curl "https://sandbox-api.biklo.com/v1/orders/711/label?format=zpl" \
-H "Authorization: Bearer $KEY" -o label.zpl
Returns the label bytes, not JSON. pdf for a human, zpl for a warehouse
printer.
PDF labels come as A6 (105 × 148 mm, the label-printer size) unless the seller
chose another size in their Biklo settings. Ask for one explicitly with size:
curl "https://sandbox-api.biklo.com/v1/orders/711/label?size=a4" \
-H "Authorization: Bearer $KEY" -o label.pdf
a6, a5, a4 or original (the courier's own PDF). Each parcel gets its own
portrait page; the duplicate copy couriers print on the same sheet is dropped. The
X-Label-Size response header tells you what you got.
6. Your invoice#
Biklo does not issue the invoice for the goods — you do, in your own accounting system. Which means you first need to know who to bill:
curl https://sandbox-api.biklo.com/v1/orders/711/customer -H "Authorization: Bearer $KEY"
{
"id": 9000001,
"name": "John Doe",
"email": "[email protected]",
"billing": {
"address_line": "Str. Exemplu 12",
"city": "Brasov",
"county": "Brasov",
"postal_code": "500002",
"country_code": "RO",
"is_company": true,
"company": {
"name": "Example Bike Shop SRL",
"vat_code": "RO12345678",
"registration_no": "J16/123/2020"
},
"is_live": true
}
}
Branch on is_company. A business buyer is invoiced as the company, with its CUI;
a private buyer as the person. Do not infer it from whether company happens to be
filled in.
This needs the customers.read scope, which is separate from orders.read and off by
default — an order carries a delivery address because you cannot ship without one, and
this carries a full postal address, an email and a fiscal identity.
is_live: truemeans the street is read live, from an address the buyer can still edit. Only the city, county, postcode and country were frozen at checkout. If your invoices have to be reproducible, store this block when you issue the invoice rather than re-fetching it a year later.
Then file the finished document against the order:
curl -X POST https://sandbox-api.biklo.com/v1/orders/711/invoice \
-H "Authorization: Bearer $KEY" -F "[email protected]"
Or send {"url": "…"} and we reference it instead.
Billing in bulk#
GET /v1/customers is the same data keyed on the buyer rather than the order, and
GET /v1/customers/{id} adds every order they placed with you, each carrying
has_invoice. That last flag is the one a nightly job wants: has_invoice: false is
your queue of orders still to bill.
7. Track#
Hand the parcel to the courier — there is nothing to confirm. The order stays
accepted; where the parcel is comes from the courier:
curl https://sandbox-api.biklo.com/v1/orders/711/tracking -H "Authorization: Bearer $KEY"
status there is the courier's latest status, and events[].is_final tells you it was
delivered. (POST /v1/orders/{id}/confirm-shipped is deprecated: it still answers, and
does nothing.)
Returns#
GET /v1/returns lists what buyers have raised; POST /v1/returns/{orderId}/accept
accepts one and refunds the buyer.
Testing all of it without a courier#
In the sandbox, fabricate an order and walk it:
curl -X POST https://sandbox-api.biklo.com/v1/sandbox/orders \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"external_id": "ERP-1001", "quantity": 2}'
curl -X POST https://sandbox-api.biklo.com/v1/sandbox/orders/711/advance \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"status": "delivered"}'
Every status change fires the webhooks it would fire in production, so you can test
your handler end to end in about a minute. AWBs and labels in the sandbox are
simulated: real, printable files with an SBX number, and no carrier is contacted.
