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.
The order lifecycle
Section titled “The order lifecycle”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 --> [*]
Tracking an order
Section titled “Tracking an order”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:
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.
What partial delivery means
Section titled “What partial delivery means”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.
Returns, end to end
Section titled “Returns, end to end”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.
1. Request the return
Section titled “1. Request the return”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 beDelivered. - 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.
2. The RMA lifecycle
Section titled “2. The RMA lifecycle”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.
3. The refund
Section titled “3. The refund”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.
Withdrawing a request
Section titled “Withdrawing a request”While a request is still Requested — before Linra has acted on it — you can withdraw it
yourself:
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.
Where to go next
Section titled “Where to go next”- Order states & errors — the full
status/failureReasoncatalogue. - Webhooks & HMAC verification — subscribe to fulfilment and return events instead of polling.
- API Reference → Returns — every return-request endpoint’s exact wire shape.

