Forms
A form is a hosted endpoint you point ordinary HTML at. Its submission route is the one place in the public API with no API key — HTML forms post there directly, and it is rate-limited instead.
Every submission emits form.submitted, which is the usual trigger for an automation.
The form object
{
"id": "…",
"name": "Contact form",
"fields": [
{ "key": "email", "label": "Email", "type": "email", "required": true },
{ "key": "message", "label": "Message", "type": "textarea", "required": false }
],
"is_active": true,
"redirect_url": "https://example.com/thanks",
"submission_count": 42,
"last_submission_at": "…",
"created_at": "…"
}Create and update
POST /v1/forms
GET /v1/forms
GET /v1/forms/{id}
PATCH /v1/forms/{id}
DELETE /v1/forms/{id}| Field | Required | Notes |
|---|---|---|
name | yes | Up to 255 characters. |
fields | no | Up to 50. See below. |
redirect_url | no | Absolute http(s). An explicit null or empty string clears it. |
is_active | update only | false makes the submission endpoint reject. |
A PATCH must carry at least one of name, fields, is_active, redirect_url.
Fields
A field is { "key", "label", "type", "required" }, or a bare string used as the key. key is up to 100 characters and unique within the form (case-insensitively); label up to 200 and defaults to the key. type is one of text, email, tel, url, number, date, textarea, checkbox.
Declaring fields is optional
An empty fields list means the form captures whatever is posted. Declared fields drive the embed snippets and how values are labelled in stored submissions; they are not a schema the endpoint enforces. Markup already live on your site keeps working after you change them.
Deleting a form removes every submission it received.
The submission endpoint
POST /v1/forms/{id}/submissionsPublic — no API key. Rate-limited, and scoped by the form id in the path.
<form action="https://api.example.com/v1/forms/FORM_ID/submissions" method="post">
<input type="email" name="email" required>
<textarea name="message"></textarea>
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden>
<button type="submit">Send</button>
</form>What it accepts
Form encodings only — application/x-www-form-urlencoded or multipart/form-data, which is what a browser sends. JSON returns 415 notifyzr.form.error.unsupported_media_type.
What it answers
| The caller accepts | Response |
|---|---|
text/html (a browser) | 303 to the form's redirect_url, or to the JSON response when none is set |
| Anything else | 201 with the usual JSON envelope |
Limits
100 fields per submission, 200 characters per label, 5,000 per value, and 256 KB across all of them combined. Values are trimmed, stripped of control characters and length-capped on the way in; escaping happens at render time.
Reserved names
Field names starting with _ are reserved and never stored as content. One of them is load-bearing:
_gotcha is a spam trap
A bot that fills every input fills the hidden _gotcha too. When it arrives filled, the endpoint answers 201 as usual and stores nothing. Keep it in your markup and keep it hidden — and leave it out of any request you make yourself, or your test submission will vanish.
An inactive form's endpoint answers 404, the same as an unknown id.
Submissions
GET /v1/forms/{id}/submissionsPaginated (page, per_page, default 25, max 100).
{
"submissions": [{
"id": "…",
"data": [
{ "label": "email", "value": "someone@example.com" },
{ "label": "message", "value": "Hello" }
],
"remote_ip": "203.0.113.10",
"created_at": "…"
}],
"pagination": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 }
}data is an ordered list, not an object
Submissions keep the labels and the order the visitor's browser sent, and a list allows duplicate labels — which an object would silently collapse. Read it by iterating, not by key lookup.