# Forms API

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

**URL:** https://inference.sh/docs/api/rest/forms
**Last updated:** 2026-10-11

---

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](/docs/api/rest/teams#bounties) (`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](/docs/api/rest/teams#survey), [Bounties](/docs/api/rest/teams#bounties), and [CLI setup — Forms](/docs/extend/cli-setup#forms-cli).

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](/docs/api/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](#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](/docs/api/rest/entitlements#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 example `short_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 `required` list, when its property sets `x-required: true`, or when an `allOf[].then.required` list (standard JSON Schema) names it.
- Minimum answer length (for example on survey prompts) comes from JSON Schema `minLength` on 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.

→ [Workspaces API — Survey](/docs/api/rest/teams#survey) · [CLI — Feedback](/docs/extend/cli-setup#feedback-cli)
