Submitting a form

ezform has one public endpoint. It takes a flat map of fields, stores it, and queues it for delivery. There is no authentication: the form ID in the URL identifies the form, and the origin allowlist decides whether the submission is kept.

POST/f/{formId}

Submit a form

Posts one submission. The body is a flat object of field names to values, in either JSON or URL-encoded form.

Field names and values are yours; ezform does not reserve any. There is no _subject, no _next and no _replyto. A redirect is configured on the form, not passed in the body, and the notification’s Reply-To is inferred from whichever field looks like an email address.

Body

  • Name
    <field name>
    Type
    string | number | boolean
    Description

    Any field name you like, up to 100 characters. Numbers and booleans are accepted and coerced to strings, because everything is ultimately stored and rendered as text.

Limits

  • Name
    fields
    Type
    50
    Description

    At most 50 fields per submission, and at least one field present. A form-encoded body must also have at least one field that is not blank, because a browser posts every input it has and a form where nothing was filled in is not a lead. JSON is taken at its word: {"email": ""} is one field and is accepted.

  • Name
    key length
    Type
    100 characters
    Description

    Per field name.

  • Name
    value length
    Type
    10,000 characters
    Description

    Per field value.

  • Name
    body size
    Type
    100,000 bytes
    Description

    The whole request body, measured as UTF-8.

These are bounds that stop one request becoming a storage problem, not spam heuristics.

Request

POST
/f/{formId}
curl https://api.ezform.io/f/YOUR_FORM_ID \
  -H "content-type: application/json" \
  -H "origin: https://example.com" \
  -d '{"email":"someone@example.com","message":"Hello"}'
{ "ok": true }

Content types

Two encodings are supported, and which one you use changes the shape of the reply as well as the parsing.

  • Name
    application/json
    Type
    default
    Description

    What fetch sends. Anything unrecognised, including a request with no Content-Type at all, is also parsed as JSON, because a failed parse is a clearer error than a guess.

  • Name
    application/x-www-form-urlencoded
    Description

    What a plain form posts with no JavaScript. Repeated keys, which is what a checkbox group sends, are joined with a comma rather than overwriting each other.

  • Name
    multipart/form-data
    Type
    refused
    Description

    Deliberately not accepted. Browsers only send it when there are file inputs, and accepting it would mean silently dropping the files inside, which is worse than refusing the type.

curl https://api.ezform.io/f/YOUR_FORM_ID \
  -H "content-type: application/x-www-form-urlencoded" \
  -H "origin: https://example.com" \
  -d "email=someone@example.com&message=Hello"

What comes back

Three shapes, chosen by what the caller looks like it wants. Accept: application/json wins outright; failing that, JSON in means JSON out.

  • Name
    200
    Type
    application/json
    Description

    A body of {"ok": true}. Sent when the request was JSON, or when the form post asked for JSON in its Accept header.

  • Name
    303
    Type
    redirect
    Description

    A form post with no JavaScript, on a form that has a redirect path configured, from an allowed origin. The target resolves against the origin the request came from, so it is always somewhere the account authorised. A 303 rather than a 302, so the browser follows it with GET and a refresh cannot repost the form.

  • Name
    200
    Type
    text/html
    Description

    Any other form post: a built-in page saying the message was received.

Every one of those is a 2xx, and so is every failure. That is not an omission; see Responses and failure.

curl https://api.ezform.io/f/YOUR_FORM_ID \
  -H "content-type: application/x-www-form-urlencoded" \
  -H "accept: application/json" \
  -H "origin: https://example.com" \
  -d "email=someone@example.com&message=Hello"

Rate limits

There is one throttle, and it is global rather than per-form: 10 requests per second sustained, bursting to 20, shared across every form on the service. None of it is configurable, and per-form limits are not built yet.

In practice the origin allowlist is what stands between a leaked form ID and a flood, because a submission from an unlisted origin is discarded before it is written.