Skip to content
Linra Omni Developer Docs

Errors

The response envelope and a catalogue of the error codes you'll actually see.

Every response — success or failure — is wrapped the same way:

// success
{ "state": "SUCCESS", "payload": { "...": "..." } }
// created (201 responses)
{ "state": "CREATED", "payload": { "...": "..." } }
// error
{ "state": "SOME_ERROR_CODE", "payload": null, "details": { "...": "optional context" } }

state is always present. On an error, it carries a stable, machine-readable code — parse and branch on state, not on the HTTP status code alone, and never on the human-readable text some errors surface in details.

This holds for every error, including the ones the API Gateway produces itself before your request reaches a service (a missing token, an unknown route, a wrong method, a downstream outage) — see Gateway errors below. A service’s own error (e.g. NOT_FOUND_ORDER) passes through the Gateway unchanged.

A 201 always carries a Location header with the absolute public URL of the new resource, e.g. https://api-omni.linra.net/api/v1/scent/orders/{orderId} (staging: https://api-omni-stg.linra.net/...) — never an internal host.

HTTP status Meaning
200 / 201 Success (state: "SUCCESS" / "CREATED").
400 Request validation failed — a malformed/missing body field, or a query parameter that is unknown, unparseable or out of range (see Query parameters).
401 Missing, invalid, or expired credentials/token (or, as UPSTREAM_AUTH_FAILED, a server-side fault — see Gateway errors).
403 Authenticated, but not allowed to do this specific thing (e.g. an invalid delegation target, or a route your credential can’t use).
404 The resource doesn’t exist — or belongs to another partner. Cross-tenant access always reports 404, never 403, so a not-yours resource is indistinguishable from a genuinely nonexistent one.
409 A conflict — usually a duplicate/in-progress/racing request.
405 The path exists but not with this HTTP method — the Allow header lists the methods it supports.
415 The request body isn’t sent as application/json.
422 The request was well-formed but violates a business rule (e.g. a price that is no longer available).
429 Rate-limited — see Rate limits.
5xx Something went wrong on our side (502/503/504 come from the Gateway when a service behind it is unreachable or slow — see Gateway errors). Retries with backoff are safe for these (and, for order creation, always safe regardless of status — see Idempotency).

These come from the API Gateway itself, in the same envelope (details.message is a short human-readable hint):

Code HTTP When What to do
UNAUTHORIZED_MISSING_TOKEN 401 No Authorization: Bearer ... header. Send a token from POST /api/v1/auth/token.
UNAUTHORIZED_INVALID_TOKEN 401 The token is malformed, tampered, signed for a different audience, or not a partner token. Fix the request; mint a new token only if you were sending the wrong one.
UNAUTHORIZED_TOKEN_EXPIRED 401 The token is past its 5-minute lifetime (at most 30 seconds of clock skew is allowed). Mint a new token and retry once.
UPSTREAM_AUTH_FAILED 401 A service behind the Gateway refused the Gateway — not your token. A server-side fault. Retry later with backoff. Do not re-mint your token.
FORBIDDEN 403 The route is not available to your credential. Don’t retry.
AUTH_ENVIRONMENT_MISMATCH 403 A staging token sent to the production host, or the reverse. Use the host that matches your credential’s environment (see Environments).
NOT_FOUND_ROUTE 404 No API route matches the path. (A missing resource on a real route returns that service’s own NOT_FOUND_* code instead.) Check the path against the API Reference.
METHOD_NOT_ALLOWED 405 The path exists, but not with this HTTP method. The Allow header lists the supported methods. Use a supported method.
UNSUPPORTED_MEDIA_TYPE 415 The body isn’t application/json (or Content-Type is missing). Previously returned as application/problem+json. Send Content-Type: application/json.
BAD_GATEWAY 502 A service behind the Gateway returned an invalid response. Retry with backoff.
SERVICE_UNAVAILABLE 503 A service behind the Gateway is temporarily unavailable. Retry with backoff.
GATEWAY_TIMEOUT 504 A service behind the Gateway didn’t answer in time. Retry with backoff (order create is safe to retry with the same externalReference — see Idempotency).

Every 401 keeps the standard WWW-Authenticate: Bearer ... header; every 405 keeps Allow.

{ "state": "UNAUTHORIZED_TOKEN_EXPIRED", "payload": null, "details": { "message": "The bearer token has expired. Request a new one from POST /api/v1/auth/token." } }

Query input is strict on every endpoint. A request with a typo that used to be silently ignored (and return everything) now returns 400, so a filter can never be dropped without you noticing. The first failing rule decides the code; details.errors names every offending parameter.

Code When Extra details
VALIDATION_UNKNOWN_QUERY_PARAMETER A parameter name the endpoint doesn’t have (e.g. ?type= where the real name is referenceType). details.allowedParameters lists the accepted names.
VALIDATION_INVALID_DATE A date that doesn’t parse, or a from/to pair with from after to. —
VALIDATION_INVALID_PAGE_SIZE pageSize is not a whole number from 1 to 100. details.minimum / details.maximum
VALIDATION_INVALID_PAGE page is not a whole number of 1 or more. details.minimum
VALIDATION_UNKNOWN_INCLUDE An include token the endpoint doesn’t support (e.g. ?include=bogus). details.allowedValues.include lists the accepted tokens.
VALIDATION_INVALID_QUERY_PARAMETER A value that doesn’t parse as the parameter’s type — an id that isn’t a GUID, an unknown enum value, a non-number, a boolean other than true/false. For an enum, details.allowedValues.{param} lists the accepted values.
{
"state": "VALIDATION_UNKNOWN_QUERY_PARAMETER",
"payload": null,
"details": {
"errors": { "type": ["'type' is not a parameter of this endpoint."] },
"allowedParameters": ["fromUtc", "page", "pageSize", "referenceType", "sortBy", "sortDescending", "toUtc"]
}
}

Paging: pageSize is 1–100 and defaults to 20; page starts at 1 (the default). Out-of-range values are rejected, never clamped.

Dates: every date filter (createdFrom/createdTo, fromUtc/toUtc, from/to, fromDate/toDate, changedSince) is UTC. A value without an offset is read as UTC (2026-01-01T10:00:00 = 2026-01-01T10:00:00Z), and a date-only value means midnight UTC (2026-01-01 = 2026-01-01T00:00:00Z). A value with an offset (+03:00) is converted to UTC.

Required booleans: isActive is required on every PATCH .../active call (webhooks, discount codes). A body without it returns 400 VALIDATION_ERRORS and changes nothing — an empty body never deactivates anything. Webhook PUT stays a partial update (omitted fields are unchanged).

This isn’t every code the API can ever return, but it covers what you’re likely to build error handling around:

Code HTTP When
UNAUTHORIZED_INVALID_CREDENTIALS 401 Wrong clientId/clientSecret at the token endpoint.
UNAUTHORIZED_PARTNER_INACTIVE 401 Your partner account is inactive.
FORBIDDEN_NOT_YOUR_DESCENDANT / FORBIDDEN_DELEGATION_TARGET_INVALID 403 An onBehalfOf delegation target you don’t actually have authority over.
NOT_FOUND_ORDER 404 Unknown orderId on GET /api/v1/scent/orders/{orderId} (or its /tracking sibling), or one belonging to another partner.
NOT_FOUND_SCENT_ORDER 404 Unknown orderId on a returns endpoint (POST/GET .../orders/{orderId}/return-requests), or one belonging to another partner.
NOT_FOUND_WALLET / NOT_FOUND_STATEMENT 404 No wallet provisioned yet / unknown or not-yours statement.
NOT_FOUND_COMMISSION_PERIOD 404 Unknown commission period, or one belonging to another partner.
NOT_FOUND_WEBHOOK_SUBSCRIPTION 404 Unknown subscription id.
NOT_FOUND_SCENT / NOT_FOUND_BRAND / NOT_FOUND_VARIANT 404 Unknown catalog entity. A variant that exists but isn’t in your account’s assortment is reported exactly the same way (NOT_FOUND_VARIANT) — on a variant read, adding it to your cart (or increasing its quantity), your wishlist, or a back-in-stock subscription.
NOT_FOUND_CART_ITEM 404 No such line in your cart.
NOT_FOUND_FULFILLMENT_GROUP / NOT_FOUND_ORDER_LINE 404 Unknown target for a return request.
VALIDATION_MISSING_REPORT_RANGE 400 GET /api/v1/statements/report without both from and to.
VALIDATION_INVALID_COMMISSION_PERIOD_STATUS 400 An unrecognized status value on GET /api/v1/commission-periods (not one of Provisional/Issued/Settled).
VALIDATION_INVALID_PERIOD 400 fromUtc equals toUtc on a sales report endpoint (GET /api/v1/scent/reports/sales/**). fromUtc after toUtc is VALIDATION_INVALID_DATE.
VALIDATION_REPORT_WINDOW_TOO_LARGE 400 A sales report window (fromUtc/toUtc) spans more than 400 days.
VALIDATION_ERRORS 400 A request body failed field validation — e.g. an empty or over-1000-character reason on POST /api/v1/commission-periods/{id}/objection, or a PATCH .../active body without isActive. details.errors names the offending field(s).
VALIDATION_UNKNOWN_LOOKUP_CODE 400 An unknown concentration or audience code on a catalog list (details.list, details.value).
VALIDATION_WEBHOOK_URL_UNSAFE 400 A webhook url that isn’t a valid absolute https URL, doesn’t resolve, or resolves to a private/non-routable address (details.reason).
VALIDATION_PRICE_LOCK_INVALID / VALIDATION_PRICE_LOCK_EXPIRED 400 A lockId that’s unusable (unknown/not-yours/cart-mutated, or expired) — see Cart preview & price locks.
VALIDATION_INVALID_COUNTRY_CODE 400 shippingAddress.countryCode isn’t a recognized ISO 3166-1 alpha-3 code.
VALIDATION_CUSTOMER_REF_TOO_LONG 400 customerRef over 128 characters.
VALIDATION_WEBHOOK_EVENT_TYPE_NOT_SUBSCRIBED 400 Test-firing an event type your subscription isn’t subscribed to.
VALIDATION_INVALID_REASON_CODE 400 An unrecognized reasonCode on a return request line.
VALIDATION_INVALID_RETURN_STATUS 400 An unrecognized status filter value on GET /api/v1/scent/return-requests (not one of Requested/Accepted/Processing/Refunded/Rejected/Withdrawn).
BUSINESS_PRICE_NOT_AVAILABLE 422 A price in the cart (or the price lock) is no longer available — prices moved since you priced it. Re-run the checkout preview and check out again at the new price. The checkout preview reports the same condition as the issue PRICE_NOT_AVAILABLE.
BUSINESS_VARIANT_UNAVAILABLE 422 Adding a variant to your cart or wishlist that isn’t currently available for purchase.
BUSINESS_VARIANT_NOT_PRICED 422 Adding a variant to your cart that has no active price for your account.
BUSINESS_PRODUCT_ACCESS_DISABLED 422 Linra has paused your partner account’s access to start anything new on Scent (existing orders keep fulfilling). Nothing to retry — stop attempting new cart/order actions and contact your account manager. Always check the checkout-preview’s cartIssue first — see Cart preview & price locks — so you find out before you even try.
BUSINESS_CART_NOT_CHECKOUT_READY 422 POST .../cart/{customerRef}/price-lock was called on a cart whose recomputed preview reports canCheckout: false — a broken basket is never locked. Order-create returns the same code with details.cartIssue: "VARIANT_NOT_ASSIGNED" and details.variantIds when a cart line’s variant was removed from your assortment after it was carted (this applies to a create with a lockId too). Remove those lines and preview again.
BUSINESS_WEBHOOK_SUBSCRIPTION_LIMIT 422 You already have 5 active subscriptions.
BUSINESS_VARIANT_ALREADY_AVAILABLE 422 A back-in-stock registration for a variant that is already available — just add it to the cart.
BUSINESS_RETURN_REQUEST_GROUP_NOT_DELIVERED / _WINDOW_EXPIRED / _EXCEEDS_REMAINING / _DUPLICATE_LINE 422 A return request eligibility rule failed — see the API Reference for each.
BUSINESS_RETURN_REQUEST_NOT_PENDING 422 You tried to withdraw a return request that’s no longer Requested — we’ve already taken some action on it, so withdrawal is locked out permanently.
CONFLICT_CART_HAS_PENDING_ORDER 409 The cart already has an order in flight.
CONFLICT_CART_CHECKOUT_RACE / CONFLICT_PRICE_LOCK_CREATE_RACE 409 A concurrent request on the same cart lost a race — safe to retry.
CONFLICT_ORDER_DUPLICATE_REFERENCE 409 A create with this externalReference already succeeded — details.orderId names the existing order; fetch it via GET /api/v1/scent/orders/{orderId} rather than retrying. See Idempotency.
CONFLICT_ORDER_IN_PROGRESS 409 A create with this externalReference is still being processed. Retry again shortly.
CONFLICT_ORDER_REQUIRES_INTERVENTION 409 A prior attempt with this externalReference hit an unrecoverable state and needs operator intervention before it can be retried. Contact your account manager rather than retrying.
CONFLICT_PRICE_LOCK_ALREADY_USED 409 The lockId you passed to order-create was already used to create an order (single-use). details.orderId names that order.
CONFLICT_COMMISSION_PERIOD_ALREADY_OBJECTED / _NOT_PROVISIONAL / _PAST_ISSUE_DATE 409 POST /api/v1/commission-periods/{id}/objection — see Commission periods.
RATE_LIMITED 429 See Rate limits.

The Gateway and query-parameter codes are listed in their own sections above.

  • Never distinguish “wrong client ID” from “wrong secret” at the token endpoint — both are the same generic UNAUTHORIZED_INVALID_CREDENTIALS, to avoid letting a caller enumerate valid client IDs.
  • Never leak a stack trace, an internal exception message, or implementation detail in details.
  • Never return a 200 with an error state embedded in the payload — if state isn’t SUCCESS/ CREATED, the HTTP status code is always a matching 4xx/5xx too.