# Create Form (POST)

Create a hosted form. It is published immediately at `https://frms.click/{tid}`, and every submission becomes a message on your account — see [Forms](https://info.pocketalert.app/features/forms.md).

## Endpoint

```
POST https://api.pocketalert.app/v1/forms
```

## Authentication

> **Info: Required**
>
> Include one of these headers in your request:
> - `Token: <your-api-key>` — Get it from [API Keys](https://pocketalert.app/account/apikeys)
> - `Authorization: Bearer <jwt-token>` — From [Login](https://info.pocketalert.app/api/user/login.md)

## Request

### Headers

| Header | Required | Description |
|--------|:--------:|-------------|
| `Token` | ✅ | Your API key |
| `Authorization` | ✅ | `Bearer <jwt-token>` |

### Body Parameters

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `name` | string | ✅ | Internal name, up to 191 characters. Also the default push title |
| `fields` | array | ✅ | The form's fields, max 30. At least one must be something a visitor fills in |
| `settings` | object | ❌ | Title, texts and design. Every key has a default |
| `application_tid` | string | ❌ | Application the submissions belong to |
| `device_tid` | string | ❌ | Target device TID, or `all`. Omit for every device |
| `default_level` | int | ❌ | Priority level (`-2`..`2`) for submissions. See [Message priority levels](https://info.pocketalert.app/api/messages/create.md#priority-levels) |
| `push_title` | string | ❌ | Push title template with `{Field label}` tokens, up to 191 characters |
| `allowed_origins` | array | ❌ | Hosts allowed to embed and submit the form, max 10. Empty means anywhere |
| `max_responses` | int | ❌ | Close the form after this many responses. `0` (default) is unlimited |
| `closes_at` | string | ❌ | Closing time, RFC 3339. Stored in UTC |
| `is_active` | boolean | ❌ | `false` closes the form straight away. Defaults to `true` |

### Fields

Each entry in `fields`:

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `id` | string | ✅ | Unique within the form: letters, digits, `_` and `-`, up to 40 characters |
| `type` | string | ✅ | One of the types below |
| `label` | string | ✅ | Shown above the input, up to 200 characters (2000 for `paragraph`) |
| `placeholder` | string | ❌ | Text, textarea, email, phone, number, url and dropdown only |
| `required` | boolean | ❌ | Ignored by layout fields |
| `options` | array | ✅ for choice fields | Up to 50 entries for `dropdown`, `radio` and `checkboxes` |
| `param` | string | ✅ for `hidden` | URL parameter the value is read from, e.g. `utm_source` |

| Group | Types | Submitted value |
|-------|-------|-----------------|
| Text | `text`, `textarea`, `email`, `phone`, `number`, `url`, `date` | String. `text` is capped at 1000 characters, `textarea` at 5000 |
| Choice | `dropdown`, `radio` | One of `options` |
| Choice | `checkboxes` | Array of `options` |
| Choice | `checkbox` | Boolean, rendered as `Yes` / `No` |
| Rating | `rating` (1–5), `nps` (0–10) | Number |
| Layout | `heading`, `paragraph`, `divider` | Never submitted |
| Tracking | `hidden` | Taken from the page URL, not from the visitor |

Email, phone, number, URL and date values are validated on the server; the browser check is a convenience, not the guard.

### Settings

All keys are optional — anything you leave out keeps its default.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `title` | string | `""` | Heading above the form, up to 200 characters |
| `top_text` | string | `""` | Intro text, up to 2000 characters. Plain text; links become clickable |
| `bottom_text` | string | `""` | Text under the button, up to 2000 characters |
| `success_message` | string | `Thanks! Your response has been sent.` | Shown after a submission, up to 500 characters |
| `closed_message` | string | `This form is no longer accepting responses.` | Shown when the form is closed |
| `background_color` | string | `#f5f5f4` | Page background, `#rrggbb` |
| `card_color` | string | `#ffffff` | Form background |
| `text_color` | string | `#1c1917` | Text |
| `primary_color` | string | `#3f5cad` | Accent and, unless overridden, the button |
| `font_family` | string | `Inter` | A Google Font by name |
| `font_size` | int | `16` | 12–24 |
| `font_weight` | int | `400` | 300, 400, 500, 600 or 700 |
| `border_radius` | int | `10` | 0–48 |
| `hide_branding` | boolean | `false` | Hides the "Powered by Pocket Alert" link. Paid plans only — on the free plan the API resets it to `false` |
| `submit` | object | — | `{ "text": "Submit", "color": "", "size": "md" }`. `size` is `sm`, `md` or `lg`; an empty `color` follows `primary_color` |

### Push Title Template

`push_title` builds the notification title from the answers:

```
New lead: {Name} ({Email})
```

Tokens reference fields **by label**, case-insensitive, plus `{form}` for the form name. Tokens with no answer drop out; if nothing is left, the title falls back to `Form: {name}`. The template is never exposed on the public page.

### Allowed Origins

A form id travels in its public link, so by default anyone holding it can embed the form. `allowed_origins` closes that: only the listed hosts may frame the form, and a submission whose `Origin` (or `Referer`) is not on the list is rejected with `403`.

Write hosts, not URLs: `example.com`, `app.example.com`, `*.example.com` for every subdomain, `localhost:3000` while developing. `www.` is ignored.

### Example Request

**cURL**

```bash
curl -X POST "https://api.pocketalert.app/v1/forms" \
  -H "Token: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Contact form",
    "push_title": "New lead: {Name}",
    "fields": [
      { "id": "name", "type": "text", "label": "Name", "required": true },
      { "id": "email", "type": "email", "label": "Email", "required": true },
      { "id": "message", "type": "textarea", "label": "Message" },
      { "id": "src", "type": "hidden", "label": "Source", "param": "utm_source" }
    ],
    "settings": {
      "title": "Get in touch",
      "top_text": "We reply within one business day.",
      "primary_color": "#3f5cad"
    }
  }'
```

**JavaScript**

```javascript
const response = await fetch('https://api.pocketalert.app/v1/forms', {
  method: 'POST',
  headers: {
    'Token': 'your-api-key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Contact form',
    push_title: 'New lead: {Name}',
    fields: [
      { id: 'name', type: 'text', label: 'Name', required: true },
      { id: 'email', type: 'email', label: 'Email', required: true },
      { id: 'message', type: 'textarea', label: 'Message' }
    ],
    settings: { title: 'Get in touch' }
  })
});

const form = await response.json();
console.log('Form link:', `https://frms.click/${form.tid}`);
```

**Python**

```python
import requests

response = requests.post(
    'https://api.pocketalert.app/v1/forms',
    headers={'Token': 'your-api-key'},
    json={
        'name': 'Contact form',
        'push_title': 'New lead: {Name}',
        'fields': [
            {'id': 'name', 'type': 'text', 'label': 'Name', 'required': True},
            {'id': 'email', 'type': 'email', 'label': 'Email', 'required': True},
            {'id': 'message', 'type': 'textarea', 'label': 'Message'},
        ],
        'settings': {'title': 'Get in touch'},
    }
)

form = response.json()
print(f"Form link: https://frms.click/{form['tid']}")
```

**PHP**

```php
$form = Http::withHeaders([
    'Token' => 'your-api-key',
])->post('https://api.pocketalert.app/v1/forms', [
    'name' => 'Contact form',
    'push_title' => 'New lead: {Name}',
    'fields' => [
        ['id' => 'name', 'type' => 'text', 'label' => 'Name', 'required' => true],
        ['id' => 'email', 'type' => 'email', 'label' => 'Email', 'required' => true],
        ['id' => 'message', 'type' => 'textarea', 'label' => 'Message'],
    ],
    'settings' => ['title' => 'Get in touch'],
])->json();

echo 'Form link: https://frms.click/'.$form['tid'];
```

## Response

### Success Response

> **Tip: 201 Created**
>
> The form is live. Share `https://frms.click/{tid}`, or embed it — see [Forms](https://info.pocketalert.app/features/forms.md#embedding).

The body is the same shape as one entry from [Get All Forms](https://info.pocketalert.app/api/forms/get-forms.md#response).

```json
{
  "tid": "vml19aihrga216gt8apc5e3m9",
  "name": "Contact form",
  "fields": [
    { "id": "name", "type": "text", "label": "Name", "required": true },
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "message", "type": "textarea", "label": "Message" },
    { "id": "src", "type": "hidden", "label": "Source", "param": "utm_source" }
  ],
  "settings": {
    "title": "Get in touch",
    "top_text": "We reply within one business day.",
    "success_message": "Thanks! Your response has been sent.",
    "closed_message": "This form is no longer accepting responses.",
    "background_color": "#f5f5f4",
    "card_color": "#ffffff",
    "text_color": "#1c1917",
    "primary_color": "#3f5cad",
    "font_family": "Inter",
    "font_size": 16,
    "font_weight": 400,
    "border_radius": 10,
    "hide_branding": false,
    "submit": { "text": "Submit", "color": "", "size": "md" }
  },
  "application_tid": "",
  "device_tid": "",
  "default_level": null,
  "push_title": "New lead: {Name}",
  "allowed_origins": [],
  "max_responses": 0,
  "closes_at": null,
  "closed": false,
  "is_active": true,
  "submissions_count": 0,
  "last_submission_at": "",
  "created_at": "23.09.2026 11:40:02"
}
```

### Error Responses

| Status | Description |
|:------:|-------------|
| `400` | Validation error — the `error` field says which field or setting is wrong |
| `401` | Unauthorized — Invalid or missing token |
| `403` | You have reached the number of forms your plan allows |
| `500` | Failed to create form |
