Dailybot Forms
Requires
dailybot-cli >= 3.9.0(the skill-pack baseline). The full forms lifecycle —form list/submit/get/responses/response get/update/transition/delete— plus the structured--json4xx error shape are all available. Ifdailybot --versionis below 3.9.0, ask the developer to rundailybot upgrade. See../SKILL.md§ Required Dailybot CLI version for install commands and version-check tooling.
You help developers work with the full Dailybot forms lifecycle: list, inspect, submit, update, transition between workflow states, and read prior responses. Forms are custom questionnaires created by team leads — feedback surveys, retrospectives, release checklists, approval flows, or any structured data collection. Some forms are simple "fill once and done"; others have workflow states (e.g. draft → review → released) with audience-scoped permissions on who can edit and who can transition.
This skill is distinct from daily check-ins (handled by dailybot-checkin) and free-text reports (handled by dailybot-report).
Upgrade-safety contract — read this before editing forms behavior
This SKILL.md is the universal forms skill. It is overwritten on every upgrade of the Dailybot skill pack (managed by skills-lock.json). Two rules keep customer customizations safe:
| MUST | MUST NOT |
|---|---|
Read .dailybot/profile.json and honor vars.custom_form_skills. |
Write anything outside .agents/skills/dailybot/. |
| Defer to a custom skill when the resolver finds a mapping. | Read, modify, or scan files under .agents/skills/dailybot-custom/ or any other customer-owned namespace. |
| Warn (do not error) when a registered custom-skill path is missing on disk — fall back to the generic flow. | Send .dailybot/profile.json contents (especially vars) in any report / payload to the server. |
| Document this contract verbatim here. | Embed customer-specific logic in this universal skill — if you find yourself writing if form.name == "Release Form":, the right answer is a custom skill under .agents/skills/dailybot-custom/. |
Customer-authored form skills live at .agents/skills/dailybot-custom/<name>/SKILL.md. The universal skill MUST defer to them; never copy customer logic into this file.
Auth model — API key or login
All form commands accept either a Bearer login session (dailybot login) or an org API key (DAILYBOT_API_KEY). Access is scoped to the acting identity's permissions (the server resolves the API key's owner) — they only see forms (and responses) they have access to, and the server enforces every audience check on the API side.
If the developer has only an API key, form commands still work — the CLI falls back to X-API-KEY.
When to Use
- The developer asks "what forms do I have?", "list my forms", "show available surveys"
- The developer asks to "fill out the retro form", "submit the feedback survey", "answer the pulse check"
- The developer wants to continue an in-progress form response: "keep filling the release form", "update my draft"
- The developer wants to move a response forward: "mark the release as approved", "transition to released", "send for review"
- The developer wants to read prior responses: "show me the last release form", "what was answered last time?"
- The developer wants to delete a response they own
Do not use this skill for daily standup check-ins — route those to dailybot-checkin. Forms are ad-hoc, periodic, or workflow-driven; check-ins are recurring daily/weekly rituals tied to follow-ups.
Step 1 — Verify Setup
Read and follow the authentication steps in ../shared/auth.md. That file covers CLI installation, login, API key setup, and agent profile configuration.
Additionally, confirm at least one credential is present (a login session or an API key):
dailybot status --auth 2>&1
If the output shows a logged-in user session or a configured API key, proceed. Otherwise guide them through dailybot login (see auth.md) or ask them to set DAILYBOT_API_KEY.
A scripted preflight that does both at once and exits with code 3 if unauthenticated:
dailybot status --auth >/dev/null 2>&1 || { echo "Run: dailybot login"; exit 3; }
If auth fails or the developer declines, skip and continue with your primary task.
Step 2 — Lifecycle Decision Tree
When the developer asks anything form-related, walk this tree before acting:
1. Identify the target form by name or slug (or pick from a list).
2. Run `dailybot form list --json` to find its UUID and confirm visibility.
3. Run `dailybot form get <form_uuid> --json` to read questions, the `workflow`
object (`null` or `{enabled, states}`), audience configuration, and the form's slug.
4. RESOLVER: check .dailybot/profile.json → vars.custom_form_skills.
- If a custom skill is registered for this form (by UUID or by slug),
LOAD THAT SKILL'S SKILL.md and follow its instructions INSTEAD of
continuing the generic flow.
- Otherwise, continue.
5. Decide whether to continue an existing response or start fresh:
- `dailybot form responses <form_uuid> --latest --json`
- If a response exists and its `current_state` is NOT terminal
(or the form allows reopening), prefer the update + transition path.
- Otherwise, submit a new response.
6. After every mutating call, re-read `current_state` and `allowed_transitions`
on the response to decide the next move (update? transition? done?).
The resolver step (4) is the customer-extension hook. See Step 7 — Custom-skill resolver below.
Step 3 — List Available Forms
dailybot form list --json
Returns all forms in the caller's organization. Every org member sees every org form; capabilities (editing, response visibility, state changes) are governed by each form's permissions. Pass --mine to narrow to only your own forms. (If a developer reports "I only see a handful of my forms," check dailybot --version and have them dailybot upgrade — older CLIs had a server-side visibility bug.) The shape is stable and machine-readable:
[
{
"uuid": "<form-uuid>",
"slug": "team-feedback",
"name": "Team Feedback",
"workflow": null,
"questions": [
{
"uuid": "<question-uuid>",
"question": "How was your week?",
"question_type": "text_field"
}
]
},
{
"uuid": "<form-uuid>",
"slug": "code-release-form",
"name": "Code Release Form",
"workflow": { "enabled": true, "states": [ ... ] },
"questions": [ ... ]
}
]
The
slugfield is stable across environments (dev / staging / prod), whileidrotates per environment. Preferslugfor any persistent mapping you keep across deployments. See the resolver in Step 7.
Pagination, search, and date filters
Note: the shared list query flags below are part of the
dailybot-cli >= 3.9.0baseline. Older CLIs return the full list with no filtering.
form list accepts the full shared list query flag set — pagination
(--page, --page-size, --all, --limit), search (--search / --grep),
and date range (--since, --until, --date, --last-week, --today). With
no flags it fetches everything. The complete flag table, the
{count, next, previous, results} envelope, and the Showing X of N footer are
documented once in ../shared/list-query-and-errors.md.
# Search forms by name, restricted to a date range:
dailybot form list --search retro --since 2026-07-01 --json
# Fetch every page explicitly (equivalent to the no-flag default):
dailybot form list --all --json
# One explicit page of 20:
dailybot form list --page 2 --page-size 20 --json
Filter by owner — --mine and --owner
Pass --mine to narrow the result to only the forms you own, or
--owner (repeatable) to filter by specific owners. --owner accepts
a UUID, email, or name — non-UUID values are resolved via the org directory.
The two flags can be combined (AND semantics with every other filter).
# Only the forms I own:
dailybot form list --mine --json
# Forms owned by a specific person (by name):
dailybot form list --owner "Jane Doe" --json
# Forms owned by two people (by UUID):
dailybot form list --owner <uuid-1> --owner <uuid-2> --json
Deprecation: the
--filter mescope is deprecated server-side. Use--mineor--ownerinstead —--filter mestill works but maps to the legacyfilter=meparameter.
Form owners picker — form owners
A lightweight endpoint to discover which org members own at least one form, without pulling the full member directory.
dailybot form owners # table of owners
dailybot form owners --search jane # search by name/email
dailybot form owners --json # machine-readable
Each result has uuid, full_name, image, role, and optionally email
(email is only visible to admins/managers — the CLI must not assume it exists).
Server-side filtering, sorting, and archived forms
| Flag | Values | Description |
|---|---|---|
--filter |
all, public, approval, workflow, archived |
Scope filter (server-side). me still works but is deprecated. |
--owner |
UUID, email, or name (repeatable) | Filter by form owner(s). Max 50. |
--order |
alphabetical, recent, total |
Sort field (total = total response count). |
--ascending / --asc |
flag | Sort ascending (default: descending). |
--include-questions |
flag | Include question definitions in each form. |
--include-archived |
flag | Include archived forms (hidden by default). |
# Workflow-enabled forms, sorted alphabetically ascending:
dailybot form list --filter workflow --order alphabetical --asc --json
# Only forms with approval flow:
dailybot form list --filter approval --json
# Public forms sorted by total responses:
dailybot form list --filter public --order total --json
# Archived forms:
dailybot form list --filter archived --json
# Include question definitions:
dailybot form list --include-questions --json
The envelope is unconditional.
GET /v1/forms/and the responses endpoint below always return{count, next, previous, results}; no query parameter is needed. See the shared doc.
Present forms to the developer
When forms are found:
"You have 2 forms available in Dailybot:
- Team Feedback — 3 questions (no workflow)
- Code Release Form — 8 questions (workflow: draft → review → released)
Which one would you like to work with?"
When no forms are found:
"No forms are available for you right now."
Step 4 — Inspect a Form
dailybot form get <form_uuid> --json
Use this before submitting or updating. It returns the form's full configuration including workflow definition and the audience-based permission flags. Treat this output as the source of truth for "what can I do on this form?".
JSON shape (workflow-enabled form)
The workflow definition is nested under the workflow key: null when the form has no
workflow, or {"enabled": true, "states": [...]} when it does. Each state carries
{key, label, color, order} — the ordered list defines the forward progression (the
first state is the initial one, the last is the final/terminal one). allow_reopen_from_final_state
is a top-level boolean. Audience permissions also live at the top level. All of it is
server-defined.
{
"uuid": "<form-uuid>",
"slug": "code-release-form",
"name": "Code Release Form",
"allow_reopen_from_final_state": false,
"state_change_permission": { "audience": "...", "...": "..." },
"edit_permission": { "audience": "...", "...": "..." },
"view_reports_permission": { "audience": "...", "...": "..." },
"workflow": {
"enabled": true,
"states": [
{"key": "draft", "label": "Draft", "color": "#9CA3AF", "order": 0},
{"key": "review", "label": "Review", "color": "#F59E0B", "order": 1},
{"key": "released", "label": "Released", "color": "#10B981", "order": 2}
]
},
"questions": [ ... ]
}
A form with no workflow returns
"workflow": null. The legacyworkflow_enabledboolean andworkflow_configobject are gone — readworkflowand, when present,workflow.enabled/workflow.states. The per-response moves the current caller can make still arrive asallowed_transitionson each response payload (Steps 5–6) — the server computes those from the ordered states and the caller's audience.
Audience permissions
The three *_permission fields are server-evaluated. Treat them as opaque in this skill — never re-implement the audience evaluation client-side. The server returns can_change_state, can_edit, and can_view_reports (or equivalent) on each response payload as the decided result. Trust those booleans.
Step 5 — Workflow-state Vocabulary
When the form has a workflow (workflow.enabled: true), the agent must understand five fields that appear on every response payload. Surface them when they affect what the developer can do next:
| Field | Meaning | Agent behavior |
|---|---|---|
current_state |
The effective state of this response right now. | Display the label. Drive the next action from it. |
allowed_transitions |
[{to_state, label}] — the moves the current caller can make from current_state. Server-computed; honors audience. |
Pick one and confirm with the developer before invoking transition. If the list is empty, no transitions are available to the caller. |
can_change_state |
Boolean — whether the caller is in the audience for state changes. | If false, do not offer the transition path. Tell the developer the audience excludes them. |
allow_reopen_from_final_state |
Form-level boolean. false (default) means the terminal state is sticky — once reached, the response cannot move out. |
If false and current_state is a final state, do not attempt a transition. If true, the response can transition back to a non-final state. |
state_history |
Append-only list of {from_state, to_state, actor_name, at} entries. |
Surface the latest entry (who moved it last, when) when relevant for context. |
Never infer state transitions from labels or names — the only valid moves are the ones in
allowed_transitions. The server's audience checks may legitimately exclude transitions the developer thinks should be available.
Step 5.5 — Authoring forms (create / configure / questions)
Requires
dailybot-cli >= 3.9.0(the skill-pack baseline). The full authoring surface —form create,form config(workflow states, the three permission audiences, anonymous/public/brand/require-identity withpublic_url, approval + approvers, the ChatOps command),form archive, theform questions add|edit|delete|reordergroup, resolving people by email,--no-approvers, the 3 report-channel cap, and the create requires ≥ 1 question rule (questions_required) — is all available. Ifdailybot --versionis below 3.9.0, ask the developer to rundailybot upgrade.
Everything above this point reads and responds to forms. This section builds and reshapes them. An agent with the right permissions can now create a form, wire up its workflow states, permission audiences, approval flow, and ChatOps command, and manage its questions — all end-to-end from the CLI, without opening the Dailybot webapp.
Permissions are server-enforced. Creating and configuring forms typically requires org admin / manager rights (or an API key whose owner has them). If the acting identity lacks the rights, the server returns a 403 — surface it and fall back to the non-blocking rule; never re-implement the permission check client-side.
One-shot create vs. incremental config
There are two ways to reach the same configured form. Pick based on how much you know up front:
| Approach | Command | When to use |
|---|---|---|
| One-shot | dailybot form create -n NAME [all config flags] [--questions-file / --interactive] |
You already know the full shape (name, workflow, permissions, seed questions). Everything lands in a single call. |
| Incremental | dailybot form create -n NAME → then one or more dailybot form config <uuid> [flags] → then form questions add … |
You're building up the form conversationally, or reconfiguring an existing one. |
form createandform configaccept the same config flags (listed below).createadditionally accepts the question-seeding flags (--questions-file,--interactive,--ai-short-question);configdoes not seed questions — use theform questionsgroup for that.form configis a full partial-update: send only the flags you want to change; anything you omit is left untouched. It is a strict superset ofform edit(which only touches name + report channels). Preferform configfor anything beyond a name/channel tweak.--report-channeland the permission/approver objects use full-replace semantics — see the callouts below. Most other scalar flags are simple set-if-passed.
A form must have at least one question at create time — POST create with a missing/empty questions array is rejected with 400 {"code": "questions_required"} ("A form must have at least one question."). Seed at least one question inline on create (see the --questions-file schema below); you can add, edit, remove, and reorder questions afterward via the form questions group. (Even a workflow-only or approval-routing form needs a seed question.)
Config flag reference
Both form create and form config accept these. Grouped by concern:
Basics
| Flag | Meaning |
|---|---|
-n, --name |
Form name (min length enforced — too short → form_name_too_short). |
--active / --inactive |
Whether the form is active (accepting responses). |
--report-channel UUID |
Chat channel to post responses to. Repeatable, max 3. On config this REPLACES the entire set — pass all channels you want every time. More than 3 → too_many_report_channels. |
Sharing / behavior
| Flag | Meaning |
|---|---|
--anonymous / --no-anonymous |
Collect responses anonymously. Freely toggleable in both directions (unlike check-in anonymity, which is irreversible once on). |
--public / --no-public |
Allow responses through a public shared link (no Dailybot account needed). Surfaces public_url when on — see Public forms below. |
--brand / --no-brand |
Brand the public form with the org logo. |
--require-identity / --no-require-identity |
Make email + name mandatory on public responses. |
--reopen-from-final / --no-reopen-from-final |
Set the top-level allow_reopen_from_final_state boolean (whether a response can move back out of a terminal workflow state). |
Workflow
| Flag | Meaning |
|---|---|
--state "Label:#color" |
Add a workflow state. Repeatable and ordered — position defines the sequence. Passing any --state enables the workflow. Max 20 states. |
--no-workflow |
Turn the workflow off (clears states). |
Permissions (three independent audiences)
| Flag | Meaning |
|---|---|
--can-edit MODE |
Who can edit responses. MODE = everyone | owner_and_admins | restricted. |
--can-see MODE |
Who can see responses/reports. Same MODE values. |
--can-change-states MODE |
Who can transition responses between states. Same MODE values. |
--can-edit-user, --can-edit-team |
For restricted edit: name the allowed users/teams. |
--can-see-user, --can-see-team |
For restricted view. |
--change-states-user, --change-states-team |
For restricted state-changes. |
Users accept name / email / UUID; teams accept name / UUID. Passing any -user/-team flag implies restricted mode for that audience — you don't have to also pass --can-edit restricted. Each audience object is full-replace: the users/teams you send become the new value; omitting a key clears it. A restricted audience with empty user + team lists effectively means "owner + admins only".
Approval flow
| Flag | Meaning |
|---|---|
--approval / --no-approval |
File new submissions for approval before they count as final. |
--approver-user |
An approver by name / email / UUID. Repeatable. |
--approver-team |
An approver team. Repeatable. |
--no-approvers |
Clear the approver list. |
The approver list is full-replace ({user_uuids, team_uuids}) — pass every approver each time you set it.
ChatOps command
| Flag | Meaning |
|---|---|
--command NAME |
The chat shortcut that opens the form (e.g. release, invoked as @dailybot release). Charset [a-z0-9][a-z0-9_-]{0,30}; unique per org. Invalid → invalid_command; taken by another form → command_already_exists. |
--no-command |
Remove the command. |
Question seeding (create only)
| Flag | Meaning |
|---|---|
--questions-file PATH |
Seed questions from a JSON array (see the questions-file schema below). Max 50. |
--interactive |
Walk through adding questions one at a time. |
--ai-short-question |
Let the server auto-generate the report title for seeded questions that omit one. |
Workflow states — write shape vs. read shape
The write shape and the read shape are deliberately asymmetric:
- Write (
--state "Label:#color"): you send only{label, color}, ordered by flag position. The server deriveskey(a slugified label) andorder(the position) for you. - Read (
form get): each state comes back as{key, label, color, order}insideworkflow.states(withworkflow.enabled: true).
# Enable a 3-state release workflow (order follows flag order):
dailybot form config <form_uuid> \
--state "Draft:#9CA3AF" \
--state "Review:#F59E0B" \
--state "Released:#10B981"
# Turn the workflow off entirely:
dailybot form config <form_uuid> --no-workflow
- A form with
--stateflags hasworkflow.enabled: true; enabling a workflow with no states is invalid →workflow_requires_states. A malformed"Label:#color"spec →invalid_workflow_state. - Max 20 states.
Permission audiences — the three independent controls
--can-edit, --can-see, and --can-change-states are independent — a form can let everyone see responses but restrict state changes to two people. Each takes one of:
| MODE | Meaning |
|---|---|
everyone |
Anyone with form access. |
owner_and_admins |
The form owner plus org admins. |
restricted |
Only the named users/teams (empty lists ⇒ owner + admins). |
# Everyone can see; only two named people can change states; owner+admins edit.
dailybot form config <form_uuid> \
--can-see everyone \
--can-edit owner_and_admins \
--change-states-user jane@example.com \
--change-states-user "John Doe" \
--change-states-team "Release Managers"
Passing --change-states-user/--change-states-team above implied restricted for that audience — no separate --can-change-states restricted needed. Remember full-replace semantics: the next time you set that audience, list everyone who should be in it. Invalid audience mode → invalid_permission_audience.
Approval + command
# Route new submissions through an approver team, and bind a ChatOps shortcut.
dailybot form config <form_uuid> \
--approval \
--approver-team "Release Managers" \
--approver-user lead@example.com \
--command release # opens the form via `@dailybot release`
- Approvers are full-replace.
--no-approversclears the list;--no-approvalturns the flow off. Invalid approver reference →invalid_approvers. - The command name is unique per org:
command_already_existsif another form already claims it,invalid_commandif it violates the charset.--no-commandunbinds it.
Public forms and public_url
When --public is on, form get (and the output right after form config --public) includes a public_url — an absolute, shareable link anyone can open to submit a response:
https://app.dailybot.com/forms/<form-uuid>/responses/create/
public_url is null when --no-public. Pair --public with --require-identity if you need submitter email + name, and --brand to show the org logo.
# Anonymous public NPS survey with a branded, shareable link:
dailybot form create -n "Q3 NPS Survey" \
--anonymous --public --brand --require-identity \
--report-channel <channel-uuid>
# → the response includes public_url — share it with respondents.
Question authoring
Questions are managed with the form questions subgroup. The question model is identical to check-in questions.
dailybot form questions add <form_uuid> --type TYPE --question TEXT [flags]
dailybot form questions edit <form_uuid> <question_uuid> [flags]
dailybot form questions delete <form_uuid> <question_uuid>
dailybot form questions reorder <form_uuid> <q_uuid> <q_uuid> ...
Types — the complete catalog is exactly four:
--type |
Notes |
|---|---|
text |
Free-text answer. |
multiple_choice |
Requires --options "A,B,C" (comma-separated). |
boolean |
Yes/No. No options. |
numeric |
Integer or decimal. |
Common flags
| Flag | Meaning |
|---|---|
--question TEXT |
The question prompt. |
--required / --optional |
Whether an answer is mandatory. |
--blocker / --no-blocker |
Whether leaving it blank blocks submission. |
--short-question "Title" |
Report title (≤ 512 chars). Required on add — see below. |
--ai-short-question |
Let the server generate the report title instead of --short-question. |
--variation TEXT |
Alternate phrasing shown to different respondents. Repeatable, up to 10. |
--options "A,B,C" |
Choices for multiple_choice. |
--logic-file PATH / inline jump flags |
Conditional logic — see below. |
Report title is mandatory on
add. Every question needs a report title: pass either--short-question "Title"or--ai-short-question. Explicit titles are preserved; AI only fills blanks. Passing neither →short_question_required. Oneditthe report title is not required (edits are partial).
Conditional logic lets a question jump forward, or trigger another check-in or form, based on the answer. Provide it inline (simple single-rule jump) or via --logic-file (full control):
# Inline: if the answer equals "No", jump to question index 5; else jump to 3.
dailybot form questions add <form_uuid> \
--type multiple_choice --options "Yes,No" \
--question "Did all tests pass?" \
--short-question "Tests passed" \
--jump-if-equals "No" --jump-to 5 --else-jump-to 3
The full logic object (what --logic-file contains, and what the inline flags build under the hood):
{
"rules": {
"rules_if": [
{
"conditions": [
{"operator": "is_equal_to", "comparison_value": "No", "logic_connector": "and"}
],
"then": {"action": "jump_to", "target": 5}
}
],
"rules_else": {"action": "jump_to", "target": -1}
}
}
rules_elseis required.target: -1means "jump to the end" (finish the form).- Jumps are forward-only:
targetmust be greater than the current question index, or-1. On delete/reorder the server auto-clamps dangling targets so logic never points at a removed/moved question.
Operators allowed per question type:
| Type | Operators |
|---|---|
text |
is_equal_to, is_not_equal_to, contains, not_contains, begins_with, not_begins_with, ends_with, not_ends_with |
numeric |
is_equal_to, is_not_equal_to, lower_than, lower_or_equal_than, greater_than, greater_or_equal_than |
multiple_choice / boolean |
is_equal_to, is_not_equal_to |
Logic connectors (logic_connector on each condition):
| Type | Connectors |
|---|---|
text, numeric, boolean |
and / or |
multiple_choice |
or only |
booleancomparison values are JSONtrue/false(not strings).
Actions (then.action and rules_else.action):
| Action | target |
|---|---|
jump_to |
An integer question index (forward-only, or -1 for end). |
trigger_checkin |
A check-in UUID — firing this question routes the respondent into that check-in. |
trigger_form |
A form UUID — a form question can chain into another form. |
A trigger_form example — when the release is a hotfix, chain into the hotfix intake form:
{
"rules": {
"rules_if": [
{
"conditions": [
{"operator": "is_equal_to", "comparison_value": "Hotfix", "logic_connector": "or"}
],
"then": {"action": "trigger_form", "target": "<hotfix-form-uuid>"}
}
],
"rules_else": {"action": "jump_to", "target": -1}
}
}
Editing and reordering
# Partial edit — report title NOT required here:
dailybot form questions edit <form_uuid> <question_uuid> \
--question "Did every test pass in CI?" --required
# Reorder — you MUST pass the COMPLETE set of question UUIDs in the new order.
dailybot form questions reorder <form_uuid> <q3> <q1> <q2>
An incomplete reorder (missing any of the form's question UUIDs) → question_uuids_incomplete.
--questions-file schema (seed on create)
A JSON array (max 50 objects). Each object accepts these keys (aliases shown with /):
| Key | Meaning |
|---|---|
question_type / type |
One of text, multiple_choice, boolean, numeric. |
question / label |
The prompt text. |
options |
Choices array (for multiple_choice). |
required |
Boolean. |
is_blocker |
Boolean. |
short_question |
Report title (omit + rely on --ai-short-question to auto-fill). |
variations |
Array of alternate phrasings (≤ 10). |
logic |
The conditional-logic object (same shape as above). |
[
{
"question_type": "text",
"question": "What service is being released?",
"short_question": "Service",
"required": true,
"is_blocker": true
},
{
"type": "multiple_choice",
"label": "Release kind?",
"options": ["Standard", "Hotfix"],
"short_question": "Kind",
"logic": {
"rules": {
"rules_if": [
{"conditions": [{"operator": "is_equal_to", "comparison_value": "Hotfix", "logic_connector": "or"}],
"then": {"action": "trigger_form", "target": "<hotfix-form-uuid>"}}
],
"rules_else": {"action": "jump_to", "target": -1}
}
}
}
]
dailybot form create -n "Code Release Form" \
--questions-file ./release-questions.json --ai-short-question
form get — canonical detail JSON
dailybot form get <form_uuid> --json returns the full authoritative shape. This is the read contract for everything the authoring flags write:
{
"uuid": "<form-uuid>",
"name": "Code Release Form",
"is_active": true,
"is_archived": false,
"is_anonymous": false,
"allow_public_responses": true,
"public_url": "https://app.dailybot.com/forms/<form-uuid>/responses/create/",
"require_email_and_name": true,
"brand_with_logo": true,
"allow_reopen_from_final_state": false,
"workflow": {
"enabled": true,
"states": [
{"key": "draft", "label": "Draft", "color": "#9CA3AF", "order": 0},
{"key": "review", "label": "Review", "color": "#F59E0B", "order": 1},
{"key": "released", "label": "Released", "color": "#10B981", "order": 2}
]
},
"who_can_edit": {"mode": "owner_and_admins"},
"who_can_see_responses": {"mode": "everyone"},
"who_can_change_states": {"mode": "restricted", "user_uuids": ["<uuid>"], "team_uuids": ["<uuid>"]},
"use_for_approval": true,
"approvers": {"user_uuids": ["<uuid>"], "team_uuids": ["<uuid>"]},
"command_enabled": true,
"command": "release",
"report_channels": [
{"id": "<channel-uuid>", "name": "releases", "platform": "slack", "type": "channel"}
],
"questions": [
{
"uuid": "<question-uuid>",
"index": 0,
"question": "What service is being released?",
"question_type": "text",
"required": true,
"is_blocker": true,
"short_question": "Service",
"choices": [],
"variations": [],
"logic": null
}
]
}
workflowisnullfor non-workflow forms, else{enabled, states:[{key,label,color,order}]}.who_can_edit/who_can_see_responses/who_can_change_stateseach carry{mode}and, whenrestricted,{user_uuids, team_uuids}.multiple_choicequestions exposechoices: [{label, value}]; other types returnchoices: [].
Archiving a form
dailybot form archive <form_uuid> --yes
Soft-deletes the form (is_archived: true). Confirm before invoking — archived forms disappear from form list. This does not hard-delete responses.
Authoring error codes
Server code |
Meaning / fix |
|---|---|
questions_required |
create had no questions. A form needs ≥ 1 question at create time — seed with --questions-file/--interactive. |
too_many_report_channels |
More than 3 --report-channel values. Trim to ≤ 3. |
short_question_required |
A questions add omitted both --short-question and --ai-short-question. Supply one. |
workflow_requires_states |
Tried to enable a workflow with no states. Pass at least one --state. |
invalid_workflow_state |
A --state "Label:#color" spec is malformed. Fix the label/color. |
invalid_permission_audience |
A --can-* MODE is not everyone/owner_and_admins/restricted. |
invalid_approvers |
An approver reference didn't resolve. Check the name/email/UUID. |
invalid_command |
ChatOps command violates [a-z0-9][a-z0-9_-]{0,30}. |
command_already_exists |
Another form already owns that command name. Pick another. |
form_name_too_short |
--name is below the minimum length. |
question_uuids_incomplete |
reorder didn't include every question UUID. Pass the complete set. |
unknown_field |
An unrecognized key in --questions-file / --logic-file. Remove it. |
End-to-end authoring examples
A — Release checklist form with workflow, permissions, and a ChatOps command
# 1. Create the shell.
FID=$(dailybot form create -n "Code Release Form" --active --json | jq -r '.uuid')
# 2. Wire the workflow, permissions, report channel, and command in one config call.
dailybot form config "$FID" \
--state "Draft:#9CA3AF" --state "Review:#F59E0B" --state "Released:#10B981" \
--can-see everyone \
--can-edit owner_and_admins \
--change-states-team "Release Managers" \
--report-channel <channel-uuid> \
--command release
# 3. Add questions.
dailybot form questions add "$FID" --type text \
--question "What service is being released?" --short-question "Service" --required --blocker
dailybot form questions add "$FID" --type multiple_choice --options "Standard,Hotfix" \
--question "Release kind?" --short-question "Kind"
# 4. Verify.
dailybot form get "$FID" --json | jq '{workflow, command, questions: (.questions|length)}'
# 5. Optional — attach organization Labels (CLI >= 3.9.0; create has no --labels).
# Full procedure: ../labels/SKILL.md
# LABEL=$(dailybot label create --name "Release" --color "#4A90E2" --json | jq -r '.uuid')
# dailybot label assign "$FID" --type forms --label "$LABEL"
B — Anonymous public NPS survey (shareable link)
dailybot form create -n "Q3 NPS Survey" \
--anonymous --public --brand --require-identity \
--report-channel <channel-uuid> --json | jq -r '.public_url'
# → share the printed public_url with respondents.
# Add the score question:
dailybot form questions add <form_uuid> --type numeric \
--question "How likely are you to recommend us (0–10)?" --short-question "NPS score" --required
C — Approval-routing intake form
# A workflow + approval routing form. Even routing forms need >= 1 question at
# create time (questions_required), so seed one with --questions-file.
dailybot form create -n "Budget Approval" \
--questions-file budget-questions.json \
--approval \
--approver-user finance-lead@example.com \
--approver-team "Finance" \
--state "Requested:#9CA3AF" --state "Approved:#10B981" \
--can-change-states restricted \
--change-states-team "Finance"
Step 6 — Find In-Progress Responses
Before submitting a new response, check whether there's an in-progress one to continue:
dailybot form responses <form_uuid> --latest --json
Continue-or-start idiom
The standard shell pattern for "continue if there's an in-progress response, otherwise create one":
RID=$(dailybot form responses <form_uuid> --latest --json | jq -r '.[0].uuid // empty')
if [ -z "$RID" ]; then
RID=$(dailybot form submit <form_uuid> --content '{...}' --yes --json | jq -r '.uuid')
fi
# $RID is now the response UUID — pass it to subsequent update / transition calls.
Useful flags:
| Flag | Description |
|---|---|
--latest |
Return only the most recent response visible to the caller. |
--state STATE |
Filter to responses in a specific workflow state. Pass the state key (draft), not the label (Draft) — read the keys from workflow.states[].key on form get. Filtering is server-side. On a form with no workflow the API returns 400 invalid_workflow_state. |
--all |
List everyone's responses (requires VIEW_REPORTS permission — admin/owner only; a member receives 403). |
--search TEXT |
Search response content and submitter name/email. Max 256 chars. Does not search anonymous member identities (privacy preserved). |
--json |
Machine-readable output (stable shape). |
form responsessearch. The--search/--grepflag filters responses case-insensitively — handy for finding a past response by a service name or keyword. Likeform list, this endpoint (GET /v1/forms/<uuid>/responses/) returns the{count, next, previous, results}envelope with aShowing X of Nfooter. It also takes--page/--page-size/--limit. Careful:--allhere means every author's responses (admin/owner only), not every page. See../shared/list-query-and-errors.md.
Advanced response filters
| Flag | Values | Description |
|---|---|---|
--source |
CSV: member, anonymous, automation, public |
Filter by submission origin (OR semantics within the group). |
--submitter |
CSV of UUIDs (max 50) | Filter by specific submitter user UUIDs. |
--flow-status |
pending, approved, denied |
Approval flow status filter. Silently ignored if the form has no approval flow. |
--order |
recent, oldest |
Sort by creation time. |
--ascending / --asc |
flag | Sort ascending (alias for --order oldest). |
--user UUID |
single UUID | Filter to one user's responses (admin/owner only). |
--from DATE |
YYYY-MM-DD | Responses on/after this date. |
--to DATE |
YYYY-MM-DD | Responses on/before this date. |
Submission sources — every response belongs to exactly one bucket:
| Source | Meaning |
|---|---|
member |
Identified org member submitted normally. |
anonymous |
Org member submitted anonymously (collect_responses_anonymously=true |
…(truncated)