> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Forms

> List form definitions and retrieve paginated submission records captured from your published pages.

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

export const APIBadge = ({method = "GET", path, scope}) => {
  const m = method.toUpperCase();
  return <span className="gv-api-badge">
      <span className={`gv-api-badge__method gv-api-badge__method--${m.toLowerCase()}`}>
        {m}
      </span>
      <code className="gv-api-badge__path">{path}</code>
      {scope && <span className="gv-api-badge__scope">
          <span className="gv-api-badge__scope-label">scope</span>
          <code>{scope}</code>
        </span>}
    </span>;
};

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="RESOURCE" />

Forms are authored in the Builder by adding a Form block to a page and binding its fields to a schema-defined table. This resource gives you read access to the form definitions and their submissions. Form creation is Builder-only — there are no write endpoints for form schemas. Submissions are written by end-users filling out the rendered form on your live page, not through this API. By the end of this page you will know what reads exist, how submissions are paginated, and what the on-submit hook represents.

## A submission in shape

<ExpectedOutput>
  ```json theme={null}
  {
    "id":           "sub_01H...",
    "form_id":      "frm_01H...",
    "data":         { "name": "Alex Kim", "email": "alex@example.com", "company": "Acme" },
    "submitted_at": "2026-05-09T09:22:11Z"
  }
  ```
</ExpectedOutput>

`data` keys mirror the field names on the form definition; values are the end-user input.

## Endpoints

<APIBadge method="GET" path="/v1/workspaces/{slug}/forms" scope="forms:read" />

List all forms in the workspace.

<APIBadge method="GET" path="/v1/workspaces/{slug}/forms/{id}" scope="forms:read" />

Fetch the schema for a specific form.

<APIBadge method="GET" path="/v1/workspaces/{slug}/forms/{id}/submissions" scope="forms:read" />

Paginated submission records for a form.

All three endpoints require `forms:read`. There are no write endpoints — forms must be created and edited in the Builder.

## What a form definition carries

<DefList>
  <Def term="id" type="string">Stable form identifier (`frm_01H...`).</Def>
  <Def term="name" type="string">Display name set in the Builder.</Def>
  <Def term="schema_table" type="string">The schema-defined table submissions are written into.</Def>
  <Def term="fields" type="string[]">Ordered list of field names; each maps to a column on `schema_table`.</Def>
  <Def term="submit_label" type="string">Text on the form's submit button.</Def>
  <Def term="on_submit" type="object">Optional capability invocation fired after a submission is recorded — `{ capability, args }`.</Def>
</DefList>

The `on_submit` hook lets a form trigger a workspace capability — typically `sendEmail` for confirmation — without separate wiring.

## Submissions pagination

`GET /forms/{id}/submissions` returns submissions newest-first, with cursor pagination. Defaults: `limit=50`, maximum `200`. Cursor mechanics match the [Pagination](/api-reference/pagination) protocol; the response shape is the standard `data` + `next_cursor` envelope, with an extra boolean `has_more`.

## Limits

<DefList>
  <Def term="Read-only form schemas">Forms are Builder-authored. No POST, PATCH, or DELETE on form definitions.</Def>
  <Def term="Read-only submissions">Submissions are end-user writes from rendered pages. No endpoint creates or modifies a submission.</Def>
  <Def term="Bulk delete">Not currently supported through the API. Contact support if you need it for data-compliance reasons.</Def>
</DefList>

## Common errors

<ErrorTable
  errors={[
{ code: "400 invalid_cursor", cause: "The submissions cursor is malformed or has expired.", fix: "Restart pagination from page one." },
{ code: "403 insufficient_scope", cause: "Token lacks `forms:read`.", fix: "Mint a token with `forms:read`." },
{ code: "404 not_found", cause: "Form or workspace not found, or not visible to this token.", fix: "Verify the form id and that the token can see the workspace." },
]}
/>

## Related

<Card title="Form block" icon="input-cursor-move" href="/pageblocks/blocks/form">
  Add a Form block, bind it to a schema table, and wire an on-submit capability in the Builder.
</Card>
