Update vendor offer
Update an existing API-submitted offer.
Offers
/api/v1/scent/offers/{id}A partial update: send only the fields you are changing. A field you omit (or send as null)
keeps its stored value; an empty string ("") clears an optional text field (vendorSku,
vendorExternalId, rawConcentration, rawDescription; also rawBrandName/rawProductName once the
offer is matched to a catalog variant). A whitespace-only string counts as empty. With no price
field in the body the stored price is kept. Minimal bodies: {"barcode": "4006381333931"} (adds a first barcode to an
offer created before barcodes were required — the offer keeps its status and every other
field); {"cost": 120, "costIsVatInclusive": false} (Model 1);
{"salePrice": 300, "salePriceIsVatInclusive": true} (Model 2).
A cost change on an Approved offer becomes a pending change awaiting reviewer approval; an
identity-fact change on an Approved offer resets it to PendingReview. Adding a first barcode
is not an identity change. Never changes the
matched variantId — re-matching is a reviewer-only action, and never carries declared stock
— push stock via PATCH /offers/{offerId}/stock instead. Reaches ONLY an offer THIS vendor
submitted through THIS door (source: Api) — one belonging to another vendor, or one a
catalog admin entered manually on this vendor’s behalf, is reported as 404. Requires the
Offers capability — prefer PATCH /offers/{id}/cost for a cost-only push from a credential
holding only the Cost capability.
Path parameters
idstring (uuid)RequiredBody parameters
vendorSkustringOmit to keep; “” clears.
barcodestringThe product’s EAN/UPC barcode (GTIN). Omit to keep the stored barcode (never cleared). An offer created before barcodes were required can have its price changed without one, but needs one before any product-detail change (vendorSku, vendorExternalId, raw* fields) and before any edit that resubmits a Rejected or PendingRepricing offer to review (every edit of a Rejected offer; a price change on a PendingRepricing one; the same applies to the stock, cost and sale-price pushes) → otherwise 400 VALIDATION_ERRORS (field Barcode). A value you send must be valid.
vendorExternalIdstringOmit to keep; “” clears.
costnumber (decimal)Model 1 only (must be omitted under Model 2). Omit to keep the stored cost.
costIsVatInclusivebooleanREQUIRED whenever cost is present — there is no default. See CreateVendorOfferRequest.costIsVatInclusive. Safe round-trip: a VendorOfferResponse always returns cost.isVatInclusive false alongside the stored NET amount, so resubmitting a GET response verbatim satisfies this and changes nothing.
salePricenumber (decimal)Model 2 only (must be omitted under Model 1). Omit to keep the stored sale price. On an Approved offer a change becomes a pending change awaiting reviewer approval.
salePriceIsVatInclusivebooleanREQUIRED whenever salePrice is present. See CreateVendorOfferRequest.salePriceIsVatInclusive.
rawBrandNamestringOmit to keep. Cannot be cleared on an offer not yet matched to a catalog variant.
rawProductNamestringOmit to keep. Cannot be cleared on an offer not yet matched to a catalog variant.
rawConcentrationstringOmit to keep; “” clears.
rawSizeMlintegerOmit to keep.
rawDescriptionstringOmit to keep; “” clears.
Response
idstring (uuid)statusstring (enum)Draft (saved, not submitted) → PendingMatch (no variantId, awaiting reviewer match) or PendingReview (a proposed match, awaiting approval) → Approved (live) → Withdrawn/Suspended; Rejected is reachable from Draft/PendingMatch/PendingReview. You never set Approved yourself. PendingRepricing: your agreement’s pricing model was switched by Linra while this offer was Approved or PendingReview — the offer’s linked supply is paused (not sellable) until you resubmit ONLY the new model’s price field (cost under Model 1, sale price under Model 2; stock updates are still accepted and never change this status) and it is re-reviewed, at which point it returns to PendingReview and then Approved as usual.
pricingModelobjectCostPlusMargin (“Model 1”): you declare cost, Linra adds a markup. PercentageOfSale (“Model 2”): you declare the final sale price; Linra + the reselling partner deduct a percentage of it.
vendorExternalIdstringOpaque id reserved for API integrations; doubles as an idempotency key.
product.variantIdstring (uuid)A vendor-proposed match to an existing, active Variant. Null for an unmatched free-form entry.
product.vendorSkustringproduct.barcodestringThe product’s EAN/UPC barcode. Null only on an offer created before the barcode became required, until its next edit.
product.rawBrandNamestringproduct.rawProductNamestringproduct.rawConcentrationstringproduct.rawSizeMlintegerproduct.rawDescriptionstringcost.amountnumber (decimal)The live, stored NET cost Linra pays you (SAR).
cost.isVatInclusivebooleanAlways false — pair amount with this value (false) on a write to safely resubmit it unchanged. This is the one entry mode that can never re-interpret an already-net figure as a fresh gross one. See enteredVatInclusive for how you actually entered it.
cost.enteredVatInclusivebooleanDisplay-only — your actual original entry-mode preference. NEVER accepted on a write; use isVatInclusive (always false) for that.
cost.inputVatnumber (decimal)Your own input VAT on the current cost, back-derived when enteredVatInclusive is true.
cost.pendingobjectA cost change staged by you (or an internal edit) on an Approved offer, awaiting reviewer approval — the live cost above is untouched until applied.
price.amountnumber (decimal)The live, stored NET sale price (SAR).
price.isVatInclusivebooleanAlways false — pair amount with this value (false) on a write to safely resubmit it unchanged. See enteredVatInclusive for how you actually entered it.
price.enteredVatInclusivebooleanDisplay-only — your actual original entry-mode preference. NEVER accepted on a write.
price.vatnumber (decimal)The VAT on the current sale price, back-derived when enteredVatInclusive is true.
price.pendingobjectA sale-price change staged by you on an Approved offer, awaiting reviewer approval — the live sale price above is untouched until applied.
stock.declaredQuantityintegerYour declared quantity on hand, when you report a number rather than a bare yes/no.
stock.declaredInStockbooleanreview.reviewedAtstring (date-time)review.reasonstringOther responses
400Request validation failed.401No bearer token (UNAUTHORIZED_MISSING_TOKEN), an invalid token (UNAUTHORIZED_INVALID_TOKEN) or an expired one (UNAUTHORIZED_TOKEN_EXPIRED — mint a new token). UPSTREAM_AUTH_FAILED is different: a dependent service refused the Gateway itself. That is a server-side fault — retry later and do NOT re-mint your token. See the Rate limits & errors guide.403Your token doesn’t carry the capability this endpoint requires.404Unknown offer id, or one belonging to another vendor.415The request body is not sent as application/json (UNSUPPORTED_MEDIA_TYPE).429Too many requests. Retry after the Retry-After header (seconds).502A dependent service returned an invalid response (BAD_GATEWAY). Retry with backoff.503The service is temporarily unavailable (SERVICE_UNAVAILABLE). Retry with backoff.504A dependent service did not respond in time (GATEWAY_TIMEOUT). Retry with backoff.curl --request PATCH \ --url https://api-omni-vendors.linra.net/api/v1/scent/offers/5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "vendorSku": "string", "barcode": "string", "vendorExternalId": "string", "cost": 0, "costIsVatInclusive": true, "salePrice": 0, "salePriceIsVatInclusive": true, "rawBrandName": "string", "rawProductName": "string", "rawConcentration": "string", "rawSizeMl": 0, "rawDescription": "string" }'const res = await fetch('https://api-omni-vendors.linra.net/api/v1/scent/offers/5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b', { method: 'PATCH', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ vendorSku: 'string', barcode: 'string', vendorExternalId: 'string', cost: 0, costIsVatInclusive: true, salePrice: 0, salePriceIsVatInclusive: true, rawBrandName: 'string', rawProductName: 'string', rawConcentration: 'string', rawSizeMl: 0, rawDescription: 'string' }),});const { payload } = await res.json();var response = await httpClient.PatchAsJsonAsync( "https://api-omni-vendors.linra.net/api/v1/scent/offers/5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b", new { vendorSku = "string", barcode = "string", vendorExternalId = "string", cost = 0, costIsVatInclusive = true, salePrice = 0, salePriceIsVatInclusive = true, rawBrandName = "string", rawProductName = "string", rawConcentration = "string", rawSizeMl = 0, rawDescription = "string" });
var envelope = await response.Content.ReadFromJsonAsync<ApiResponse<VendorOfferResponse>>();Response
{ "state": "SUCCESS", "payload": { "id": "5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b", "status": "Draft", "pricingModel": {}, "vendorExternalId": "string", "product": { "variantId": "5b1e3a1c-9d2e-4f3a-8b1c-9f0a2e3d4c5b", "vendorSku": "string", "barcode": "string", "rawBrandName": "string", "rawProductName": "string", "rawConcentration": "string", "rawSizeMl": 0, "rawDescription": "string" }, "cost": { "amount": 0, "isVatInclusive": true, "enteredVatInclusive": true, "inputVat": 0, "pending": { "amount": 0, "inputVat": 0 } }, "price": { "amount": 0, "isVatInclusive": true, "enteredVatInclusive": true, "vat": 0, "pending": { "amount": 0, "vat": 0 } }, "stock": { "declaredQuantity": 0, "declaredInStock": true }, "review": { "reviewedAt": "2026-08-22T09:14:03Z", "reason": "string" } }, "details": null}
