Forms
A form is a hosted endpoint you point your own HTML at. There is nothing to host and no API key to ship to the browser: the submission endpoint is public and rate-limited, and every submission is stored against your organization and raised as a form.submitted event that automations and webhooks can act on.
Create a form
curl -X POST http://api.localhost/v1/forms \
-H "X-API-Key: $NOTIFYZR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Contact form",
"fields": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "email", "label": "Email", "type": "email", "required": true },
{ "key": "message", "label": "Message", "type": "textarea", "required": true }
]
}'fields is optional. A form without one stores whatever is posted, using each input's name as the label.
Field types
text, email, tel, url, number, date, textarea, checkbox. An unrecognised type is rejected rather than silently treated as text.
The type and required decide which element the embed snippet generates — they are not submission validation. The browser enforces required; the endpoint does not, because the markup lives on your site and cannot be re-rendered once you have pasted it. Editing a form's schema therefore never starts rejecting submissions from markup that is already live.
Embed it
The dashboard generates the markup for you under Forms → your form → Embed, with the fields you configured and a copy button. It looks like this:
<form action="http://api.localhost/v1/forms/{id}/submissions" method="post">
<label for="nz-email">Email</label>
<input type="email" id="nz-email" name="email" required />
<label for="nz-message">Message</label>
<textarea id="nz-message" name="message" rows="4" required></textarea>
<!-- Spam trap: a real person never fills this in. -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden>
<button type="submit">Send</button>
</form>The endpoint accepts application/x-www-form-urlencoded and multipart/form-data — exactly what a native <form> sends. It does not accept JSON. A scripted submission has to encode the body:
await fetch(ENDPOINT, {
method: 'POST',
body: new URLSearchParams(new FormData(formElement)),
})For uploads, only the client filename is recorded — file contents are never stored.
The spam trap
Any input whose name starts with _ is reserved and never stored as content. One name is special: _gotcha. If it arrives non-empty, the request is answered exactly as a real submission would be, but nothing is stored and no event is raised. Bots fill hidden inputs; people do not.
Keep it in the markup. Leave it out of scripted test calls, or your test will report success while storing nothing.
Where the visitor lands
By default the endpoint answers 201 with JSON, which a browser submitting a plain <form> will simply display. Set a thank-you page on the form to send them somewhere of your own instead:
curl -X PATCH http://api.localhost/v1/forms/{id} \
-H "X-API-Key: $NOTIFYZR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "redirect_url": "https://example.com/thanks" }'It must be an absolute http(s) URL. Send null or "" to clear it.
The endpoint decides which answer you get from your Accept header: a browser navigating a form submission asks for text/html and is redirected with a 303; fetch and cURL send the wildcard and keep receiving the JSON. So a scripted embed is unaffected by a thank-you page, and needs none — it never leaves the page.
Reading submissions
curl "http://api.localhost/v1/forms/{id}/submissions?page=1&per_page=25" \
-H "X-API-Key: $NOTIFYZR_API_KEY"Paged, 25 per page by default and 100 at most, newest first, alongside a pagination block. Submissions are also listed on the form's Submissions tab in the dashboard.
Each submission is an ordered list of { label, value } pairs rather than an object, so field order and repeated labels survive. Fields covered by the form's schema come first, in schema order and under their configured labels; anything submitted that the schema does not mention is appended unchanged.
Pausing a form
is_active: false makes the submission endpoint answer 404. Use it to close a form without deleting the submissions it already collected.
Limits
| Limit | Value |
|---|---|
| Fields defined per form | 50 |
| Fields captured per submission | 100 |
| Field label | 200 characters |
| Field value | 5000 characters |
| Total submission size | 256 KB |