Schema-driven forms collect structured answers from users and agents. Each form is a JSON Schema (with form-specific extensions) plus lifecycle and submission policy settings. Answers are stored as submissions only — credit rewards are claimed separately through bounty programs (POST /me/bounty), not on submit.
The belt CLI exposes forms as belt form (list, get, fill, submissions). Customer-discovery survey prompts are backed by platform forms under infsh/<question_id>. Programs that pay for a form set proof_type: form and proof_form to that ref (for example infsh/discovery); after you submit, claim with proof_id set to the submission id. See Survey, Bounties, and CLI setup — Forms.
Requires API key scopes apps:read (list and read forms and submissions), apps:write (create and update forms), and user:write (submit answers). Listing your own submissions across forms uses user:read.
→ REST overview — response format
Form object
| Field | Description |
|---|---|
namespace | Team namespace (workspace username) |
name | Immutable slug; address as namespace/name |
title, description | Display text |
schema | JSON Schema for answers (see Schema) |
status | draft, open, or closed — only open accepts submissions |
submit_policy | once_per_user, once_per_team, or many |
Resolve a form by ref: GET /forms/{namespace}/{name}.
List forms
GET /forms or POST /forms/list
Cursor pagination (limit, cursor). Pass include_others: true in the POST body (or the equivalent query flag) to include public forms owned by other teams.
GET /forms/count returns a total for the same filters.
Get a form
GET /forms/{id} — by form ID.
GET /forms/{namespace}/{name} — by stable ref (preferred for scripts and the CLI).
Create a form
POST /forms
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Slug in your team's namespace |
title | string | Yes | Display title |
description | string | No | Longer description |
schema | object | No | JSON Schema for answers |
submit_policy | string | No | Default once_per_user |
visibility | string | No | private, team, org, public, or unlisted |
Creating and updating forms needs the invitation-only feature:forms entitlement; without it the API returns 403 feature_not_available (see Entitlement requests). New forms start in draft. Open them with PATCH /forms/{id} (status: "open") before accepting submissions.
Update a form
PATCH /forms/{id} or PUT /forms/{id}
Patch fields: title, description, schema, status, submit_policy.
PUT /forms/{id}/visibility updates visibility without replacing the whole form.
DELETE /forms/{id} removes the form.
Submit answers
POST /forms/{id}/submissions
| Field | Type | Required | Description |
|---|---|---|---|
data | object | Yes | Answers validated against schema |
source | string | No | Client id (CLI sends cli) |
agent | string | No | Agent runtime (e.g. cursor) |
context | string | No | Freeform context (recent commands, page path) |
Response (SubmitFormResponse):
| Field | Description |
|---|---|
submission | Stored row (id, data, source, agent, …) |
To earn credits for a program that uses proof_type: form, call POST /me/bounty with proof_id set to submission.id after a successful submit. The program's proof_form must match this form's namespace/name.
Errors:
| HTTP | Code | Cause |
|---|---|---|
409 | form_closed | status is not open |
409 | already_submitted | submit_policy has no room for another answer from you or your team |
422 | validation_error | Answers failed schema validation, or an answer is fill-in placeholder text; meta lists every field error |
List submissions
On a form (owners): GET /forms/{id}/submissions, POST /forms/{id}/submissions/list, GET /forms/{id}/submissions/count
Yours across forms: GET /me/submissions, POST /me/submissions/list, GET /me/submissions/count
GET /forms/{id}/submissions/{submission_id} returns one submission.
Schema
Forms use JSON Schema for answer shape. The web app and belt form share the same interpretation:
- Question types use
x-widget(for exampleshort_text,single_choice,rating,yes_no). - Conditional fields use
x-visible-when(same semantics as the app: malformed or forward references are ignored). - A question is required when it is in the top-level
requiredlist, when its property setsx-required: true, or when anallOf[].then.requiredlist (standard JSON Schema) names it. - Minimum answer length (for example on survey prompts) comes from JSON Schema
minLengthon the question property, not from bounty program settings.
Answers are a single JSON object keyed by question key (property name). Hidden questions should be omitted; blank optional answers are dropped before submit.
Survey alias
POST /me/survey and GET /me/survey remain the legacy entry points the CLI uses for customer-discovery questions. Each question_id maps to the platform form infsh/<question_id> whenever that form exists, independent of bounty programs; an unknown id returns 400 unknown question. Prefer the Forms API for new integrations that are not survey-specific.