curl --request POST \
--url https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"aisle": "A5",
"metadata": {
"feed_id": "wfm-2026-q2"
},
"physical_store_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"price": {
"amount": "5.99",
"currency": "USD"
},
"product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity_on_hand": 24,
"sku": "WFM-TM-12OZ",
"source": "manual",
"store_pickup_available": true
}
'import requests
url = "https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers"
payload = {
"aisle": "A5",
"metadata": { "feed_id": "wfm-2026-q2" },
"physical_store_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"price": {
"amount": "5.99",
"currency": "USD"
},
"product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity_on_hand": 24,
"sku": "WFM-TM-12OZ",
"source": "manual",
"store_pickup_available": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
aisle: 'A5',
metadata: {feed_id: 'wfm-2026-q2'},
physical_store_id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
price: {amount: '5.99', currency: 'USD'},
product_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
quantity_on_hand: 24,
sku: 'WFM-TM-12OZ',
source: 'manual',
store_pickup_available: true
})
};
fetch('https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'aisle' => 'A5',
'metadata' => [
'feed_id' => 'wfm-2026-q2'
],
'physical_store_id' => 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
'price' => [
'amount' => '5.99',
'currency' => 'USD'
],
'product_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'quantity_on_hand' => 24,
'sku' => 'WFM-TM-12OZ',
'source' => 'manual',
'store_pickup_available' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers"
payload := strings.NewReader("{\n \"aisle\": \"A5\",\n \"metadata\": {\n \"feed_id\": \"wfm-2026-q2\"\n },\n \"physical_store_id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n \"price\": {\n \"amount\": \"5.99\",\n \"currency\": \"USD\"\n },\n \"product_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"quantity_on_hand\": 24,\n \"sku\": \"WFM-TM-12OZ\",\n \"source\": \"manual\",\n \"store_pickup_available\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"aisle\": \"A5\",\n \"metadata\": {\n \"feed_id\": \"wfm-2026-q2\"\n },\n \"physical_store_id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n \"price\": {\n \"amount\": \"5.99\",\n \"currency\": \"USD\"\n },\n \"product_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"quantity_on_hand\": 24,\n \"sku\": \"WFM-TM-12OZ\",\n \"source\": \"manual\",\n \"store_pickup_available\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"aisle\": \"A5\",\n \"metadata\": {\n \"feed_id\": \"wfm-2026-q2\"\n },\n \"physical_store_id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n \"price\": {\n \"amount\": \"5.99\",\n \"currency\": \"USD\"\n },\n \"product_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"quantity_on_hand\": 24,\n \"sku\": \"WFM-TM-12OZ\",\n \"source\": \"manual\",\n \"store_pickup_available\": true\n}"
response = http.request(request)
puts response.read_body{
"aisle": "A5",
"id": "c4d5e6f7-8901-2345-abcd-ef6789012345",
"is_verified": false,
"metadata": {
"feed_id": "wfm-2026-q2"
},
"organization_id": "9b2c3d4e-f5a6-7890-abcd-ef1234567890",
"physical_store_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"price": {
"amount": "5.99",
"currency": "USD"
},
"product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity_on_hand": 24,
"sku": "WFM-TM-12OZ",
"source": "manual",
"status": "active",
"store_pickup_available": true
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Create in-store offer
Create a new in-store offer for the given organization. The (product_id, physical_store_id, sku) triple must be unique — sending a duplicate returns 422. Returns 404 when the organization, product, or store does not exist; 404 is also used (rather than 403) when the caller lacks MANAGE_OFFERS to avoid leaking organization existence. Offer creation is rate-limited per organization; past the limit this returns 429 with retry_after. An offer on a product another organization owns is stored but hidden from public surfaces unless Closient verifies it.
curl --request POST \
--url https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"aisle": "A5",
"metadata": {
"feed_id": "wfm-2026-q2"
},
"physical_store_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"price": {
"amount": "5.99",
"currency": "USD"
},
"product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity_on_hand": 24,
"sku": "WFM-TM-12OZ",
"source": "manual",
"store_pickup_available": true
}
'import requests
url = "https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers"
payload = {
"aisle": "A5",
"metadata": { "feed_id": "wfm-2026-q2" },
"physical_store_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"price": {
"amount": "5.99",
"currency": "USD"
},
"product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity_on_hand": 24,
"sku": "WFM-TM-12OZ",
"source": "manual",
"store_pickup_available": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
aisle: 'A5',
metadata: {feed_id: 'wfm-2026-q2'},
physical_store_id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
price: {amount: '5.99', currency: 'USD'},
product_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
quantity_on_hand: 24,
sku: 'WFM-TM-12OZ',
source: 'manual',
store_pickup_available: true
})
};
fetch('https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'aisle' => 'A5',
'metadata' => [
'feed_id' => 'wfm-2026-q2'
],
'physical_store_id' => 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
'price' => [
'amount' => '5.99',
'currency' => 'USD'
],
'product_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'quantity_on_hand' => 24,
'sku' => 'WFM-TM-12OZ',
'source' => 'manual',
'store_pickup_available' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers"
payload := strings.NewReader("{\n \"aisle\": \"A5\",\n \"metadata\": {\n \"feed_id\": \"wfm-2026-q2\"\n },\n \"physical_store_id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n \"price\": {\n \"amount\": \"5.99\",\n \"currency\": \"USD\"\n },\n \"product_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"quantity_on_hand\": 24,\n \"sku\": \"WFM-TM-12OZ\",\n \"source\": \"manual\",\n \"store_pickup_available\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"aisle\": \"A5\",\n \"metadata\": {\n \"feed_id\": \"wfm-2026-q2\"\n },\n \"physical_store_id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n \"price\": {\n \"amount\": \"5.99\",\n \"currency\": \"USD\"\n },\n \"product_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"quantity_on_hand\": 24,\n \"sku\": \"WFM-TM-12OZ\",\n \"source\": \"manual\",\n \"store_pickup_available\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/retailers/api/v1/organizations/{organization_id}/in-store-offers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"aisle\": \"A5\",\n \"metadata\": {\n \"feed_id\": \"wfm-2026-q2\"\n },\n \"physical_store_id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n \"price\": {\n \"amount\": \"5.99\",\n \"currency\": \"USD\"\n },\n \"product_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"quantity_on_hand\": 24,\n \"sku\": \"WFM-TM-12OZ\",\n \"source\": \"manual\",\n \"store_pickup_available\": true\n}"
response = http.request(request)
puts response.read_body{
"aisle": "A5",
"id": "c4d5e6f7-8901-2345-abcd-ef6789012345",
"is_verified": false,
"metadata": {
"feed_id": "wfm-2026-q2"
},
"organization_id": "9b2c3d4e-f5a6-7890-abcd-ef1234567890",
"physical_store_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"price": {
"amount": "5.99",
"currency": "USD"
},
"product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity_on_hand": 24,
"sku": "WFM-TM-12OZ",
"source": "manual",
"status": "active",
"store_pickup_available": true
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Authorizations
Path Parameters
UUID of the organization that will own the offer.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Body
Payload for creating a new in-store offer.
product_id and physical_store_id are required; everything else has a
sensible default. The (product, store, sku) triple must be unique — sending
a duplicate will return 422.
UUID of the catalog Product this offer is for. The product must already exist; create it via the products API before adding offers. Immutable after creation — moving a row to a different product means deleting and recreating.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$UUID of the PhysicalStore (a single brick-and-mortar location, not the parent retailer chain) this offer lives at. The store's own currency seeds a new offer's denomination; the offer then carries it on its own row.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Developer-attached key/value data. Send {} or null to clear. Empty-string values delete that key. Omitted keys are preserved.
Show child attributes
Show child attributes
Retailer-specific SKU for this product at this store. Empty string when the source doesn't expose a SKU (scraped offers often don't). Together with product_id and physical_store_id this is the row's natural key — the trio must be unique.
100Price as {"amount", "currency"}; both are required together. Omit or send null when the price is unknown; the row is then priceless and excluded from cheapest-active resolution. There is no default currency — an amount alone is rejected.
Show child attributes
Show child attributes
{ "amount": "5.99", "currency": "USD" }
Where this offer row came from. Affects source_priority defaults used when reconciling conflicting rows for the same (product, store, sku). manual is the safe default for ad-hoc API writes; use the more specific value when you know it.
brand, affiliate_feed, scrape, pos_sync, manual, user_feedback Units physically on the shelf at last count. null when unknown — common for stores without a POS sync. Decremented as sales come in (POS sync) or set manually via update.
x >= 0Aisle/shelf locator for in-store wayfinding (e.g. A5, Dairy 12). Free-form and store-specific; we do not validate the format.
50true when the product can be reserved online and picked up at this store. Independent of status: an offer can be active on the shelf without supporting reservation.
Response
OK
An offer for a product at a specific physical store.
Mirrors :class:apps.retailers.models.InStoreOffer, whose price stores its
amount and ISO 4217 currency together. One row per
(product, physical_store, sku) — that trio is the unique key.
URL-safe 22-character shortuuid encoding of the row's UUID primary key. Stable across the row's lifetime; suitable for sharing in URLs, log lines, and external SDK clients. Accepted on input as either the shortuuid form or the canonical UUID form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$UUID of the catalog Product this offer is for. The product must already exist; create it via the products API before adding offers. Immutable after creation — moving a row to a different product means deleting and recreating.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$UUID of the PhysicalStore (a single brick-and-mortar location, not the parent retailer chain) this offer lives at. The store's own currency seeds a new offer's denomination; the offer then carries it on its own row.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Developer-attached key/value data attached to this object. Up to 50 keys; key max 40 chars, value max 500 chars.
Show child attributes
Show child attributes
URL-safe 22-character shortuuid encoding of the row's UUID primary key. Stable across the row's lifetime; suitable for sharing in URLs, log lines, and external SDK clients. Accepted on input as either the shortuuid form or the canonical UUID form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Retailer-specific SKU for this product at this store. Empty string when the source doesn't expose a SKU (scraped offers often don't). Together with product_id and physical_store_id this is the row's natural key — the trio must be unique.
100Structured price: amount and currency are stored together on the row and returned together. Both are null when the price is unknown — display 'Call for price' rather than $0.00, and never assume a denomination.
Show child attributes
Show child attributes
{ "amount": "5.99", "currency": "USD" }
Lifecycle state of this offer at this store. active is resolvable; out_of_stock, seasonal, and discontinued are filtered out of cheapest-active resolution. seasonal is a hint that the offer comes back; discontinued is permanent.
active, discontinued, seasonal, out_of_stock true when Closient has confirmed this offer is real. Read-only: set only by Closient-controlled paths, never through this API. An offer submitted by an organization other than the product's owner is shown on public surfaces (product page, search, structured data) only when this is true.
Where this offer row came from. Affects source_priority defaults used when reconciling conflicting rows for the same (product, store, sku). manual is the safe default for ad-hoc API writes; use the more specific value when you know it.
brand, affiliate_feed, scrape, pos_sync, manual, user_feedback Units physically on the shelf at last count. null when unknown — common for stores without a POS sync. Decremented as sales come in (POS sync) or set manually via update.
x >= 0Aisle/shelf locator for in-store wayfinding (e.g. A5, Dairy 12). Free-form and store-specific; we do not validate the format.
50true when the product can be reserved online and picked up at this store. Independent of status: an offer can be active on the shelf without supporting reservation.