Branching with display conditions
Compliance questionnaires are rarely flat. A question's displayCondition
controls whether it is shown, based on the answers to earlier questions — so
importers only see what's relevant.
A DisplayCondition node is either a composite (exactly one of allOf,
anyOf, or not) or a leaf (questionCode + operator, with values when
the operator compares values). Composites nest, so you can express arbitrary
boolean logic.
{
"allOf": [
{
"questionCode": "intended_use",
"operator": "EQUALS",
"values": ["cosmetic"]
},
{
"questionCode": "contains_color_additives",
"operator": "EQUALS",
"values": ["true"]
}
]
}
The leaf operator values:
| Operator↕ | Meaning↕ |
|---|---|
EQUALS | Exact match against values[0] (boolean, single-select, or text). |
NOT_EQUALS | Inverse of EQUALS. |
INCLUDES_ANY | The prior answer contains at least one entry from values. |
INCLUDES_ALL | The prior answer contains every entry in values. |
EXCLUDES | The prior answer contains none of values. |
ANSWERED | The referenced question has any non-null answer (no values). |
NOT_ANSWERED | The referenced question has no answer yet (no values). |
You can evaluate display conditions yourself, but you usually don't have to. In a
session, each line's visibleQuestions already has conditions evaluated
server-side against that line's current answers — it is the render-ready list.
The line's questions field instead returns the full tree (conditions not
evaluated) so a client can evaluate locally with zero extra round-trips.
Effect keys and materialization
A question can do more than collect information — it can write its answer onto the
item when the questionnaire is finalized. That's what effectTarget and
effectKey describe.
effectTarget↕ | Where the answer is written↕ |
|---|---|
ITEM_ATTRIBUTE | Item.attributes[effectKey] — a named attribute on the item. |
ITEM_PRODUCT_COMPOSITION | A field on the item's product-composition entry; effectKey is the field name (e.g. material, percentage, countryOfCast). |
null | Nothing — the question is purely informational or used only for branching. |
When a response is finalized, Clarify materializes each effect-keyed answer onto
the target so it carries through to the customs entry. Questions with a null
effectTarget still shape the flow (and the record) but write nothing.
Inference, confidence, and provenance
complianceQuestionnaireInfer uses Zonos AI to pre-answer questions from item
data (name, description, HS code, intended use). Every answer records how it
originated:
INFERRED- pre-filled by inference and not yet edited. Carries aconfidencescore between0and1.MANUAL- entered by a person, or submitted through the one-shotquestionnaireResponseSubmit.confidenceisnull.
Editing an inferred answer supersedes it with a MANUAL answer. A finalized
response is never left as plain inference: when you accept inferred answers
with questionnaireSessionAttest, the response is recorded as attested — an
explicit acceptance, not a guess.
Dynamic options
Most questions carry their choices inline in options. Some are provider-backed:
their choices depend on the item and on earlier answers. These questions set
optionsSource (for example FDA_PCB) and leave options empty.
For provider-backed questions, a session line delivers a dependentOptions table
up-front — one DependentOptionSet per distinct optionsSource, covering every
reachable combination scoped to the line's item (its HS code). You resolve the
visible choices locally, with no extra server calls.
{
"questionCode": "fda_product_code_builder",
"dependsOn": ["intended_use"],
"entries": [
{
"parentValues": [{ "questionCode": "intended_use", "value": "cosmetic" }],
"options": [
{ "value": "53", "label": "Cosmetics" },
{ "value": "53A", "label": "Skin care preparations" }
]
}
]
}
To use it: find the DependentOptionEntry whose parentValues match the current
answers to the dependsOn questions, then present that entry's options (each a
value / label pair). A root question — one whose choices don't depend on any
other answer — has an empty dependsOn and a single entry with empty
parentValues.
Intended use
Inference works best when it knows the declared import intent. Pass
intendedUse on each item to backstop intended-use inference: when the model
can't determine it from the product alone, the declared value is used with full
confidence (it's the importer's declaration, not a guess).
| Value↕ | For↕ |
|---|---|
PERSONAL | Personal consumption, gifts, or household use (not resale). |
CONSUMER | General consumer products sold through retail or commercial channels. |
COMMERCIAL | Business, manufacturing, or industrial purposes (not direct consumer sale). |
RESEARCH | Laboratory, testing, scientific study, or clinical trials. |
Storefront and consumer-checkout flows should send CONSUMER.
Targets and containers
Target is what a set of answers describes:
CATALOG_ITEM- durable product master data. Answer once and reuse it; effect-keyed answers materialize onto the catalog item.ITEM- a per-shipment item. Answers are stored and mapped onto the entry at filing time. When answering for anITEM, pass itscatalogItemIdso a complete catalog-level response can satisfy the program too.
Container is the grouping the answers are collected under — DECLARATION,
LANDED_COST, CONSIGNMENT, or CONSOLIDATION. Containers are grouping-only:
they don't hold answers themselves, but containerComplianceReadiness rolls up
the status of all their lines so you can gate filing on a single check.
How Clarify works
The concepts behind questionnaire branching, answer materialization, AI inference, and dynamic options.
This page explains the mechanics that the discovery and answering APIs are built on. You don't need all of it to integrate, but it's useful when you render your own questionnaire UI or debug why a question appears.