curl --request PATCH \
--url https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"is_active": true,
"sku": "SEPH-2311456"
}
'import requests
url = "https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}"
payload = {
"is_active": True,
"sku": "SEPH-2311456"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({is_active: true, sku: 'SEPH-2311456'})
};
fetch('https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}', 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/retailers/{retailer_id}/listings/{listing_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'is_active' => true,
'sku' => 'SEPH-2311456'
]),
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/retailers/{retailer_id}/listings/{listing_id}"
payload := strings.NewReader("{\n \"is_active\": true,\n \"sku\": \"SEPH-2311456\"\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"is_active\": true,\n \"sku\": \"SEPH-2311456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"is_active\": true,\n \"sku\": \"SEPH-2311456\"\n}"
response = http.request(request)
puts response.read_body{
"attributes": {
"categoryIds": [
"cat130042"
],
"sephoraDescription": "A lightweight serum."
},
"badges": [
{
"is_active": true,
"label": "Clean at Sephora",
"slug": "clean-at-sephora"
}
],
"created": "2026-01-15T10:00:00Z",
"gtin": "00812345678901",
"id": "b2c3d4e5f60718293a4b5c6d7e",
"is_active": true,
"modified": "2026-02-01T09:30:00Z",
"product_id": "9a8b7c6d5e4f30211a2b3c4d5e",
"retailer_id": "f47ac10b58cc4372a5670e02b2",
"sku": "SEPH-2311456",
"source_reference": "P442501",
"urls": [
{
"is_active": true,
"store_url": "https://www.sephora.ca",
"storefront_id": "c3d4e5f6789012345abcdef012",
"url": "https://www.sephora.ca/product/P442501"
}
]
}{
"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"
}Update one retailer product listing
Correct a single listing’s own scalar columns — SKU, active flag, attributes, source reference. Only the fields present in the body are written.
Badges and per-property URLs are not writable here. Both are synced as a whole set by POST /retailers/{retailer_id}/listings, under invariants that span a join (a badge belongs to its own retailer; a URL attaches to an active storefront of its own retailer). Send the listing through the upsert to change them.
attributes replaces the stored dict rather than merging into it, which is what the upsert does with the same field — a merge would make removing a key impossible.
Authorization is identical to creating a listing for this retailer: an organization-private retailer requires OWNER or MANAGER on the organization that owns it; a canonical (Closient-curated) retailer requires a staff account or the catalog-import service account. Whoever may upsert a listing may correct and retire it.
curl --request PATCH \
--url https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"is_active": true,
"sku": "SEPH-2311456"
}
'import requests
url = "https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}"
payload = {
"is_active": True,
"sku": "SEPH-2311456"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({is_active: true, sku: 'SEPH-2311456'})
};
fetch('https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}', 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/retailers/{retailer_id}/listings/{listing_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'is_active' => true,
'sku' => 'SEPH-2311456'
]),
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/retailers/{retailer_id}/listings/{listing_id}"
payload := strings.NewReader("{\n \"is_active\": true,\n \"sku\": \"SEPH-2311456\"\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"is_active\": true,\n \"sku\": \"SEPH-2311456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/listings/{listing_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"is_active\": true,\n \"sku\": \"SEPH-2311456\"\n}"
response = http.request(request)
puts response.read_body{
"attributes": {
"categoryIds": [
"cat130042"
],
"sephoraDescription": "A lightweight serum."
},
"badges": [
{
"is_active": true,
"label": "Clean at Sephora",
"slug": "clean-at-sephora"
}
],
"created": "2026-01-15T10:00:00Z",
"gtin": "00812345678901",
"id": "b2c3d4e5f60718293a4b5c6d7e",
"is_active": true,
"modified": "2026-02-01T09:30:00Z",
"product_id": "9a8b7c6d5e4f30211a2b3c4d5e",
"retailer_id": "f47ac10b58cc4372a5670e02b2",
"sku": "SEPH-2311456",
"source_reference": "P442501",
"urls": [
{
"is_active": true,
"store_url": "https://www.sephora.ca",
"storefront_id": "c3d4e5f6789012345abcdef012",
"url": "https://www.sephora.ca/product/P442501"
}
]
}{
"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
Unique identifier of the retailer this listing belongs to.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Unique identifier of the listing to update.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Body
Fields a PATCH may change on an existing listing.
Every field is optional; only the ones present in the request body are
written. badges and urls are deliberately absent — see this
module's docstring.
The retailer's internal SKU for this product.
100Set false to drop the product from the catalog (the same effect as DELETE), true to re-list it.
Retailer-scoped facts with no typed column yet. Replaces the stored dict rather than merging into it, so a key can be removed. Bounded: at most 200 keys and 8000 characters per value — the same bounds the import boundary applies, because the column has no database-level shape and this would otherwise be the one path that can turn a staging area into a document store.
Show child attributes
Show child attributes
The retailer's own product identifier in the source feed.
255Response
OK
One retailer product listing as the catalog holds it.
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}$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}$Identifier of the shared product this listing points at.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$GTIN of the listed product. The key the listing upsert accepts, echoed here so a caller can reconcile this row against its own feed without a second lookup.
The retailer's own SKU for this product across its whole catalog. Distinct from an offer's SKU, which is the SKU at one specific store or storefront.
False once the retailer drops the product from its catalog. DELETE clears this flag rather than removing the row.
When this listing row was first written.
When this listing row last changed.
Retailer-scoped facts that have no typed column yet — a staging area, not a permanent home. A key that proves useful is promoted to a real column.
The retailer's own product identifier in the source feed (e.g. a Sephora productId).
Designations this retailer applies to this product. Change them through POST /retailers/{retailer_id}/listings, which syncs the whole set.
Show child attributes
Show child attributes
One product URL per retailer web property. Change them through POST /retailers/{retailer_id}/listings, which syncs the whole set.
Show child attributes
Show child attributes