Biklo for developersTESTsandbox-api.biklo.comchange

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:

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: true means 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.