Create scent order
Create a scent order from the caller's own open cart.
Scent Orders
/api/v1/scent/ordersOrder LINES are never part of this request — they are taken from the partner’s (optionally
customerRef-scoped) open cart. Call GET /api/v1/scent/cart/{customerRef}/checkout-preview
to price the cart, then POST /api/v1/scent/cart/{customerRef}/price-lock to take a price
lock: lockId is REQUIRED here — every order is placed against a frozen price lock and
charged the locked prices; live-priced create is no longer allowed. See the Cart preview &
price locks guide. A price lock is SINGLE-USE:
reusing an already-consumed lockId is rejected with 409 CONFLICT_PRICE_LOCK_ALREADY_USED, carrying the order it was already used for. A repeat
externalReference against an order that already completed is REJECTED, not replayed — see
the Idempotency guide. The created order is then read via GET /api/v1/scent/orders/{orderId}
(the Location header points there; orderId == this response’s own id).
Body parameters
externalReferencestringRequiredYour own idempotency key — see the Idempotency guide.
shippingAddress.recipientstringRequiredshippingAddress.line1stringRequiredshippingAddress.line2stringshippingAddress.citystringRequiredThe delivery-zone key — checked against a “within Riyadh” allowlist that drives the order-level shipping fee.
shippingAddress.nationalAddressstringRequiredThe customer’s KSA national short address code (e.g. “RAKB2837”). MANDATORY — the required basis for delivery-zone determination alongside city.
shippingAddress.regionstringshippingAddress.postalCodestringshippingAddress.countryCodestringRequiredISO 3166-1 ALPHA-3 country code (e.g. “SAU”) — validated against the real alpha-3 code set; an unknown code is rejected VALIDATION_INVALID_COUNTRY_CODE.
shippingAddress.phonestringRequiredcustomerRefstringMust match the customerRef used when building the cart being checked out. Omit for the partner-level cart.
lockIdstring (uuid)RequiredREQUIRED. The id returned by a prior POST /api/v1/scent/cart/{customerRef}/price-lock call. Every order is placed against a frozen price lock and charged the locked prices — live-priced create is not allowed. A lock is single-use: reusing an already-consumed lockId is rejected 409 CONFLICT_PRICE_LOCK_ALREADY_USED. See the Cart preview & price locks guide.
discountCodestringOptional partner discount code to apply/consume at order creation; the applied code + amount appear on the order’s pricing.discount. Display/reporting only — does not change the amount charged.
Response
idstring (uuid)The created order’s own id — read it back via GET /api/v1/scent/orders/{orderId}, where orderId is this same value.
externalReferencestringcustomerRefstringstatusstring (enum)The one status vocabulary every product’s order maps into — see the Order states & errors guide for what each value means for a Scent order specifically, and for the create-time reject codes (CONFLICT_ORDER_DUPLICATE_REFERENCE, CONFLICT_PRICE_LOCK_ALREADY_USED, VALIDATION_PRICE_LOCK_EXPIRED/_INVALID) that stop an order from ever reaching a live status at all. failureReason on the order response is populated only when status is Failed, and is additive alongside it — status is never replaced by it.
pricing.currencystringpricing.subtotalnumber (decimal)Net goods amount, ex-VAT.
pricing.vatnumber (decimal)Output VAT on the goods leg.
pricing.shippingFeeobjectThe order-level shipping fee, shown as its own line. Zero for a free-shipping order.
pricing.discountobjectThe discount code applied at checkout, if any. Display/reporting only — it never affects any other figure in pricing.
pricing.totalnumber (decimal)The single VAT-inclusive gross amount actually charged to your wallet.
pricing.commissionobjectThe partner commission on the order. Both the net and gross (VAT-inclusive) figures are carried explicitly.
modeBreakdownobjectSum of line totals (ex-VAT) per settlement mode, e.g. {“Retail”: 640.00}. The key is YOUR OWN settlement mode for those lines (see pricingMode on an order line) — Wholesale or Retail.
Other responses
400Validation failure — including a missing/malformed lockId, an EXPIRED lock (VALIDATION_PRICE_LOCK_EXPIRED), or a lock whose cart has since mutated (VALIDATION_PRICE_LOCK_INVALID) — as well as ordinary field validation.401No bearer token (UNAUTHORIZED_MISSING_TOKEN), an invalid token (UNAUTHORIZED_INVALID_TOKEN) or an expired one (UNAUTHORIZED_TOKEN_EXPIRED — mint a new token). UPSTREAM_AUTH_FAILED is different: a dependent service refused the Gateway itself. That is a server-side fault — retry later and do NOT re-mint your token. See the Errors guide.403The route is not available to your credential (FORBIDDEN).404No API route matches this path (NOT_FOUND_ROUTE). A missing resource on a real route returns that service’s own NOT_FOUND_* code instead.409A prior attempt with the SAME externalReference already exists or is still in flight, the cart already has a pending order, a checkout race on the same cart lost, or the lockId was already used to create a (possibly different) order — see the Order states & errors guide and the Idempotency guide.415The request body is not sent as application/json (UNSUPPORTED_MEDIA_TYPE).422A business-rule rejection — most commonly BUSINESS_PRICE_NOT_AVAILABLE: a price in the cart is no longer available because prices moved since the cart was priced or locked. Re-run the checkout preview and check out again at the new price.429Too many requests — see the Rate limits guide. Retry after the Retry-After header (seconds).502A dependent service returned an invalid response (BAD_GATEWAY). Retry with backoff.503The service is temporarily unavailable (SERVICE_UNAVAILABLE). Retry with backoff.504A dependent service did not respond in time (GATEWAY_TIMEOUT). Retry with backoff.curl --request POST \ --url https://api-omni.linra.net/api/v1/scent/orders \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "externalReference": "string", "shippingAddress": { "recipient": "string", "line1": "string", "line2": "string", "city": "string", "nationalAddress": "string", "region": "string", "postalCode": "string", "countryCode": "SAU", "phone": "string" }, "customerRef": "string", "lockId": "5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b", "discountCode": "string" }'const res = await fetch('https://api-omni.linra.net/api/v1/scent/orders', { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ externalReference: 'string', shippingAddress: { recipient: 'string', line1: 'string', line2: 'string', city: 'string', nationalAddress: 'string', region: 'string', postalCode: 'string', countryCode: 'SAU', phone: 'string' }, customerRef: 'string', lockId: '5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b', discountCode: 'string' }),});const { payload } = await res.json();var response = await httpClient.PostAsJsonAsync( "https://api-omni.linra.net/api/v1/scent/orders", new { externalReference = "string", shippingAddress = new { recipient = "string", line1 = "string", line2 = "string", city = "string", nationalAddress = "string", region = "string", postalCode = "string", countryCode = "SAU", phone = "string" }, customerRef = "string", lockId = "5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b", discountCode = "string" });
var envelope = await response.Content.ReadFromJsonAsync<ApiResponse<ScentOrderCreateResponse>>();Response
{ "state": "SUCCESS", "payload": { "id": "5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b", "externalReference": "string", "customerRef": "string", "status": "Processing", "pricing": { "currency": "SAR", "subtotal": 0, "vat": 0, "shippingFee": { "amount": 0, "vatAmount": 0, "amountWithVat": 0 }, "discount": { "code": "string", "amount": 0 }, "total": 0, "commission": { "amount": 0, "vatAmount": 0, "amountWithVat": 0, "netAfterCommission": 0 } }, "modeBreakdown": {} }, "details": null}
