DOCS

How it works

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.

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:

OperatorMeaning
EQUALSExact match against values[0] (boolean, single-select, or text).
NOT_EQUALSInverse of EQUALS.
INCLUDES_ANYThe prior answer contains at least one entry from values.
INCLUDES_ALLThe prior answer contains every entry in values.
EXCLUDESThe prior answer contains none of values.
ANSWEREDThe referenced question has any non-null answer (no values).
NOT_ANSWEREDThe 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.

effectTargetWhere the answer is written
ITEM_ATTRIBUTEItem.attributes[effectKey] — a named attribute on the item.
ITEM_PRODUCT_COMPOSITIONA field on the item's product-composition entry; effectKey is the field name (e.g. material, percentage, countryOfCast).
nullNothing — 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 a confidence score between 0 and 1.
  • MANUAL - entered by a person, or submitted through the one-shot questionnaireResponseSubmit. confidence is null.

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.

Use confidence to decide how much review each inferred answer needs — surface low-confidence answers for a human to confirm, and fast-path high-confidence ones.

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

ValueFor
PERSONALPersonal consumption, gifts, or household use (not resale).
CONSUMERGeneral consumer products sold through retail or commercial channels.
COMMERCIALBusiness, manufacturing, or industrial purposes (not direct consumer sale).
RESEARCHLaboratory, 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 an ITEM, pass its catalogItemId so 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.

Book a demo

Was this page helpful?