Shipments
A shipment is a physical order routed through a courier — Biteship in v1, with adapters for additional aggregators planned. Each shipment represents one parcel: an origin warehouse, a destination address, the items inside it, the courier service chosen, and the status that walks the parcel from pending to delivered.
Shipments are the load-bearing fulfilment object: every physical-type product goes through one to reach the buyer. They consume stock reservations on creation and trigger stock movements on dispatch.
All requests on this page must be signed — see Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/shipments |
List shipments (no pagination) |
GET |
/api/v1/shipments/:id |
Retrieve one shipment with its event log |
POST |
/api/v1/shipments |
Create a shipment |
GET |
/api/v1/shipping/shipments |
Cursor-paginated list (preferred) |
GET |
/api/v1/shipping/shipments/:id/label |
Fetch the courier-generated label PDF URL |
POST |
/api/v1/shipping/shipments/:id/cancel |
Cancel a shipment before pickup |
GET |
/api/v1/shipping/track/:waybillId |
Public tracking page lookup |
The /shipments and /shipping/shipments paths overlap deliberately: /shipments is the legacy CRUD surface kept for SDK compatibility; /shipping/shipments is the newer cursor-paginated endpoint with richer filters. New integrations should prefer the /shipping/* path.
List shipments (cursor-paginated)
GET /api/v1/shipping/shipments
Returns shipments newest first, cursor-paginated. Filter by status to scope to a single lifecycle bucket.
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
limit |
integer | 25 |
Page size. Clamped to [1, 100]. |
cursor |
string | — | The previous page's meta.cursor. |
status |
string | — | Exact status filter. See statuses. |
Response — 200 OK
The envelope uses an array data and a meta.cursor:
{
"data": [
{
"id": "ship_01HXAB7K3M9N2P5QRS8TVWXY3Z",
"accountId": "acc_01HX...",
"status": "in_transit",
"courierCode": "jne",
"courierServiceCode": "reg",
"courierType": "standard",
"price": 18000,
"insurance": 0,
"insured": false,
"biteshipOrderId": "ord_jbk_xxx",
"waybillId": "JNE1234567890",
"labelUrl": "https://...",
"customerEmail": "buyer@example.com",
"checkoutSessionId": "cs_01HX...",
"createdAt": "2026-05-12T10:42:00.123Z",
"updatedAt": "2026-05-12T11:01:00.123Z"
}
],
"error": null,
"meta": {
"requestId": "req_01HX...",
"timestamp": "2026-05-12T10:42:00.124Z",
"total": 137,
"cursor": "ship_01HX...",
"hasMore": true
}
}
// Node
let cursor;
do {
const { data, meta } = await fk.shipping.shipments.list({ limit: 100, cursor });
for (const s of data) handle(s);
cursor = meta.cursor;
} while (cursor);
fulkruma_curl GET '/api/v1/shipping/shipments?limit=50&status=in_transit'
Retrieve a shipment
GET /api/v1/shipping/shipments/:id
Returns the shipment with its events array inlined — the timeline of provider-side status updates received via the inbound Biteship webhook.
Response shape (events truncated):
{
"data": {
"id": "ship_01HX...",
"status": "delivered",
"events": [
{ "id": "evt_01HX...", "status": "delivered", "note": "Received by recipient", "occurredAt": "2026-05-13T08:14:00Z" },
{ "id": "evt_01HX...", "status": "in_transit", "note": "Out for delivery", "occurredAt": "2026-05-13T05:00:00Z" }
]
},
"error": null,
"meta": { ... }
}
Errors
| Status | error.code |
When |
|---|---|---|
404 |
NOT_FOUND |
Shipment doesn't exist in this workspace. |
Create a shipment
POST /api/v1/shipments
Creates a shipment in the calling workspace and emits a fulkruma.shipment.created.v1 event.
Phase E stub. Today
POST /shipmentsrecords the intent locally with a placeholderbiteshipOrderId(prefixedpending-) but does not create a real Biteship order. The full Biteship adapter ships in Phase F as part of the Storlaunch integration. Use/api/v1/shipping/ratesfor live quoting today.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
courierCode |
string | yes | Biteship courier code, e.g. jne, sicepat, gosend. Validate with /api/v1/shipping/couriers. |
courierServiceCode |
string | yes | Service tier within the courier, e.g. reg, yes, same_day. |
courierType |
string | yes | standard, same_day, or instant. |
price |
integer (≥0) | yes | Quoted price in IDR cents. Source from a rate quote. |
origin |
object | yes | Origin snapshot. Either populate from your warehouse or take the merchant's configured shipping origin. |
destination |
object | yes | Destination address. See the destination shape. |
items |
array | yes | At least one item. Each carries name, value, weight, quantity, plus optional dimensional fields. |
insurance |
integer | no | Insurance premium in cents, if quoted. |
insured |
boolean | no | Whether insurance was elected. |
productId |
string | no | Link the shipment to a Fulkruma product. |
checkoutSessionId |
string | no | The Plugipay checkout session that originated this order. |
customerId |
string | no | The Fulkruma-tracked buyer ID. |
customerEmail |
string | no | Buyer email (used for tracking-page notifications). |
externalSource |
string | no | Where the order originated (storlaunch, your storefront name, etc.). |
externalRef |
string | no | The originating system's order ID. |
Response — 201 Created. The created shipment object.
Errors
| Status | error.code |
When |
|---|---|---|
400 |
VALIDATION |
Missing required field, bad shape, or unknown courier code. |
403 |
NO_ACCOUNT |
Token authenticated but has no accountId claim. |
fulkruma_curl POST '/api/v1/shipments' \
'{
"courierCode":"jne","courierServiceCode":"reg","courierType":"standard","price":18000,
"origin":{"contactName":"Warehouse","contactPhone":"+62811111111","address":"Jakarta 10220"},
"destination":{"contactName":"Alice","contactPhone":"+62822222222","address":"Bandung 40115"},
"items":[{"name":"Coffee 250g","value":85000,"weight":280,"quantity":1}]
}'
Cancel a shipment
POST /api/v1/shipping/shipments/:id/cancel
Cancels the shipment with the courier (best-effort — some couriers refuse cancel after pickup) and transitions Fulkruma's row to cancelled. Reservations are released; consumed stock is restocked via a refund_restock movement.
Request body
| Field | Type | Description |
|---|---|---|
reason |
string | Optional reason. Defaults to "Merchant cancelled". |
Errors
| Status | error.code |
When |
|---|---|---|
404 |
NOT_FOUND |
Shipment not in this workspace. |
409 |
INVALID_STATE |
Shipment is already delivered, cancelled, or post-pickup with a courier that rejects cancellations. |
Fetch the shipping label
GET /api/v1/shipping/shipments/:id/label
Returns the URL of the courier-generated PDF label. If the label hasn't been generated yet (most couriers take 1–5 minutes after order create), returns 409 NOT_READY and you should retry.
{ "data": { "url": "https://biteship-labels.s3.../label.pdf" }, "error": null, "meta": { ... } }
Public tracking
GET /api/v1/shipping/track/:waybillId?courier=<code>
Looks up live tracking from the courier via Biteship. Use this on your own tracking page; it returns history, status, and (where the courier exposes it) driver info.
Response shape
{
"data": {
"waybillId": "JNE1234567890",
"courier": "jne",
"status": "in_transit",
"history": [
{ "status": "in_transit", "note": "On vehicle for delivery", "updated_at": "2026-05-13T05:00:00Z" }
],
"driver": { "name": "Budi", "phone": "+62811...", "plate": "B 1234 XY", "photoUrl": null },
"externalLink": "https://www.jne.co.id/...",
"shipment": { "id": "ship_01HX...", "status": "in_transit", "events": [...] }
},
...
}
Status lifecycle
A shipment walks through these statuses, driven by inbound Biteship webhook events:
| Status | Means |
|---|---|
pending |
Created in Fulkruma, not yet sent to the courier (Phase E stub state). |
confirmed |
Courier accepted the order. |
allocated |
Driver assigned (instant/same-day only). |
picking_up |
Driver en route to origin. |
picked_up |
Parcel collected. |
dropping_off |
Driver en route to destination. |
in_transit |
Standard couriers: on the truck. |
delivered |
Recipient confirmed receipt. Arrives as fulkruma.shipment.status_updated.v1 with status: "delivered". |
returned |
Failed delivery, returned to sender. |
cancelled |
Cancelled before or during transit. |
The shipment object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | Fulkruma ID. Always ship_ + 26-char ULID. |
accountId |
string | no | The workspace this shipment belongs to. |
status |
enum | no | Current status. See the lifecycle. |
courierCode |
string | no | E.g. jne, sicepat. |
courierServiceCode |
string | no | E.g. reg, yes. |
courierType |
string | no | standard, same_day, or instant. |
price |
integer | no | Shipping price (cents). |
insurance |
integer | no | Insurance premium (cents). |
insured |
boolean | no | Whether insurance was elected. |
biteshipOrderId |
string | no | Biteship's order ID, or a pending- placeholder if the Phase F adapter hasn't yet been wired in. |
waybillId |
string | yes | Courier-issued waybill (AWB). Populates after courier acceptance. |
labelUrl |
string | yes | URL of the courier-generated PDF label. |
biteshipTrackingId |
string | yes | Biteship's internal tracking ID. |
originSnapshot |
object | no | Origin captured at create time. |
destinationSnapshot |
object | no | Destination captured at create time. |
items |
array | no | Items captured at create time. |
customerEmail |
string | yes | Buyer email. |
customerId |
string | yes | Fulkruma customer ID. |
checkoutSessionId |
string | yes | Plugipay checkout session that originated the order. |
productId |
string | yes | Linked Fulkruma product. |
externalSource |
string | yes | Origin system (e.g. storlaunch). |
externalRef |
string | yes | Origin system's order ID. |
events |
array | — | Inlined on retrieve. See ShipmentEvent. |
createdAt |
string (ISO 8601 UTC) | no | Creation timestamp. |
updatedAt |
string (ISO 8601 UTC) | no | Last mutation timestamp. |
The shipment event object
| Field | Type | Description |
|---|---|---|
id |
string (evt_…) |
Event ID. |
shipmentId |
string | The parent shipment. |
status |
string | The status this event transitioned to. |
note |
string | null | Free-form courier-supplied note. |
occurredAt |
string (ISO 8601 UTC) | When the courier recorded the event. |
Events
| Event type | Fires on | Notes |
|---|---|---|
fulkruma.shipment.created.v1 |
POST /api/v1/shipments succeeds. |
Emitted in the same transaction as the shipment insert. |
fulkruma.shipment.pickup_confirmed.v1 |
POST /api/v1/shipments/:id/confirm-pickup books the courier. |
Carries the courier order id and waybill. |
fulkruma.shipment.status_updated.v1 |
The courier reports a new status — picked up, in transit, delivered, returned, … | One per courier report; status is the shipment's new status. |
fulkruma.shipment.cancelled.v1 |
POST /api/v1/shipments/:id/cancel succeeds. |
Says how much shipping credit was refunded. |
fulkruma.shipment.rebooked.v1 |
POST /api/v1/shipments/:id/rebook creates the replacement. |
shipmentId is the new shipment, previousShipmentId the dead one. |
See Webhooks for the event envelope and signature recipe.
Next
- Shipping — rate quotes, courier catalog, origin config.
- Stock — reservations consumed by shipments.
- Addresses — saved destination addresses.
- Portal → Shipments — the dashboard walkthrough.