> For the complete documentation index, see [llms.txt](https://docs.powerfulform.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.powerfulform.com/public-api/forms.md).

# APIs for Forms

List the forms in your store and read a form's field schema — the keys you need to read or create submissions.

Two read-only endpoints. Together they tell you which forms exist and what each answer key means.

Both require the `forms:read` scope.

{% hint style="info" %}
There is no endpoint that creates or edits a form. Form configuration holds SMTP passwords, admin email addresses, reCAPTCHA secrets and integration webhook URLs, so nothing from it leaves the API except the field schema below. Build and edit forms in the app.
{% endhint %}

## List forms

```
GET /forms
```

### Query parameters

<table><thead><tr><th width="180">Parameter</th><th width="110">Type</th><th>Notes</th></tr></thead><tbody><tr><td><code>status</code></td><td>integer</td><td><code>1</code> for Active, <code>0</code> for Draft. Omit for both.</td></tr><tr><td><code>updated_at_min</code></td><td>date</td><td>Only forms changed at or after this time. Useful for incremental syncs.</td></tr><tr><td><code>limit</code></td><td>integer</td><td>1–250. Defaults to 50.</td></tr><tr><td><code>cursor</code></td><td>string</td><td>The <code>next_cursor</code> from the previous page.</td></tr></tbody></table>

### Example

```bash
curl "https://app.powerfulform.com/api/public/v1/forms?status=1&limit=50" \
  -H "X-PFB-API-KEY: pfb_your_key_here"
```

```json
{
  "data": [
    {
      "id": 10,
      "title": "Wholesale enquiry",
      "status": 1,
      "form_type": "normalForm",
      "submission_count": 214,
      "created_at": "2026-08-02T04:11:09Z",
      "updated_at": "2026-09-16T02:40:55Z"
    },
    {
      "id": 11,
      "title": "Contact us",
      "status": 0,
      "form_type": "normalForm",
      "submission_count": 38,
      "created_at": "2026-07-19T11:03:22Z",
      "updated_at": "2026-09-01T08:55:10Z"
    }
  ],
  "meta": { "limit": 50, "has_next": false, "next_cursor": null }
}
```

### Fields

<table><thead><tr><th width="220">Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>id</code></td><td>Form id. Use it in every other path.</td></tr><tr><td><code>title</code></td><td>The form's name in the app.</td></tr><tr><td><code>status</code></td><td><code>1</code> Active, <code>0</code> Draft. Only an Active form accepts submissions.</td></tr><tr><td><code>form_type</code></td><td>What kind of form it is: <code>normalForm</code>, <code>registerForm</code>, <code>cartForm</code>, <code>bulkOrderForm</code>.</td></tr><tr><td><code>submission_count</code></td><td>Total submissions stored for this form.</td></tr></tbody></table>

## Get a form

```
GET /forms/{id}
```

Returns everything above plus the field schema.

```bash
curl https://app.powerfulform.com/api/public/v1/forms/10 \
  -H "X-PFB-API-KEY: pfb_your_key_here"
```

```json
{
  "data": {
    "id": 10,
    "title": "Wholesale enquiry",
    "status": 1,
    "form_type": "normalForm",
    "submission_count": 214,
    "submit_limit": 5000,
    "requires_login": false,
    "field_count": 4,
    "fields": [
      { "key": "name",   "type": "text",   "label": "Full name", "required": true },
      { "key": "email",  "type": "email",  "label": "Email",     "required": true },
      { "key": "qty",    "type": "number", "label": "Quantity",  "required": false },
      { "key": "region", "type": "select", "label": "Region",    "required": true,
        "options": ["APAC", "EMEA"] }
    ],
    "created_at": "2026-08-02T04:11:09Z",
    "updated_at": "2026-09-16T02:40:55Z"
  }
}
```

### Extra fields

<table><thead><tr><th width="220">Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>submit_limit</code></td><td>The total-submissions cap set on the form, or <code>null</code> when there is none.</td></tr><tr><td><code>requires_login</code></td><td><code>true</code> when the form only accepts logged-in customers.</td></tr><tr><td><code>field_count</code></td><td>How many entries are in <code>fields</code>.</td></tr><tr><td><code>fields</code></td><td>One entry per input, in the order they appear on the form.</td></tr></tbody></table>

## The field schema

This is the part your integration needs. Each entry describes one input:

<table><thead><tr><th width="150">Property</th><th>Meaning</th></tr></thead><tbody><tr><td><code>key</code></td><td><strong>The answer key.</strong> It is what you send in <code>data</code> when creating a submission, and the key you read in a submission's <code>content</code>.</td></tr><tr><td><code>type</code></td><td>The element type — <code>text</code>, <code>email</code>, <code>number</code>, <code>select</code>, <code>file</code>, and so on.</td></tr><tr><td><code>label</code></td><td>The label shown to customers, in your store's primary language. <code>null</code> if the field has no label.</td></tr><tr><td><code>required</code></td><td>Whether a submission is rejected without it.</td></tr><tr><td><code>options</code></td><td>Only on choice fields, and only when the options can be read reliably. Absent otherwise.</td></tr></tbody></table>

A few things worth knowing:

* **Layout elements are not fields.** Headings, paragraphs, images, dividers, buttons and captcha never appear here, because they never appear in a submission either.
* **Grouped fields are flattened.** A field inside a group, row or step is returned at the top level, exactly as it is stored in a submission.
* **A multi-language label is resolved for you** — you get the text for your store's primary language, not the whole translation object.

{% hint style="warning" %}
Read the schema rather than hard-coding keys. Editing a form in the app can add a required field, and the first your integration hears of it is a `422` on the next create. Re-reading `GET /forms/{id}` at the start of a sync makes that visible before it fails.
{% endhint %}

## Errors

<table><thead><tr><th width="120">Status</th><th width="220">Code</th><th>Cause</th></tr></thead><tbody><tr><td>403</td><td><code>insufficient_scope</code></td><td>The key is missing <code>forms:read</code>.</td></tr><tr><td>404</td><td><code>not_found</code></td><td>No such form, or it belongs to another store.</td></tr><tr><td>422</td><td><code>validation_failed</code></td><td>A query parameter was rejected — for example a <code>status</code> other than 0 or 1.</td></tr></tbody></table>

<br>

## Next steps

* [Submissions](/public-api/submissions.md) — read the answers collected by these forms.
* [Create a submission](/public-api/submissions/create-a-submission.md) — post data using the field keys above.
* [Errors](/public-api/errors.md) — the full list of error codes.
