Option 1: Infer and prefill (recommended)
complianceQuestionnaireInfer resolves the applicable programs for your items,
opens a session, and pre-answers questions from inferred facts. Each inferred
answer has provenance: INFERRED and a confidence score. Lines are not
auto-finalized — fully-answered lines wait for you to attest them.
Pass intendedUse to backstop inference; storefront flows should send
CONSUMER.
mutation InferQuestionnaire($input: ComplianceQuestionnaireInferInput!) { complianceQuestionnaireInfer(input: $input) { session { id status lines { id programId status currentQuestion { code prompt answerType } visibleQuestions { question { code prompt answerType required } answer { value provenance confidence } } } } lines { lineId programId targetId state inferredCount remainingRequiredCount } alreadySatisfied { programId targetId } }}Each line's state tells you what to do next:
READY_TO_ATTEST- every required, visible question is answered. Attest it.PARTIAL- some answers were inferred but required questions remain. Resume the wizard atcurrentQuestion.NOT_INFERRED- nothing matched; answer it as a plain questionnaire.
Programs already satisfied by a current response are returned in
alreadySatisfied and skipped (no line is created for them).
Review and correct inferred answers
After inference, the session holds pre-filled answers — not a finalized response. The end user (for example, a FedEx shipper) reviews them, and for each answer does one of two things:
- Agrees - leaves the answer untouched. It stays
INFERRED. - Disagrees - submits a corrected value with
questionnaireAnswersSubmitto the same session line. The correction supersedes the inferred answer, which becomesMANUAL.
They only touch the answers they want to change — there's no separate "manual mode," and no need to re-enter the answers they accepted.
Suppose a session came back with two inferred answers — intended_use (high
confidence) and contains_color_additives (low confidence). The shipper accepts
intended_use but corrects contains_color_additives, so they submit only that
one answer:
mutation CorrectAnswer($input: QuestionnaireAnswersSubmitInput!) { questionnaireAnswersSubmit(input: $input) { lines { id status responseId answers { questionCode value provenance confidence } } }}The accepted intended_use answer stays INFERRED; the corrected
contains_color_additives is now MANUAL. Both land on the finalized response.
If the shipper had agreed with everything, they would skip the corrections and
use Attest inferred answers (below) instead. The full submission mechanics
are covered under Submit answers.
Option 2: Start a session manually
If you would rather drive answering yourself, start a session directly with the
programs you discovered. questionnaireSessionStart fans out one line per
(item, program) and returns the first question for each line.
mutation StartSession($input: QuestionnaireSessionStartInput!) { questionnaireSessionStart(input: $input) { id status lines { id programId targetType targetId status currentQuestion { code prompt answerType required } } }}Submit answers
Whether the session came from inference or questionnaireSessionStart, submit
answers to a line with questionnaireAnswersSubmit. You can send one answer or
several at once. The server evaluates display conditions, advances
currentQuestion to the next required, visible, unanswered question, and — when
none remain — completes the line and finalizes its response (responseId is
populated). The session completes once every line is COMPLETED.
Editing an inferred answer simply supersedes it with a MANUAL one.
mutation SubmitAnswers($input: QuestionnaireAnswersSubmitInput!) { questionnaireAnswersSubmit(input: $input) { id status lines { id status responseId currentQuestion { code prompt } answers { questionCode value provenance confidence } } }}Attest inferred answers
When a line came back READY_TO_ATTEST from inference, you don't need to resubmit
the answers — accept them in one call with questionnaireSessionAttest. It
finalizes every eligible line (or only the lineIds you pass) and rolls the
response provenance up to attested.
mutation AttestSession($input: QuestionnaireSessionAttestInput!) { questionnaireSessionAttest(input: $input) { id status lines { id status responseId } }}To abandon an in-progress session instead, call
questionnaireSessionAbandon(id: ID!).
Option 3: Submit a complete response in one shot
If you already have every answer — for example, a durable answer set for a
CATALOG_ITEM — skip the session entirely and submit directly with
questionnaireResponseSubmit. It validates the answers against the program's
ACTIVE questionnaire (no answer for a hidden question; every required visible
question answered) and persists a COMPLETE response, superseding any prior
complete response for the same (program, target, route).
mutation SubmitResponse($input: QuestionnaireResponseSubmitInput!) { questionnaireResponseSubmit(input: $input) { id status targetType targetId completedAt answeredBy answers { questionCode value provenance } }}Read sessions, responses, and readiness
Fetch an in-progress session with questionnaireSession(id), a finalized record
with questionnaireResponse(id), and an all-or-nothing rollup for a whole
container with containerComplianceReadiness.
query ContainerReadiness($containerType: ContainerType!, $containerId: ID!) { containerComplianceReadiness( containerType: $containerType containerId: $containerId ) { ready totalLineCount outstandingLineCount }}ready is true only when the container has at least one line and every line
has a COMPLETE response — a convenient gate before you file the entry.
To read a finalized response and its answers:
query QuestionnaireResponse($id: ID!) { questionnaireResponse(id: $id) { id status targetType targetId shipToCountry completedAt answeredBy answers { questionCode value provenance confidence answeredAt expiresAt } }}Next steps
- How it works - branching, effect keys and materialization, inference and provenance, and dynamic options.
- Check compliance requirements - discover programs and read questionnaire structures.
Complete a questionnaire
Answer and finalize compliance questionnaires through the Zonos API — with AI prefill, a guided wizard, or a single one-shot submission.
GraphQL
Once you know which programs apply (see Check compliance requirements), Clarify gives you three ways to answer them:
All of these write a response: an immutable record whose effect-keyed answers materialize onto the item for customs filing.
Reading sessions and responses requires the
COMPLIANCE_READscope; submitting answers requiresCOMPLIANCE_WRITE. See OAuth.Every answer
valueis a JSON-encoded string. A boolean is"true", a select or text value is a quoted string such as"\"cosmetic\"", a multi-select is"[\"a\",\"b\"]", and a number is"42". Encode accordingly when you submit, and decode when you read.