Skip to content

Offers, cost & stock

POST /api/v1/vendor/offers (requires Offers) accepts two shapes:

  • A proposed match — set variantId to an existing, active catalog variant you’re confirming you can supply.
  • A free-form new entry — omit variantId and supply rawBrandName/rawProductName (and optionally rawConcentration/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.

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.

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).

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 changependingCost/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.

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 (requires Cost) — carries only cost and costIsVatInclusive. 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} (requires Stock) — carries only declaredStockQuantity and declaredInStock. 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.

costIsVatInclusive, on a write, controls how cost is interpreted:

  • false (default) — exclusive: cost is the net amount; VAT is calculated on top.
  • trueinclusive: cost is the VAT-inclusive gross amount; the net cost and your own input VAT are back-derived from it and returned as inputVatAmount/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.

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.