curl --request PATCH \
--url https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"expiry_behavior": "REDIRECT",
"expiry_redirect_url": "https://acmesnacks.com/reorder"
}
'import requests
url = "https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior"
payload = {
"expiry_behavior": "REDIRECT",
"expiry_redirect_url": "https://acmesnacks.com/reorder"
}
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({
expiry_behavior: 'REDIRECT',
expiry_redirect_url: 'https://acmesnacks.com/reorder'
})
};
fetch('https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior', 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/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior",
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([
'expiry_behavior' => 'REDIRECT',
'expiry_redirect_url' => 'https://acmesnacks.com/reorder'
]),
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/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior"
payload := strings.NewReader("{\n \"expiry_behavior\": \"REDIRECT\",\n \"expiry_redirect_url\": \"https://acmesnacks.com/reorder\"\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/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"expiry_behavior\": \"REDIRECT\",\n \"expiry_redirect_url\": \"https://acmesnacks.com/reorder\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior")
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 \"expiry_behavior\": \"REDIRECT\",\n \"expiry_redirect_url\": \"https://acmesnacks.com/reorder\"\n}"
response = http.request(request)
puts response.read_body{
"age_restricted": false,
"enhanced_resolution_enabled": true,
"expiry_behavior": "REDIRECT",
"expiry_redirect_url": "https://acmesnacks.com/reorder",
"gtin": "00012345678905",
"product_name": "Acme Trail Mix 12oz",
"resolver_enabled": true,
"resolver_enabled_at": "2026-06-01T09:00:00Z",
"serial_policy": "PERMISSIVE",
"shelf_life_offset_ai": "15",
"shelf_life_offset_days": 180
}{
"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 product resolver behavior
Change any of the product’s resolver behavior controls; fields left out keep their values and the update applies all or nothing. Requires an OWNER, MANAGER or EDITOR (or an EDITOR+ API key). Turning enhanced resolution on beyond the plan’s allowance, or on a lapsed account, is 403 with an upgrade_url; a refused value is 422.
curl --request PATCH \
--url https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"expiry_behavior": "REDIRECT",
"expiry_redirect_url": "https://acmesnacks.com/reorder"
}
'import requests
url = "https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior"
payload = {
"expiry_behavior": "REDIRECT",
"expiry_redirect_url": "https://acmesnacks.com/reorder"
}
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({
expiry_behavior: 'REDIRECT',
expiry_redirect_url: 'https://acmesnacks.com/reorder'
})
};
fetch('https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior', 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/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior",
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([
'expiry_behavior' => 'REDIRECT',
'expiry_redirect_url' => 'https://acmesnacks.com/reorder'
]),
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/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior"
payload := strings.NewReader("{\n \"expiry_behavior\": \"REDIRECT\",\n \"expiry_redirect_url\": \"https://acmesnacks.com/reorder\"\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/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"expiry_behavior\": \"REDIRECT\",\n \"expiry_redirect_url\": \"https://acmesnacks.com/reorder\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/resolver/api/v1/organizations/{organization_id}/products/{gtin}/resolver-behavior")
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 \"expiry_behavior\": \"REDIRECT\",\n \"expiry_redirect_url\": \"https://acmesnacks.com/reorder\"\n}"
response = http.request(request)
puts response.read_body{
"age_restricted": false,
"enhanced_resolution_enabled": true,
"expiry_behavior": "REDIRECT",
"expiry_redirect_url": "https://acmesnacks.com/reorder",
"gtin": "00012345678905",
"product_name": "Acme Trail Mix 12oz",
"resolver_enabled": true,
"resolver_enabled_at": "2026-06-01T09:00:00Z",
"serial_policy": "PERMISSIVE",
"shelf_life_offset_ai": "15",
"shelf_life_offset_days": 180
}{
"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
ID of the organization. Caller must be an editor.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$GTIN-8, GTIN-12, GTIN-13, or GTIN-14 digits (no separators) with a valid GS1 check digit. Normalized to GTIN-14 for lookup; the product must belong to one of the organization's brands.
8 - 14Body
A partial update to a product's resolver behavior.
Send only the fields to change; a field left out keeps its stored value. The whole update is applied or none of it is.
Turn the paid enhanced tier on or off. Turning it on counts against the plan's enhanced allowance (403 with upgrade_url beyond it) and needs the resolver to be on (422).
NONE, NOTICE or REDIRECT. REDIRECT needs expiry_redirect_url.
NONE, NOTICE, REDIRECT Absolute http(s) URL for expired scans under REDIRECT.
2048PERMISSIVE or STRICT.
PERMISSIVE, STRICT Days from manufacture to the printed date (0-3650).
0 <= x <= 365015 (best-before) or 17 (use-by). Needs shelf_life_offset_days in the same request unless the product already has an offset.
15, 17 Turn the 18+ age gate on or off.
Response
OK
A product's resolver state and its per-GTIN resolver behavior.
Normalized GTIN-14 (zero-padded).
Product name, for display.
Whether the product is enrolled in the resolver (its billable unit). While false, its lots and serials are not billable either, whatever their own flags say, and enhanced resolution cannot be on. Scans of the product resolve either way.
Whether the product is on the paid enhanced tier, which unlocks expiry behavior and the strict serial policy. Requires the resolver to be on.
What a scan carrying a past use-by (AI 17) or best-before (AI 15) date gets: NONE forwards it unchanged, NOTICE shows a past-date notice, REDIRECT sends it to expiry_redirect_url. Only acted on while enhanced resolution is on.
NONE, NOTICE, REDIRECT Destination for an expired scan when expiry_behavior is REDIRECT; empty otherwise.
PERMISSIVE falls back to the deepest available page for an unknown serial; STRICT shows an unverified-unit page. Only acted on while enhanced resolution is on.
PERMISSIVE, STRICT Whether scans of this product pass an 18+ age gate first.
When the resolver was last turned on for this product; null if never.
Days from manufacture to the printed date; null when not configured.
Which printed date the offset produces: 15 best-before or 17 use-by; null when not set.
15, 17