Offers, cost & stock
Submitting an offer
Section titled “Submitting an offer”POST /api/v1/vendor/offers (requires Offers) accepts two shapes:
- A proposed match — set
variantIdto an existing, active catalog variant you’re confirming you can supply. - A free-form new entry — omit
variantIdand supplyrawBrandName/rawProductName(and optionallyrawConcentration/rawSizeMl/rawDescription) as you know the product. An internal reviewer will match it to an existing catalog entry or create a new one.
Either way, the offer lands in the SAME review queue Linra’s own catalog team works from — nothing you submit is live until a reviewer approves it.
Idempotent submission
Section titled “Idempotent submission”Set vendorExternalId to your own opaque identifier for this offer (e.g. your internal SKU-to-us
mapping key). Re-submitting the exact same vendorExternalId returns the ALREADY-CREATED offer
unchanged, rather than creating a duplicate or erroring — safe to retry blindly after a timeout or
an unknown outcome. Omit vendorExternalId and every POST creates a brand-new offer, even with
identical content.
Draft vs. submitted
Section titled “Draft vs. submitted”saveAsDraft: true on create saves the offer with no review-queue entry yet — a “save for later.”
Everything else submits for review immediately (matched → PendingReview, unmatched →
PendingMatch).
The field-ownership policy
Section titled “The field-ownership policy”Not every field on an offer behaves the same way when you edit it — this is deliberate, not an inconsistency:
| Field(s) | Behavior |
|---|---|
declaredInStock, declaredStockQuantity |
Apply instantly, regardless of the offer’s status. Availability is a real-world fact, not a commercial term subject to review. |
cost, costIsVatInclusive |
On a not-yet-Approved offer: applies directly (nothing is live yet regardless). On an Approved offer: becomes a pending change — pendingCost/pendingInputVatAmount populate, the live cost is untouched until a reviewer approves or rejects the change. |
vendorSku, vendorExternalId, rawBrandName, rawProductName, rawConcentration, rawSizeMl, rawDescription |
On an Approved offer, changing any of these resets the offer to PendingReview — an identity-fact change on a live offer needs re-review. |
variantId |
Never editable by you. Re-matching an offer to a different catalog variant is exclusively an internal reviewer action. |
Cost-only and stock-only pushes
Section titled “Cost-only and stock-only pushes”If your integration only needs to keep cost or stock current — not manage full offer
submissions — use the narrower, single-purpose doors instead of the general update endpoint. Each
requires only its own capability, independent of Offers:
PATCH /api/v1/vendor/offers/{id}/cost(requiresCost) — carries onlycostandcostIsVatInclusive. Every other field is left exactly as it was; this endpoint can never touch identity or stock, even by accident.PATCH /api/v1/vendor/stock/{offerId}(requiresStock) — carries onlydeclaredStockQuantityanddeclaredInStock. Reachable for ANY of your offers regardless of how they were originally submitted (through this API, or entered manually by a Linra catalog admin on your behalf) — stock is a fact about your SKU, not about which door the offer’s identity entered through.
Both are idempotent: re-pushing the identical cost overwrites the same pending value (never a duplicate pending artifact); re-pushing the identical stock state is a pure no-op.
VAT declaration
Section titled “VAT declaration”costIsVatInclusive, on a write, controls how cost is interpreted:
false(default) — exclusive:costis the net amount; VAT is calculated on top.true— inclusive:costis the VAT-inclusive gross amount; the net cost and your own input VAT are back-derived from it and returned asinputVatAmount/pendingInputVatAmount.
This takes effect immediately regardless of the offer’s Approved/pending-cost gating described above — it’s a declaration about how to interpret the number you sent, not a commercial change to be reviewed on its own.
Important: only use costIsVatInclusive: true when cost is a genuinely NEW gross figure you
are declaring right now. Internally, cost is always stored net — there is no way to recover the
original gross number you may have entered previously. Resubmitting a number you read back from
this API (see below) under costIsVatInclusive: true would incorrectly treat that already-net
figure as a fresh gross one and strip VAT off it a second time.
Reading cost back — the response’s safe round-trip contract
Section titled “Reading cost back — the response’s safe round-trip contract”The response’s costIsVatInclusive field is always false, regardless of how you originally
entered the offer — it is not an echo of what you declared. This is deliberate: cost on a
response is always the net figure, and false (exclusive) is the one entry mode that is
mathematically guaranteed to round-trip as a pure identity — resubmitting the exact cost +
costIsVatInclusive: false pair you just read, as part of an unrelated edit (e.g. changing
vendorSku), is always safe and never changes the cost, no matter how the offer was originally
entered.
To see how you actually entered the cost (net/exclusive vs. gross/inclusive), read
costEnteredVatInclusive instead — a separate, display-only field. It is never accepted on any
write; it exists purely so your integration (or a human) can tell how the number was originally
declared.
// GET response for an offer originally entered as cost=115, costIsVatInclusive=true (15% VAT){ "cost": 100.00, "costIsVatInclusive": false, "costEnteredVatInclusive": true, "inputVatAmount": 15.00}A stock-only push never touches cost, regardless of entry mode. PATCH /api/v1/vendor/stock/{offerId}
only ever carries declaredStockQuantity/declaredInStock — cost is completely untouched by a
stock push, no matter how the offer was originally entered or how many times you push stock in a
row.
Scoping
Section titled “Scoping”Every offer/cost/stock endpoint reaches ONLY an offer YOU submitted (or, for stock, any offer
associated with your vendor id regardless of submission channel). An offer belonging to another
vendor — or one a Linra catalog admin entered manually — is reported as 404 NOT_FOUND_VENDOR_OFFER,
indistinguishable from a genuinely nonexistent id. There is no cross-vendor visibility anywhere on
this API.