Vendor Integration Standard
This document is the single canonical reference for integrating with the Linra Omni vendor API. If something in a guide or the API Reference ever seems to disagree with this page, this page wins — file it as a docs bug.
1. Who you are, and who you are not
Section titled “1. Who you are, and who you are not”A vendor is a supplier Linra sources physical product from (e.g. a perfume distributor). This
is a different relationship, a different identity plane, and a different API from a partner (a
business reselling through Linra Omni). If you were told you’re integrating as a partner, you are
in the wrong docs — go to the
Linra Omni Developer Docs instead. The two credential types are
issued from the same identity provider but are cryptographically distinct: a vendor token carries
audience vendor-api and is rejected by every partner-facing route; a partner token carries
audience partner-api and is rejected by every vendor-facing route.
2. Authentication
Section titled “2. Authentication”POST /api/v1/vendor/auth/token exchanges a clientId + clientSecret for a short-lived JWT
bearer token (audience vendor-api). Send it as Authorization: Bearer <token> on every other
/api/v1/vendor request.
- Lifetime: 5 minutes, no refresh-token grant — re-authenticate with your long-lived
clientId/clientSecretinstead of holding a token indefinitely. See Authentication & capabilities for the caching pattern. - No delegation. Unlike the partner plane’s
onBehalfOf, a vendor credential has no hierarchy to act on behalf of — a vendor is a single, flat identity. - Capabilities are baked into the token. Your credential is issued with a specific set of capabilities (see §3); the issued token’s claims carry exactly that set, and every vendor endpoint checks it from the token alone — no additional lookup.
3. Capabilities — the four independently-grantable doors
Section titled “3. Capabilities — the four independently-grantable doors”Every vendor credential is issued with a subset of four capabilities. They are independently grantable — a credential can hold any combination, including exactly one.
| Capability | Grants access to | Endpoints |
|---|---|---|
| Offers | Submit and edit your own product offers | GET/POST /api/v1/vendor/offers, GET/PATCH /api/v1/vendor/offers/{id} |
| Cost | Push a cost change for one of your offers | PATCH /api/v1/vendor/offers/{id}/cost |
| Stock | Push declared availability/quantity for one of your offers | PATCH /api/v1/vendor/stock/{offerId} |
| Orders | Poll your assigned fulfilment groups, push fulfilment status, and manage webhook subscriptions | GET /api/v1/vendor/orders, GET/PATCH /api/v1/vendor/orders/{groupId}[/status], all of /api/v1/vendor/webhooks/** |
A credential holding only Stock, for example, can push availability updates but cannot read or
edit offers, push cost, or see any order data — each door checks its OWN required capability, and a
missing one is rejected with 403 FORBIDDEN_CAPABILITY_NOT_GRANTED, never silently ignored or
downgraded to a partial response.
Webhook subscription management (§7) rides the Orders capability rather than a fifth one — a
credential with no order/offer-decision data to subscribe to has no webhook to configure in the
first place. If your integration is Offers-only and wants offer-review-decision push notifications
without polling, ask your onboarding contact — this is a known, tracked gap (see §9).
4. The three-door model
Section titled “4. The three-door model”If you’ve read integration docs for other B2B APIs, you may expect one big “vendor account” resource. Linra Omni instead exposes four narrow, single-purpose doors, each doing exactly one thing:
- Offers (
/api/v1/vendor/offers) — the identity/cost/stock facts about a product you supply, submitted through a review workflow. Nothing here is ever live until an internal reviewer approves it. - Cost (
/api/v1/vendor/offers/{id}/cost) — a narrow sibling action on the SAME offer resource, but gated by its own capability so a Cost-only integration never needs Offers access. Always routes through the same approval pipeline as an Offers-capability cost edit. - Stock (
/api/v1/vendor/stock/{offerId}) — applied INSTANTLY, no review gate. Availability is a real-world fact, not a commercial term, so it doesn’t wait on a reviewer. - Orders (
/api/v1/vendor/orders,/api/v1/vendor/webhooks) — read-only visibility into what you’ve been assigned to fulfil, plus a narrow status-push action and webhook subscription management. You can never see an order, a partner, or another vendor’s data through this door — only your own assigned fulfilment groups.
Every door delegates to the exact same internal write path Linra’s own catalog/fulfilment teams use — you are not on a second-class “integration API,” you are calling the same machinery through a narrower, capability-gated front door.
5. Idempotency & retries
Section titled “5. Idempotency & retries”- Offer creation is idempotent when you set
vendorExternalId. Re-submitting the SAMEvendorExternalIdreturns the already-created offer unchanged — safe to retry after a timeout or an unknown outcome. Omit it and everyPOSTcreates a new offer. - Cost and stock pushes are naturally idempotent by overwrite. Re-pushing the identical value is a pure no-op (stock) or overwrites the same pending value (cost) — never a duplicate.
- Fulfilment status pushes are idempotent and never error on a stale/backward/duplicate/invalid
target. A push that can’t apply returns
200withapplied: falseand the group’s unchanged current state — build your retry logic to treat this as informational, not a failure. - Build a single retry-with-backoff policy at your HTTP client layer for
5xxand429responses, honoringRetry-Afteron a 429 (see §6). Never retry a4xxother than429without changing the request — those are telling you the request itself is wrong.
6. Rate limits
Section titled “6. Rate limits”Every /api/v1/vendor route is rate-limited using a sliding window (evaluated over 4 sub-segments,
so a burst at a window boundary is smoothed, not doubled), partitioned per authenticated caller.
| Route | Limit | Window |
|---|---|---|
POST /api/v1/vendor/auth/token |
10 requests | 60 seconds |
Every other /api/v1/vendor/** route |
120 requests | 60 seconds |
A 429 never includes remaining-quota detail beyond the Retry-After header (seconds) — respect
it rather than guessing a retry interval. If you’re polling GET /api/v1/vendor/orders for new
assignments, use the changedSince delta-sync parameter (§7) rather than re-fetching the full list
on every poll, and prefer the webhook (§8) over polling at all if your Orders integration is
latency-sensitive.
7. Status vocabularies
Section titled “7. Status vocabularies”Offer status
Section titled “Offer status”Draft → PendingMatch/PendingReview → Approved → (Suspended | Withdrawn), or Rejected from any
pending state. You never set Approved yourself — that’s always an internal reviewer action. A
Draft offer is a save-for-later with no review-queue entry yet.
| Status | Meaning |
|---|---|
Draft |
Saved, not yet submitted for review. |
PendingMatch |
Submitted without a variantId — awaiting a reviewer to match or create the canonical catalog entry. |
PendingReview |
Submitted with a proposed match — awaiting reviewer approval. |
Approved |
Live. A cost push here becomes a pending change (see below), never overwrites the live cost directly. |
Rejected |
The reviewer rejected the submission, with a reason. |
Withdrawn |
Withdrawn (by you or an internal action). |
Suspended |
Temporarily paused by an internal reviewer. |
An Approved offer’s pendingCost/pendingInputVatAmount fields are non-null exactly when a cost
push is awaiting reviewer approval — poll these, or subscribe to vendor.offer.review-decision
(§8), to learn the outcome.
Fulfilment status
Section titled “Fulfilment status”Pending → Packed → Shipped → Delivered, or Cancelled from Pending/Shipped, or Returned
after delivery. You may only ever push Shipped, Delivered, or Cancelled — Packed is an
internal operator-only transition (Linra confirms packing before you ship), and Returned is
customer-return-driven, never vendor-initiated. Pushing any other target, or a backward/stale/
already-applied one, is a clean no-op (applied: false), never an error — see
Orders & fulfilment status for the full transition table and
worked examples.
8. Webhooks
Section titled “8. Webhooks”Instead of polling GET /api/v1/vendor/orders, subscribe to push notifications for two event
types:
| Event type | Fired when |
|---|---|
vendor.order.assigned |
A fulfilment group is newly assigned to you (at order creation, or a later manual/auto sourcing decision). |
vendor.offer.review-decision |
One of your offers is approved, rejected, or its pending cost change is approved/rejected. |
Payloads are deliberately minimal — an assignment notification carries only the group id, the
order’s canonical id, and your vendor id; fetch full shipping/line details via
GET /api/v1/vendor/orders/{groupId}. See
Webhooks & HMAC verification for the full envelope shape, the signature
scheme, a verified code sample, and retry/backoff behavior.
9. What’s explicitly out of scope today
Section titled “9. What’s explicitly out of scope today”- No credential management on THIS API. There is no create/rotate/deactivate-credential
endpoint under
/api/v1/vendor— that’s a permanent design choice, not a gap (mirrors the partner API’s own posture: a live API token should never be able to mint or manage the credential that issued it). Credential management for a Vendor-typed user DOES already exist, self-service, on the Linra Omni Portal dashboard (its own separate login, not this API) — see §11 Onboarding. - No offer-review-decision webhook without the Orders capability. An Offers-only credential
cannot subscribe to
vendor.offer.review-decisionand must pollGET /api/v1/vendor/offersinstead — a per-event-type capability split is a tracked future item if offer-only integrations turn out to be common. - No vendor-visible partner/order-commercial data, ever. Order totals, commission, VAT breakdown, discount codes, the reselling partner’s identity, and Linra’s own cost-floor/margin policy are permanently out of scope for this API by design (§4) — this is not a “not yet built” gap, it never will be.
10. Service levels
Section titled “10. Service levels”The specific numeric commitments below are TBD — to be set by Linra business/ops. The structure (what will be measured, and how it will be communicated) is fixed; the numbers are not invented here.
| Metric | Target |
|---|---|
API availability (/api/v1/vendor/**) |
TBD — to be set by Linra business/ops |
| Offer review turnaround (submission → Approved/Rejected) | TBD — to be set by Linra business/ops |
| Webhook delivery latency (event occurs → first delivery attempt) | Today: VendorWebhookDispatcher polls every 5 seconds — a soft, implementation-level figure, not a contractual SLA. A contractual latency target is TBD. |
| Support response time for integration issues | TBD — to be set by Linra business/ops |
11. Onboarding
Section titled “11. Onboarding”- Contact Linra to begin vendor onboarding (commercial + catalog-side setup — outside this
API’s scope). Linra creates your
Vendorrecord, a dashboard login for the Linra Omni Portal (a separate, human-operated login — not part of this API), and your first API credential — with the capability set (Offers/Stock/Cost/Orders, any combination) your onboarding conversation established. The first credential for a new vendor is always Linra-issued; self-service generation requires at least one already-active credential to establish a baseline and is rejected otherwise (422 BUSINESS_VENDOR_FIRST_CREDENTIAL_REQUIRES_ONBOARDING). - Manage further credentials yourself. Once you hold at least one active credential, generate
additional ones self-service under My Vendor API Credentials on the Portal dashboard — e.g.
a second credential for a different environment, or to rotate a compromised one (generate new,
then deactivate the old — the same zero-downtime pattern the partner API’s own credential
guidance describes). Self-service generation can never grant a capability beyond the union of
what your OTHER active credentials already hold — requesting a superset is rejected
(
403 FORBIDDEN_CAPABILITY_NOT_GRANTED_TO_VENDOR). Need a genuinely new capability (e.g. you were onboarded Offers-only and now also need Orders)? That still goes through your onboarding contact, not self-service. - Exchange the credential for a token (§2) and confirm you can call an endpoint matching a granted capability, and that a NON-granted capability correctly 403s — see Getting started.
- Submit a test offer (Offers capability) and confirm it appears in Linra’s internal review queue.
- If you hold the Orders capability, subscribe to
vendor.order.assigned(§8) or set up polling withchangedSince(§7), and usePOST /api/v1/vendor/webhooks/{id}/test-fireto prove your endpoint receives and correctly verifies a signed delivery before going live. - Move to production once staging is proven end-to-end — request a production credential with the same capability set.
12. Change control
Section titled “12. Change control”This is a versioned facade (/api/v1/vendor) — a breaking change to an existing endpoint’s
request/response shape gets a new version prefix, never an in-place breaking change to v1. See
the Changelog for what’s shipped since this document was first published.