Skip to content
Linra Omni Developer Docs

Platform overview

What Linra Omni is, how the API is versioned, and the contracts every endpoint shares — independent of which product you're calling.

Linra Omni is a single B2B commerce platform, not a collection of separately-integrated systems. You sign one partner agreement, hold one set of API credentials, and integrate against one versioned API — and that stays true as Linra adds more product lines over time. Today that means Scent (perfume and fragrance); tomorrow it may mean others. Whichever products you sell through, you never re-authenticate differently, re-learn a different response shape, or manage a second wallet.

This page covers only the platform layer — the concepts and contracts that work identically no matter which product you’re calling: authentication, the response envelope, pagination, rate limits, and the shared error model. Anything specific to Scent (its catalog, cart, order states, returns) lives on the Scent product page and its own guides instead.

flowchart TD
    Partner["Your integration"] -->|"HTTPS + bearer token"| API["Linra Omni API — /api/v1"]
    API --> Platform["Platform layer<br/>Authentication · Wallet · Billing Statements · Commission Periods"]
    API --> Scent["Scent — /api/v1/scent/**<br/>Catalog · Cart · Orders · Returns · Webhooks"]
    API -.-> Future["A future product — /api/v1/{product}/**"]

Everything under Platform in the sidebar is built to keep working exactly the same way once a second product line ships, with no breaking change on your side — that is the guarantee this split exists to make. Everything under Scent is specific to that one product’s own catalog and order lifecycle. See URL conventions for exactly how that split shows up in the URLs themselves.

If you only integrate with one product today, you don’t need to think about this distinction much — just start with Getting started. It matters mainly for knowing which parts of your integration are safe to assume will never change shape as Linra grows.

The entire partner-facing surface lives under one version prefix, /api/v1. There is no per-resource versioning — a breaking change to any endpoint would ship as /api/v2 for the whole facade, announced in the Changelog well ahead of time. Every path in this reference is relative to that prefix and one of the base URLs for your environment.

Every request other than the token exchange itself carries a short-lived bearer token, obtained by exchanging your clientId/clientSecret credential at POST /api/v1/auth/token:

sequenceDiagram
    autonumber
    participant P as Your integration
    participant L as Linra Omni API
    P->>L: POST /api/v1/auth/token<br/>{ clientId, clientSecret }
    alt Valid credential
        L-->>P: 200 { accessToken, expiresIn: 300 }
        P->>L: Any /api/v1/** call<br/>Authorization: Bearer accessToken
        L-->>P: { "state": "SUCCESS", "payload": { ... } }
    else Invalid credential
        L-->>P: 401 UNAUTHORIZED_INVALID_CREDENTIALS
    end

The token is scoped to your own partner identity, is short-lived by design (5 minutes, no refresh grant), and carries no data you supply yourself — see Authentication & tokens for the full model, why it’s deliberately short-lived, and the caching pattern you should build around it from day one.

Every response — success or error — is wrapped in the same three-key envelope, { state, payload, details }. See Errors for the full shape and the error codes you’ll actually encounter; it’s covered there rather than duplicated here since the envelope and the error-code catalogue are two halves of the same page.

Every list endpoint accepts page (1-based) and pageSize query parameters and returns its results alongside a pagination object with this exact shape, regardless of which resource you’re paging through:

{
"items": [ "..." ],
"pagination": { "page": 1, "pageSize": 20, "totalCount": 143, "totalPages": 8 }
}
  • totalCount is the FULL count across every page, not just the page you requested.
  • totalPages is derived (ceil(totalCount / pageSize)) — keep paging while page < totalPages.
  • pageSize is 1 to 100 and defaults to 20; page starts at 1. A value outside those bounds (or one that isn’t a whole number) returns 400 VALIDATION_INVALID_PAGE_SIZE / VALIDATION_INVALID_PAGE — it is never silently clamped.

Every /api/v1 endpoint validates its query string. An unknown parameter name (VALIDATION_UNKNOWN_QUERY_PARAMETER, with details.allowedParameters), a value that doesn’t parse as the parameter’s type (VALIDATION_INVALID_QUERY_PARAMETER; enums add details.allowedValues), or an unknown include token (VALIDATION_UNKNOWN_INCLUDE) returns 400 instead of being ignored — so a typo can never silently widen a filter. Date filters are read as UTC when they carry no offset; a date-only value means 00:00Z. See Errors.

Every /api/v1 route is rate-limited using a sliding window, keyed by your credential — a burst past the limit gets a 429 with a Retry-After header, never a silent throttle. See Rate limits for the exact limits per route and the recommended retry posture.

Every error response’s state field is a stable, machine-readable code — cross-tenant access always reports 404 (never 403, so a not-yours resource is indistinguishable from a nonexistent one), and no error response ever leaks a stack trace or internal detail. See Errors for the full HTTP-status-to-shape mapping and the codes you’re likely to build handling around.