Skip to content
Linra Omni Developer Docs

Fulfilment & returns

Tracking a Scent order from Processing through delivery, what a partial delivery looks like, and how a self-serve return works end to end.

This guide walks the partner-facing fulfilment lifecycle of a Scent order — from the moment it’s charged through delivery, and, if the customer needs it, a return and refund. It only covers what you can see and do through the API: your counterparty throughout is Linra — there’s nothing in this guide (or on the API) that requires you to know how a shipment physically moves behind the scenes.

Every Scent order’s status is the platform-wide UnifiedOrderStatus value. This is the complete set — see Order states & errors for the failureReason contract and the create-time reject codes that sit alongside it:

status What it means
Processing Charged and accepted — fulfilment is under way (being picked, packed, and shipped). You’ll see this immediately after a successful create.
PartiallyDelivered At least one, but not every, fulfilment group has been delivered — some parcels have arrived, others are still in transit.
Completed Every fulfilment group on the order has been delivered.
Cancelled The order was cancelled before it was ever charged. No money moved.
PartiallyReturned At least one line has been returned (refunded, and any commission clawed back) but the order isn’t fully returned.
Returned Every line across the whole order has been returned and refunded.
Failed The order could not be created/charged at all. The only value a create call itself can ever hand you directly — see failureReason below for why.

PartiallyDelivered and a return state (PartiallyReturned/Returned) aren’t mutually exclusive underneath — if both would apply, the returns state takes display precedence, since “some of this order has been sent back” is more actionable to show a partner than “some of it has arrived.”

stateDiagram-v2
    [*] --> Failed: create/charge rejected — no live order
    [*] --> Cancelled: cancelled before it was ever charged
    [*] --> Processing: charged — fulfilment under way
    Processing --> PartiallyDelivered: some parcels delivered, others still in transit
    Processing --> Completed: every parcel delivered
    PartiallyDelivered --> Completed: remaining parcels delivered
    Processing --> PartiallyReturned: at least one line returned
    PartiallyDelivered --> PartiallyReturned: at least one line returned
    Completed --> PartiallyReturned: at least one line returned
    PartiallyReturned --> Returned: every line across the order returned
    Failed --> [*]
    Cancelled --> [*]
    Completed --> [*]
    Returned --> [*]

An order can ship as more than one parcel — each one is a fulfilment group, with its own status (Processing → Shipped → Delivered) independent of the others. GET /api/v1/scent/orders/{orderId} always returns the full fulfillmentGroups[] array alongside the order’s own coarse status.

For a lighter, delivery-focused read, use the dedicated tracking endpoint:

Terminal window
curl https://api-omni.linra.net/api/v1/scent/orders/order-guid/tracking \
-H "Authorization: Bearer $TOKEN"
{
"state": "SUCCESS",
"payload": {
"status": "PartiallyDelivered",
"deliveryRollup": { "totalGroups": 2, "deliveredGroups": 1 },
"shipments": [
{
"shipmentId": "33333333-3333-3333-3333-333333333333",
"status": "Delivered",
"trackingNumber": "TRK-1234567890",
"carrierDisplayName": "Aramex",
"isConsolidated": false,
"deliveredAt": "2026-08-09T12:00:00Z"
},
{
"shipmentId": "44444444-4444-4444-4444-444444444444",
"status": "InTransitToCustomer",
"trackingNumber": "TRK-0987654321",
"carrierDisplayName": "SMSA",
"isConsolidated": false,
"deliveredAt": null
}
]
}
}

shipments[].status includes exception states you should watch for, not just Delivered: FailedDelivery, RefusedReceipt, ReturnedToSender, and Lost (a shipment that goes missing entirely). A Lost shipment triggers Linra’s own loss-handling process (a shortfall cancel and refund) automatically — you’ll see the result via the order.line-unfulfillable/order.returned webhooks and this same tracking read. carrierDisplayName is the carrier’s name — never Linra’s own internal fulfilment details, which stay internal.

See Webhooks & HMAC verification for order.fulfilment-updated, the play-by-play event you can subscribe to instead of polling this endpoint.

PartiallyDelivered (on the order) simply means: more than one fulfilment group exists on this order, and at least one — but not all — of them has reached Delivered. It says nothing about why the order split into multiple parcels; from your side, treat it exactly like any other multi-parcel shipment: some of the customer’s items have arrived, the rest are still in transit, and each parcel’s own tracking detail (above) tells you which is which. Once every fulfilment group reaches Delivered, the order’s own status advances to Completed.

When a line can’t be fulfilled after you’ve been charged

Section titled “When a line can’t be fulfilled after you’ve been charged”

Rarely, a line becomes unfulfillable after you’ve already been charged for it (e.g. a receiving-time shortage that couldn’t be resolved in time, or a lost shipment). When that happens, Linra raises an unfulfillable-line notice and fires the order.line-unfulfillable webhook, giving you 72 hours to choose how to proceed:

  • CancelWhole — cancel the whole outstanding line; you’re refunded in full.
  • ShipAvailable — ship whatever quantity is available; you’re refunded only for the shortfall.

If you miss the webhook (or it’s never delivered), GET /api/v1/scent/unfulfillable-notices lists your own still-open notices as a self-serve recovery path — see the API Reference for the full request/response shapes. If you don’t respond within the window, Linra applies its own documented default rather than leaving the line stuck.

A customer-initiated return is entirely self-serve: you request it, Linra runs it through a review-and-inspection lifecycle, and the refund lands in your wallet automatically once approved — no separate settlement step on your side.

Terminal window
curl -X POST https://api-omni.linra.net/api/v1/scent/orders/order-guid/return-requests \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"fulfillmentGroupId": "group-guid",
"lines": [
{ "scentOrderLineId": "line-guid", "quantity": 1, "reasonCode": "Damaged" }
]
}'

A few eligibility rules are checked up front, before anything is created:

  • The fulfillmentGroupId’s shipment must already be Delivered.
  • Each line’s requested quantity must be within what’s still returnable on that line.
  • The request must land within your configured return window (measured from delivery).
  • A line already carrying an unresolved request can’t be requested again.

A failing check reports one of BUSINESS_RETURN_REQUEST_GROUP_NOT_DELIVERED / _WINDOW_EXPIRED / _EXCEEDS_REMAINING / _DUPLICATE_LINE (422) — see the API Reference for the full set. reasonCode is one of Damaged, WrongItem, NotAsDescribed, ChangedMind, Other. On success you get back the created request at status: "Requested" — this moves no money by itself.

status on a return request is one of six values, in this order:

status What’s happening
Requested You’ve submitted the request. Nothing has been decided yet — you may still withdraw it yourself.
Accepted Linra approved the return; return logistics (a waybill, a collection) are being arranged.
Processing The return is in transit back to Linra and/or being inspected. This one partner-facing value covers several internal steps — expect to sit here for a while.
Refunded The refund (and any commission clawback) has landed in your wallet. Terminal.
Rejected Linra declined the return — see declineReason for why. Terminal.
Withdrawn You cancelled your own request while it was still Requested. Terminal.

The response’s timeline{} gives you the exact instant of each milestone as it happens (requestedAt, decidedAt, waybillIssuedAt, collectionScheduledAt, inTransitAt, refundReleasedAt), and logistics{} carries the waybill number and carrier once assigned. Poll GET .../orders/{orderId}/return-requests or subscribe to order.return-request.updated — covered in Webhooks & HMAC verification — instead of guessing when to check.

Once status reaches Refunded, the request’s refund{} object is populated — goods net/VAT/total, any commissionClawback debited from your own earnings, and (for a defect-caused return) any shippingFeeRefund — mirroring the original order’s own pricing{} shape so the refund reconciles field-for-field against the original charge. Both the goods refund and the commission clawback post to your wallet automatically; see Your sales & cart analytics and the Wallet reference for reading the resulting transactions.

While a request is still Requested — before Linra has acted on it — you can withdraw it yourself:

Terminal window
curl -X PATCH https://api-omni.linra.net/api/v1/scent/return-requests/request-guid/withdraw \
-H "Authorization: Bearer $TOKEN"

This moves no money and immediately makes the line eligible for a fresh request. Once any action has been taken on the request, this locks out permanently (422 BUSINESS_RETURN_REQUEST_NOT_PENDING) — withdrawal is only ever available in the earliest window.