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.