questionnaire_apply
See the current correction and older-server guidance before preparing new inputs; keep existing task inputs unchanged during recovery.
This implementation is a dependency of the documentation branch.
Create or revise a full questionnaire through one composed workflow, including ordered questions/options and optional original images. Use visibility to activate or deactivate existing records.
Access and input
Requires OWNER or DELEGATEE with MANAGE_MARKETING_TOOLS on the selected channel. The source has no questionnaire plan or connected-domain gate. Hosted selected-channel resources admit these tools only for the exact channelCode; account resources exclude them.
channelCode and questionnaire/answer IDs are strings of 1–512 characters, excluding literal dot/double dot, slash, backslash, ASCII controls and DEL. Obtain them from authorized discovery/results; numeric row IDs, URLs and credentials are not substitutes. Strict input objects reject unknown properties.
| Input | Contract |
|---|---|
| channelCode | Required selected channel code. |
| requestId | Required 8–80 ASCII letters, digits, underscores or hyphens; preserve for recovery. |
| action | create or revise. |
| qrid | Required for revise; forbidden for create. |
| expectedRevision | Required 64 lowercase hexadecimal characters from creationRevision (create) or detail revision (revise). |
| settings | Required nonempty strict object described below. |
| dryRun | Optional boolean, defaults to false. true checks/prepares without business effects or task JSON. A local empty lock directory may be created. |
Action combinations and nonempty settings are runtime refinements beyond the flat JSON Schema. The all-zero revisions and illustrative IDs below must be replaced with current authorized reader values. All examples are dry runs.
{
"channelCode": "selected-channel-code",
"requestId": "questionnaire-create-01",
"action": "create",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"settings": {
"title": "Feedback",
"titleDisplay": "Share your feedback",
"description": "Tell us about your experience.",
"isGuestAnswerAllowed": false,
"isDuplicateAnswerAllowed": false,
"backgroundColorInHex": "#f5f5f8",
"questions": [
{
"title": "Your topic",
"type": "SHORT_ANSWER",
"isEssential": true,
"hintText": "A short summary"
},
{
"title": "Your experience",
"type": "LONG_ANSWER",
"isEssential": false
},
{
"title": "Choose a category",
"type": "RADIO",
"isEssential": true,
"options": [
{
"label": "Product"
},
{
"label": "Support"
}
]
},
{
"title": "Topics of interest",
"type": "CHECKBOX",
"isEssential": false,
"options": [
{
"label": "Updates"
}
]
},
{
"title": "Preferred contact",
"type": "DROPDOWN",
"isEssential": false,
"options": [
{
"label": "Email"
},
{
"label": "None"
}
]
}
]
},
"dryRun": true
}
Settings and ordered replacement
| Field | Contract |
|---|---|
| settings.title | Trimmed 1–50 characters; required for creation, otherwise optional. |
| settings.titleDisplay | Optional trimmed string up to 200 characters; empty is allowed. |
| settings.description | Optional string up to 2,000 characters; empty clears it. |
| settings.isGuestAnswerAllowed | Optional boolean; the composed result requires duplicate answers when true. |
| settings.isDuplicateAnswerAllowed | Optional boolean; must be true when guests are enabled. |
| settings.backgroundColorInHex | Optional #RGB, #RGBA, #RRGGBB or #RRGGBBAA, case-insensitive. |
| settings.questions | Optional ordered array of 0–100 questions; supplying it replaces the entire list. |
| settings.coverImage | Optional original image object, null to remove, or omission to preserve. |
| settings.coverImage.filename | PNG/JPEG filename, 1–240 characters; no path separators, colon or controls. |
| settings.coverImage.base64 | Canonical base64 of original PNG/JPEG bytes; see image limits below. |
Creation defaults titleDisplay to title, description to empty, active state to true, guest/duplicate answers to false, backgroundColorInHex to #f5f5f8, and questions to empty. The console has a localized default description; supply the intended localized text explicitly. This tool does not infer locale. Omitted settings on revision preserve existing values; empty arrays and explicit null have different meanings from omission.
Each settings.questions[] is strict and uses these fields:
| Question field | Contract |
|---|---|
| settings.questions[].qid | Optional existing string identity. |
| settings.questions[].id | Optional existing positive safe-integer identity. |
| settings.questions[].title | Required string, up to 16,384 characters for text types; up to 200 for choices. |
| settings.questions[].description | Optional string, up to 16,384 characters for text types; up to 2,000 for choices. |
| settings.questions[].isEssential | Required boolean. |
| settings.questions[].type | SHORT_ANSWER, LONG_ANSWER, RADIO, CHECKBOX or DROPDOWN. |
| settings.questions[].hintText | Optional string up to 16,384 characters; intended for text questions. The schema also accepts it on choices. |
| settings.questions[].hasSubTextField | Optional boolean for choices; forbidden on text types, true forbidden for DROPDOWN. |
| settings.questions[].options | Required choice array: 2–30 for RADIO/DROPDOWN, 1–30 for CHECKBOX; forbidden on text types. |
| settings.questions[].options[].label | Required trimmed nonempty string up to 16,384 characters; unique after trimming within the question. |
| settings.questions[].options[].existingOrder | Optional positive safe integer identifying an existing option in this retained question; no duplicate references. |
| settings.questions[].options[].image | Optional original image, null removal or omission preservation. |
| settings.questions[].options[].image.filename | Same filename rules as coverImage.filename. |
| settings.questions[].options[].image.base64 | Canonical original PNG/JPEG bytes, with the smaller option-image limit. |
Retain a question with qid and/or id; both must match the same existing question when supplied. Each existing question can be retained once. A question without either identity is new. Existing types cannot be changed in place: omit the old question and add a new one without an old identity. Omitted questions/options are removed by replacement. Question and option order is recomputed from array order starting at 1. existingOrder preserves the selected option's image and unknown business fields while permitting label/reordering changes. Omitted question description/hint and hidden source fields are preserved. A stored null question description or cover reference remains null when omitted; this does not permit null in the writable description input. Explicit image removal still produces the empty source reference. UI helpers question.uuid and option.value are removed from the submitted DTO.
The 100-question and 16,384-character text/label caps are bridge limits for source-unbounded controls. Runtime composition rejects unknown/mismatched/duplicated retained identities and invalid guest/duplicate combinations. A supplied questions array is the complete desired list, not a patch.
{
"channelCode": "selected-channel-code",
"requestId": "questionnaire-revise-01",
"action": "revise",
"qrid": "selected-questionnaire-id",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"settings": {
"title": "Updated feedback",
"coverImage": null,
"questions": [
{
"qid": "selected-question-id",
"title": "Choose a category",
"type": "RADIO",
"isEssential": true,
"options": [
{
"label": "Support",
"existingOrder": 2
},
{
"label": "Product",
"existingOrder": 1,
"image": null
}
]
}
]
},
"dryRun": true
}
Images and service prerequisites
Images must contain structurally validated original PNG/JPEG bytes. Filename extensions are case-insensitive at runtime; the JSON-exported regex omits that flag, so use lowercase extensions for portability. No data URL or arbitrary image URL is accepted. Cover bytes must be strictly below 10 MiB, each option image strictly below 2 MiB. All replacement images together must be below 10 MiB per request, a bridge cap. The shared exported image object has the cover base64 bound (13,981,012 characters); option byte limits, canonical base64, structure and aggregate size are enforced at runtime. Split larger changes into separately read/revised operations.
A larger envelope does not raise those tools' individual image limits.
The host configures the normal API origin. Replacing images additionally needs the resource gateway, bucket and region before any effect, including when planning. The workflow obtains channel-scoped grants accepting HTTP200/201 data, validates the configured upload grant, and uploads checked original bytes sequentially. An older uncertain grant is never retried by this correction. Only verified receipt locations enter the full DTO; byte length and checksum must match. Journals retain metadata/receipts, never original bytes, base64 or upload credentials. Null stores an empty image reference; omission preserves it. No automatic orphan deletion or rollback occurs.
{
"channelCode": "selected-channel-code",
"requestId": "questionnaire-image-01",
"action": "revise",
"qrid": "selected-questionnaire-id",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"settings": {
"coverImage": {
"filename": "cover.png",
"base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aDWQAAAAASUVORK5CYII="
}
},
"dryRun": true
}
Inputs
| Field | Required here | Contract |
|---|---|---|
action |
Yes | create, revise |
qrid |
No | string; min length 1; max length 512 |
settings |
Yes | object |
settings.title |
No | string; min length 1; max length 50 |
settings.titleDisplay |
No | string; max length 200 |
settings.description |
No | string; max length 2000 |
settings.isGuestAnswerAllowed |
No | boolean |
settings.isDuplicateAnswerAllowed |
No | boolean |
settings.backgroundColorInHex |
No | string; pattern ^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ |
settings.questions |
No | array; max items 100 |
settings.questions[].qid |
No | string; min length 1; max length 512 |
settings.questions[].id |
No | integer; greater than 0 |
settings.questions[].title |
Yes | string; max length 16384 |
settings.questions[].description |
No | string; max length 16384 |
settings.questions[].isEssential |
Yes | boolean |
settings.questions[].type |
Yes | SHORT_ANSWER, LONG_ANSWER, RADIO, CHECKBOX, DROPDOWN |
settings.questions[].hintText |
No | string; max length 16384 |
settings.questions[].hasSubTextField |
No | boolean |
settings.questions[].options |
No | array; max items 30 |
settings.questions[].options[].label |
Yes | string; min length 1; max length 16384 |
settings.questions[].options[].existingOrder |
No | integer; greater than 0 |
settings.questions[].options[].image |
No | one of: local-file, more-parts, complete, cancel (by kind); Upload the image from this computer: {kind:"local-file", filename, byteLength, sha256} returns one presigned PUT (size, type and SHA-256 signed) and the console link; send the file, then call again with the same input and this field set to {kind:"complete", uploadId, parts:[{partNumber:1, etag}]}. The server never receives the file or downloads a link; download a link to this computer first. or null |
settings.questions[].options[].image.filename |
Yes | When settings.questions[].options[].image.kind is local-file: string; min length 1; max length 240; pattern ^[^/\\:\x00-\x1f\x7f]+$; The name of the file on this computer, with its extension. |
settings.questions[].options[].image.byteLength |
Yes | When settings.questions[].options[].image.kind is local-file: integer; greater than 0; The exact file size in bytes (for example from wc -c < FILE or stat). |
settings.questions[].options[].image.sha256 |
No | When settings.questions[].options[].image.kind is local-file: string; pattern ^[0-9a-f]{64}$; The SHA-256 of the file in lowercase hex (shasum -a 256 FILE). Required when the upload answers UPLOAD_CHECKSUM_REQUIRED. |
settings.questions[].options[].image.uploadId |
Yes | When settings.questions[].options[].image.kind is more-parts: string; pattern ^upl_[A-Za-z0-9_-]{22}$ |
settings.questions[].options[].image.fromPart |
Yes | When settings.questions[].options[].image.kind is more-parts: integer; minimum 1; maximum 10000 |
settings.questions[].options[].image.uploadId |
Yes | When settings.questions[].options[].image.kind is complete: string; pattern ^upl_[A-Za-z0-9_-]{22}$ |
settings.questions[].options[].image.parts |
Yes | When settings.questions[].options[].image.kind is complete: array; min items 1; max items 10000 |
settings.questions[].options[].image.parts[].partNumber |
Yes | When settings.questions[].options[].image.kind is complete: integer; minimum 1; maximum 10000 |
settings.questions[].options[].image.parts[].etag |
Yes | When settings.questions[].options[].image.kind is complete: string; min length 1; max length 200 |
settings.questions[].options[].image.measured |
No | When settings.questions[].options[].image.kind is complete: object; What the video measures on this computer, read with measureCommand. |
settings.questions[].options[].image.measured.durationSeconds |
Yes | When settings.questions[].options[].image.kind is complete: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
settings.questions[].options[].image.measured.frameRate |
Yes | When settings.questions[].options[].image.kind is complete: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
settings.questions[].options[].image.measured.width |
Yes | When settings.questions[].options[].image.kind is complete: integer; maximum 100000; greater than 0 |
settings.questions[].options[].image.measured.height |
Yes | When settings.questions[].options[].image.kind is complete: integer; maximum 100000; greater than 0 |
settings.questions[].options[].image.measured.frameRateMode |
No | When settings.questions[].options[].image.kind is complete: CFR, VFR; Constant or variable frame rate, when the tool reports it. |
settings.questions[].options[].image.measured.minFrameRate |
No | When settings.questions[].options[].image.kind is complete: number; maximum 1000; greater than 0 |
settings.questions[].options[].image.measured.maxFrameRate |
No | When settings.questions[].options[].image.kind is complete: number; maximum 1000; greater than 0 |
settings.questions[].options[].image.uploadId |
Yes | When settings.questions[].options[].image.kind is cancel: string; pattern ^upl_[A-Za-z0-9_-]{22}$ |
settings.coverImage |
No | one of: local-file, more-parts, complete, cancel (by kind) or null |
settings.coverImage.filename |
Yes | When settings.coverImage.kind is local-file: string; min length 1; max length 240; pattern ^[^/\\:\x00-\x1f\x7f]+$; The name of the file on this computer, with its extension. |
settings.coverImage.byteLength |
Yes | When settings.coverImage.kind is local-file: integer; greater than 0; The exact file size in bytes (for example from wc -c < FILE or stat). |
settings.coverImage.sha256 |
No | When settings.coverImage.kind is local-file: string; pattern ^[0-9a-f]{64}$ |
settings.coverImage.uploadId |
Yes | When settings.coverImage.kind is more-parts: string; pattern ^upl_[A-Za-z0-9_-]{22}$ |
settings.coverImage.fromPart |
Yes | When settings.coverImage.kind is more-parts: integer; minimum 1; maximum 10000 |
settings.coverImage.uploadId |
Yes | When settings.coverImage.kind is complete: string; pattern ^upl_[A-Za-z0-9_-]{22}$ |
settings.coverImage.parts |
Yes | When settings.coverImage.kind is complete: array; min items 1; max items 10000 |
settings.coverImage.parts[].partNumber |
Yes | When settings.coverImage.kind is complete: integer; minimum 1; maximum 10000 |
settings.coverImage.parts[].etag |
Yes | When settings.coverImage.kind is complete: string; min length 1; max length 200 |
settings.coverImage.measured |
No | When settings.coverImage.kind is complete: object; What the video measures on this computer, read with measureCommand. |
settings.coverImage.measured.durationSeconds |
Yes | When settings.coverImage.kind is complete: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
settings.coverImage.measured.frameRate |
Yes | When settings.coverImage.kind is complete: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
settings.coverImage.measured.width |
Yes | When settings.coverImage.kind is complete: integer; maximum 100000; greater than 0 |
settings.coverImage.measured.height |
Yes | When settings.coverImage.kind is complete: integer; maximum 100000; greater than 0 |
settings.coverImage.measured.frameRateMode |
No | When settings.coverImage.kind is complete: CFR, VFR |
settings.coverImage.measured.minFrameRate |
No | When settings.coverImage.kind is complete: number; maximum 1000; greater than 0 |
settings.coverImage.measured.maxFrameRate |
No | When settings.coverImage.kind is complete: number; maximum 1000; greater than 0 |
settings.coverImage.uploadId |
Yes | When settings.coverImage.kind is cancel: string; pattern ^upl_[A-Za-z0-9_-]{22}$ |
confirmToken |
No | string; pattern ^ct[a-f\d]{32}$; The confirmToken from this tool's preview result. Omit it to preview; repeat the same input with it to execute. |
Results and full readback
A new dry run returns success / QUESTIONNAIRE_PLAN with noop, changes and plannedStages; writesPerformed is false and verification.confirmed is false. Planning does not reserve a future revision. The workflow compares full content twice, verifies channel/credential continuity, checks the supplied revision, and repeats preservation checks before every effect.
An actual no-op records done without upload/save. Otherwise it sends one full create POST or known-ID revise POST after any uploads; creation accepts HTTP200/201 data, while revision still requires HTTP200 data. Other success statuses are not implicitly accepted. There is no recache. Creation requires returned positive id, qrid and channelId equal to the selected channel. Full readback checks requested fields, retained identities and all untouched business fields, including their supported null representations. Question arrays must have the exact requested length and match after sorting by explicit question order. Ambiguous question order/identity and duplicate option order reject. Option arrays retain their source positions and must match the requested order. New server IDs/defaults may be added. answersCount is excluded from content comparison.
Expected root updatedAt/lastRevisedAt and each question updatedAt are excluded only from cross-save matching because the service regenerates them. They remain in before/after snapshots, content revisions, repeated-read stability, no-op detection and pre-effect conflict checks. Other unknown business fields remain required to match. Expected question arrays are sorted by explicit order; option positions are not normalized. Saved journal snapshots are validated without rewriting or migrating their arrays. This narrow tolerance can reconcile a known-ID sent revision by readback but does not weaken pre-save conflict checks or permit unrelated drift. A mismatch still returns QUESTIONNAIRE_READBACK_MISMATCH and preserves the pending task; never resubmit just to force completion.
All baseline/readback records, the creation receipt, outgoing revision DTO and persisted before/after/created identities must belong to the selected numeric channel. Missing or conflicting identity is rejected, not silently stripped. A foreign answer parent cannot supply a valid revision. Older journals lacking the now-required channel identity are not automatically migrated; preserve them for inspection.
QUESTIONNAIRE_COMPLETE includes taskId, taskType questionnaire, phase done, questionnaires and revisions containing qrid/revision pairs. saveAcknowledged is separate from observed completion: known-ID readback can confirm a sent change without its acknowledgement. writesPerformed is false for no-op, true for a saved acknowledgement or recorded upload, and can be null when a sent effect is uncertain. verification.confirmed and fullReadbackVerified apply only to fresh completion; atomicSnapshot, causalityVerified, visitorFormVerified and imageAvailabilityVerified stay false. Pending output is QUESTIONNAIRE_PENDING with checkpoints, requiredAction and possibly lastResult/journalPersistence. An auth/context failure can suppress channel/navigation metadata and retain pending status.
Task recovery
| Checkpoint | Continuation |
|---|---|
| prepared | Recheck original context/baseline; untouched stages may continue. |
| Per image: prepared | Original apply bytes are required after restart; status/resume do not reconstruct files. |
| Per image: grant-sent or upload-sent | Preserve the journal and inspect; no automatic retry of attempted grant/upload. |
| Per image: uploaded | Reuse the checked durable receipt; no second upload. |
| save-sent | Known qrid permits full readback only; never resend create/revise. |
| done | Immutable historical result on status/replay with confirmed:false; get again for current state. |
Use task_status or task_resume with the original taskId and explicit taskType questionnaire. Same-ID apply must keep the original input/revision; dryRun is outside its fingerprint. A conflicting requestId returns REQUEST_ID_CONFLICT. Invalid stored identity/fingerprint/progress returns TASK_STATE_UNVERIFIED without adopting the journal. QUESTIONNAIRE_CONFLICT requires a fresh read before an unsent new operation; it is not permission to replace the ID of an uncertain task.
If creation was sent without a durable returned qrid, QUESTIONNAIRE_CREATE_UNKNOWN requires operator inspection. Do not adopt a similarly titled record or automatically repeat creation. A known-ID mismatch remains pending without resend. Completed journal rejection preserves history. There is no questionnaire DELETE tool; deactivate an unwanted record.
Navigation opens the known editor, or the list until create identity is durable.
Saved record identity
A missing record differs from an existing malformed record. Stored JSON null,
false, zero or an empty string is rejected with TASK_STATE_UNVERIFIED by the
existing record validator. Apply/dry-run/status/resume must not overwrite it or
treat it as a fresh task. Preserve the file for inspection; do not delete/edit it
or replace the request ID to bypass the error. Existing valid task recovery and
genuinely missing-record behavior remain unchanged.