Skip to content

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 ​

bash
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:

html
<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:

js
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:

bash
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 ​

bash
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 ​

LimitValue
Fields defined per form50
Fields captured per submission100
Field label200 characters
Field value5000 characters
Total submission size256 KB

Released under the Apache 2.0 License.