Skip to main content
POST
Create resolution rule

Authorizations

X-API-Key
string
header
required

Path Parameters

organization_id
string<shortuuid>
required

UUID of the organization that will own the new rule. Caller must have CHANGE permission.

Required string length: 22
Pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$

Body

application/json

Payload for POST /organizations/{organization_id}/resolution-rules.

The organization is taken from the URL path, not the body. product_id is the only foreign-key handle accepted; everything else is a primitive or one of the resolver enums.

scope_type
enum<string>
required

Hierarchy level at which this rule applies. Mirrors ScopeType. Specificity ascends ORGANIZATION → BRAND → PRODUCT → BATCH → SERIAL — the evaluator walks scopes most-specific first, so a rule scoped to a single serial beats a rule scoped to the whole brand.

Available options:
ORGANIZATION,
BRAND,
PRODUCT,
BATCH,
SERIAL
order_index
integer
required

Evaluation order within the rule's (scope_type, scope_id) bucket. Lower values are evaluated first; the first matching rule wins. Unique per (scope_type, scope_id, order_index) — duplicates are rejected by a per-scope unique index in the database.

destination_type
enum<string>
required

Where to send the resolution. HOSTED_PAGE renders the Closient product page; CUSTOM_URL 302s to custom_url.

Available options:
HOSTED_PAGE,
CUSTOM_URL
metadata
Metadata · object | null

Developer-attached key/value data. Send {} or null to clear. Empty-string values delete that key. Omitted keys are preserved.

scope_id
string | null

Identifier of the scoped entity. Required when scope_type != 'ORGANIZATION' (organization-wide rules use the implicit organization from the URL). Which entity it names depends on scope_type: the brand for BRAND, the product for PRODUCT, the lot/serial for BATCH/SERIAL, the group for LOT_GROUP/SERIAL_GROUP. Accepted in either the 22-character short id or the canonical 36-character UUID form; responses always return the short id. The entity must exist and belong to the calling organization -- an id that names nothing is rejected with a 422 rather than stored. Internally the scope is a typed foreign key, not this string; scope_id remains the request/response field so consumers are not asked to migrate for a storage change.

Maximum string length: 255
Pattern: ^[^\x00]{0,255}$
enabled
boolean
default:true

Whether the rule is currently active. Disabled rules are skipped by the evaluator but kept in the listing for easy re-enable.

product_id
string<shortuuid> | null

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

Required string length: 22
Pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
absolute_start_at
string<date-time> | null

Inclusive lower bound of the rule's active window — the rule matches when now() >= absolute_start_at. null means no lower bound. Compared in the rule's timezone.

absolute_end_at
string<date-time> | null

Exclusive upper bound of the rule's active window — the rule matches when now() < absolute_end_at. null means no upper bound. Compared in the rule's timezone.

annual_start_mmdd
string | null

Annually-recurring start date in MM-DD format (e.g. 11-15). When annual_start_mmdd is later in the year than annual_end_mmdd the window wraps year-end (e.g. 11-15 → 01-05 is mid-November to early January). null means no annual constraint.

Maximum string length: 5
Pattern: ^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
annual_end_mmdd
string | null

Annually-recurring end date in MM-DD format (e.g. 01-05). See annual_start_mmdd for wrap-around semantics. null means no annual constraint. Both ends should be set together; setting only one is accepted by the API but ignored by the evaluator.

Maximum string length: 5
Pattern: ^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
days_of_week
enum<integer>[] | null

Days the rule may match, using Python's datetime.weekday() convention: 0 = Monday, 6 = Sunday. Duplicates are deduplicated and the result is sorted ascending. null (or empty list) means every day matches. Evaluated in the rule's timezone.

Day-of-week value for ResolutionRule.days_of_week.

Uses the Python datetime.weekday() convention: Monday=0, Sunday=6. Exposed as a typed enum so the OpenAPI spec advertises the full valid range (enum: [0..6]) instead of the previous "any int".

Available options:
0,
1,
2,
3,
4,
5,
6
time_of_day_start
string<time> | null

Inclusive lower bound on time-of-day, HH:MM 24-hour. Rule matches when current_time >= time_of_day_start in the rule's timezone. When start is later than end the window wraps midnight (e.g. 22:00 → 02:00 is the late-night window). null means no lower bound.

time_of_day_end
string<time> | null

Exclusive upper bound on time-of-day, HH:MM 24-hour. Rule matches when current_time < time_of_day_end in the rule's timezone. See time_of_day_start for wrap-around semantics. null means no upper bound.

timezone
string | null

IANA timezone name used to evaluate every other time-criteria field (annual_*, days_of_week, time_of_day_*, absolute_*). E.g. America/New_York, Europe/Berlin, UTC. Falls back to the organization's OrganizationResolverSettings.routing_timezone when null.

Maximum string length: 50
Pattern: ^[^\x00]{0,50}$

GS1 Web Vocabulary CURIE that the rule applies to (e.g. gs1:pip for Product Information Page, gs1:hasRecallInformation for recall redirects). When present the rule only matches resolutions for that link type; null matches all link types. See the GS1 Web Vocabulary for the full set of CURIEs.

Maximum string length: 100
Pattern: ^[^\x00]{0,100}$
location_countries
string[] | null

Allow list of ISO 3166-1 alpha-2 country codes the rule applies to. Matched against the IP-derived country of the scan request. null or empty list = any country. Case-insensitive.

Pattern: ^[^\x00]{0,}$
location_regions
string[] | null

Allow list of subdivision (state / province / region) names the rule applies to. Matched against the IP-derived region name of the scan request. null or empty list = any region. Case-insensitive.

Pattern: ^[^\x00]{0,}$
location_cities
string[] | null

Allow list of city names the rule applies to. Matched against the IP-derived city name of the scan request. null or empty list = any city. Case-insensitive.

Pattern: ^[^\x00]{0,}$
custom_url
string | null

External URL to redirect to. Required when destination_type == 'CUSTOM_URL' (enforced both at the schema level and by a database CHECK constraint). Maximum 2048 characters. On write the URL is stored as a reusable custom-URL catalog entry for the organization (deduped by URL); on read it reflects that catalog entry. Health is monitored out-of-band; see on_custom_url_dead for failure behavior.

Maximum string length: 2048
Pattern: ^[^\x00]{0,}$
on_custom_url_dead
enum<string>
default:SKIP_RULE

Behavior when the URL-health monitor has marked custom_url dead. SKIP_RULE falls through to the next matching rule; FALLBACK_TO_HOSTED short-circuits to the Closient-hosted product page. Live URLs are followed regardless of this setting.

Available options:
SKIP_RULE,
FALLBACK_TO_HOSTED
section_anchor
enum<string>

Optional section anchor that deep-links a hosted-page scan to a specific kebab-case section of the product page (e.g. where-to-buy, ingredients, dscsa-recall-status), or to one specific brand CTA link within the brand-links section, addressed by its bare camelCase GS1 link-type short name (e.g. review, leaveReview, promotion). When set on a HOSTED_PAGE rule the resolver appends #anchor to the redirect URL so the browser scrolls directly to that section or link. Must be null whenever destination_type != 'HOSTED_PAGE' — for CUSTOM_URL rules, embed any anchor directly in custom_url. The frontend resolver-config picker is responsible for enumerating the anchors available on the resolved product; canonical section anchors and per-link anchors are the section and link-type short names of the product.

Available options:
accessories,
activityIdeas,
alerts,
apps,
backgroundInfo,
brand-links,
brandHomepageClinical,
brandHomepagePatient,
careersInfo,
certifications,
claims,
consumables,
date-countdown,
dscsa-drug-id,
dscsa-recall-status,
dscsa-safety,
dscsa-traceability,
epcis,
epil,
eventsInfo,
expiry-notice,
faq,
firmware,
gallery,
homepage,
ingredients,
instructions,
itip-piece,
jws,
leaveReview,
locationInfo,
logisticsInfo,
loyaltyProgram,
manual,
masterData,
menuInfo,
nutrition,
openingHoursInfo,
parts,
paymentLink,
product-info,
production-location,
promotion,
quick-facts,
recipes,
recycling,
register-product,
registryEntry,
reportFound,
retailer-badges,
review,
safety-notices,
scheduleTime,
sensitivities,
smpc,
socials,
statisticInfo,
subscribe,
traceability,
unverified-unit,
userAgreement,
verificationService,
warranty,
whatsInTheBox,
where-to-buy

Response

Created

A resolution rule as read from the API.

Returned by GET /resolution-rules/{id} and the list/reorder endpoints. Mirrors ResolutionRule.

scope_id is the canonical text form of the rule's scope (a product, brand or lot identifier). Its format is stable, so consumers are not made to migrate when the way scopes are stored changes.

id
string<shortuuid>
required

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

Required string length: 22
Pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
scope_type
enum<string>
required

Hierarchy level at which this rule applies. Mirrors ScopeType. Specificity ascends ORGANIZATION → BRAND → PRODUCT → BATCH → SERIAL — the evaluator walks scopes most-specific first, so a rule scoped to a single serial beats a rule scoped to the whole brand.

Available options:
ORGANIZATION,
BRAND,
PRODUCT,
BATCH,
SERIAL
order_index
integer
required

Evaluation order within the rule's (scope_type, scope_id) bucket. Lower values are evaluated first; the first matching rule wins. Unique per (scope_type, scope_id, order_index) — duplicates are rejected by a per-scope unique index in the database.

enabled
boolean
required

Whether the rule is currently active. Disabled rules are skipped by the evaluator but kept in the listing for easy re-enable.

destination_type
enum<string>
required

Where to send the resolution. HOSTED_PAGE renders the Closient product page; CUSTOM_URL 302s to custom_url.

Available options:
HOSTED_PAGE,
CUSTOM_URL
on_custom_url_dead
enum<string>
required

Behavior when the URL-health monitor has marked custom_url dead. SKIP_RULE falls through to the next matching rule; FALLBACK_TO_HOSTED short-circuits to the Closient-hosted product page. Live URLs are followed regardless of this setting.

Available options:
SKIP_RULE,
FALLBACK_TO_HOSTED
created
string<date-time>
required

Server-side ISO 8601 timestamp of when the rule was created (UTC).

modified
string<date-time>
required

Server-side ISO 8601 timestamp of the rule's last modification (UTC).

metadata
Metadata · object

Developer-attached key/value data attached to this object. Up to 50 keys; key max 40 chars, value max 500 chars.

system
boolean
default:false

Whether this is the implicit, system-managed default rule rather than a user-created one. Always false for rules returned by the list/CRUD endpoints — those are all user-managed DB rows. The default rule (the baseline hosted-page fallback applied when no other rule matches) is served separately by GET .../resolution-rules/default with system set to true; it cannot be created, edited, deleted, or reordered.

scope_id
string | null

Identifier of the scoped entity. Required when scope_type != 'ORGANIZATION' (organization-wide rules use the implicit organization from the URL). Which entity it names depends on scope_type: the brand for BRAND, the product for PRODUCT, the lot/serial for BATCH/SERIAL, the group for LOT_GROUP/SERIAL_GROUP. Accepted in either the 22-character short id or the canonical 36-character UUID form; responses always return the short id. The entity must exist and belong to the calling organization -- an id that names nothing is rejected with a 422 rather than stored. Internally the scope is a typed foreign key, not this string; scope_id remains the request/response field so consumers are not asked to migrate for a storage change.

organization_id
string<shortuuid> | null

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

Required string length: 22
Pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
product_id
string<shortuuid> | null

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

Required string length: 22
Pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
absolute_start_at
string<date-time> | null

Inclusive lower bound of the rule's active window — the rule matches when now() >= absolute_start_at. null means no lower bound. Compared in the rule's timezone.

absolute_end_at
string<date-time> | null

Exclusive upper bound of the rule's active window — the rule matches when now() < absolute_end_at. null means no upper bound. Compared in the rule's timezone.

annual_start_mmdd
string | null

Annually-recurring start date in MM-DD format (e.g. 11-15). When annual_start_mmdd is later in the year than annual_end_mmdd the window wraps year-end (e.g. 11-15 → 01-05 is mid-November to early January). null means no annual constraint.

Maximum string length: 5
Pattern: ^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
annual_end_mmdd
string | null

Annually-recurring end date in MM-DD format (e.g. 01-05). See annual_start_mmdd for wrap-around semantics. null means no annual constraint. Both ends should be set together; setting only one is accepted by the API but ignored by the evaluator.

Maximum string length: 5
Pattern: ^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
days_of_week
enum<integer>[] | null

Days the rule may match, using Python's datetime.weekday() convention: 0 = Monday, 6 = Sunday. Duplicates are deduplicated and the result is sorted ascending. null (or empty list) means every day matches. Evaluated in the rule's timezone.

Day-of-week value for ResolutionRule.days_of_week.

Uses the Python datetime.weekday() convention: Monday=0, Sunday=6. Exposed as a typed enum so the OpenAPI spec advertises the full valid range (enum: [0..6]) instead of the previous "any int".

Available options:
0,
1,
2,
3,
4,
5,
6
time_of_day_start
string<time> | null

Inclusive lower bound on time-of-day, HH:MM 24-hour. Rule matches when current_time >= time_of_day_start in the rule's timezone. When start is later than end the window wraps midnight (e.g. 22:00 → 02:00 is the late-night window). null means no lower bound.

time_of_day_end
string<time> | null

Exclusive upper bound on time-of-day, HH:MM 24-hour. Rule matches when current_time < time_of_day_end in the rule's timezone. See time_of_day_start for wrap-around semantics. null means no upper bound.

timezone
string | null

IANA timezone name used to evaluate every other time-criteria field (annual_*, days_of_week, time_of_day_*, absolute_*). E.g. America/New_York, Europe/Berlin, UTC. Falls back to the organization's OrganizationResolverSettings.routing_timezone when null.

Maximum string length: 50

GS1 Web Vocabulary CURIE that the rule applies to (e.g. gs1:pip for Product Information Page, gs1:hasRecallInformation for recall redirects). When present the rule only matches resolutions for that link type; null matches all link types. See the GS1 Web Vocabulary for the full set of CURIEs.

Maximum string length: 100
location_countries
string[] | null

Allow list of ISO 3166-1 alpha-2 country codes the rule applies to. Matched against the IP-derived country of the scan request. null or empty list = any country. Case-insensitive.

location_regions
string[] | null

Allow list of subdivision (state / province / region) names the rule applies to. Matched against the IP-derived region name of the scan request. null or empty list = any region. Case-insensitive.

location_cities
string[] | null

Allow list of city names the rule applies to. Matched against the IP-derived city name of the scan request. null or empty list = any city. Case-insensitive.

custom_url
string | null

External URL to redirect to. Required when destination_type == 'CUSTOM_URL' (enforced both at the schema level and by a database CHECK constraint). Maximum 2048 characters. On write the URL is stored as a reusable custom-URL catalog entry for the organization (deduped by URL); on read it reflects that catalog entry. Health is monitored out-of-band; see on_custom_url_dead for failure behavior.

Maximum string length: 2048
section_anchor
enum<string>

Optional section anchor that deep-links a hosted-page scan to a specific kebab-case section of the product page (e.g. where-to-buy, ingredients, dscsa-recall-status), or to one specific brand CTA link within the brand-links section, addressed by its bare camelCase GS1 link-type short name (e.g. review, leaveReview, promotion). When set on a HOSTED_PAGE rule the resolver appends #anchor to the redirect URL so the browser scrolls directly to that section or link. Must be null whenever destination_type != 'HOSTED_PAGE' — for CUSTOM_URL rules, embed any anchor directly in custom_url. The frontend resolver-config picker is responsible for enumerating the anchors available on the resolved product; canonical section anchors and per-link anchors are the section and link-type short names of the product.

Available options:
accessories,
activityIdeas,
alerts,
apps,
backgroundInfo,
brand-links,
brandHomepageClinical,
brandHomepagePatient,
careersInfo,
certifications,
claims,
consumables,
date-countdown,
dscsa-drug-id,
dscsa-recall-status,
dscsa-safety,
dscsa-traceability,
epcis,
epil,
eventsInfo,
expiry-notice,
faq,
firmware,
gallery,
homepage,
ingredients,
instructions,
itip-piece,
jws,
leaveReview,
locationInfo,
logisticsInfo,
loyaltyProgram,
manual,
masterData,
menuInfo,
nutrition,
openingHoursInfo,
parts,
paymentLink,
product-info,
production-location,
promotion,
quick-facts,
recipes,
recycling,
register-product,
registryEntry,
reportFound,
retailer-badges,
review,
safety-notices,
scheduleTime,
sensitivities,
smpc,
socials,
statisticInfo,
subscribe,
traceability,
unverified-unit,
userAgreement,
verificationService,
warranty,
whatsInTheBox,
where-to-buy