API // v1

API
reference

One POST, a small JSON contract, and no surprises. The endpoint returns an opaque submission id on success and a dull, safe error otherwise: never a spam score, a recipient, or a provider detail.

Endpoint

One route, one method

mtform.co and api.maketheform.com are the same service. Endpoints already deployed on the longer host keep working; new snippets use the short one because it is the string you paste most.

POSTPublic
/f/{public_form_key}

The everyday endpoint, and the default for a new form. The key is a public address, safe in client-side HTML. Any origin may submit; the honeypot, scoring, and rate limits do the defending.

POSTBrowser-restricted
/f/{public_form_key}

The same URL with an Origin allow-list turned on for the form. A request from anywhere else, or with no Origin at all, is refused with origin_not_allowed before it is parsed.

Protected server-to-server mode, authenticated with a secret key instead of a public form key, is designed but not yet built. A form set to that mode is refused with unauthorized rather than falling back to public.

Wire format

Request and response

Send application/x-www-form-urlencoded, multipart/form-data, application/json. A new submission is a 201. An Idempotency-Key that replays an earlier one is a 200.

Request · cURL
curl -X POST https://mtform.co/f/your-form-key \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: contact-form-load-5f3c" \
  -d '{"name":"Ada","email":"ada@example.com","message":"Quote please"}'
200 / 201 · Success
HTTP/1.1 201 Created

{
  "ok": true,
  "submission_id": "sub_3Nk8Qz1aB7",
  "message": "Submission received"
}
4xx / 5xx · Error
HTTP/1.1 422 Unprocessable Entity

{
  "ok": false,
  "error": {
    "code": "validation_failed",
    "message": "Please check the highlighted fields.",
    "fields": { "email": "Enter a valid email address." }
  },
  "request_id": "req_9fQ2Lp"
}
Errors

Every code, and what causes it

One shape for all of them: { ok: false, error: { code, message, fields? }, request_id }. Branch on the code, show the message, log the request id.

Error codes returned by the submission endpoint
CodeHTTPPublic messageWhen
malformed_request400We could not read that request.The body could not be parsed as the declared content type.
unsupported_content_type415That content type is not supported.Content-Type is not one of the three supported types.
payload_too_large413That submission is too large.The body or a field exceeded the size limits below.
validation_failed422Please check the highlighted fields.One or more fields failed validation; see error.fields.
unauthorized401This endpoint requires a valid API key.The form is in protected mode, which the public endpoint does not serve.
origin_not_allowed403This form does not accept submissions from that address.The request Origin is not in the form’s allowed list.
form_disabled403This form is not currently accepting submissions.The form is paused and not accepting submissions.
form_not_found404This form does not exist.No form matches that key. Also returned for a rotated key, revealing nothing.
challenge_failed403We could not verify that submission. Please try again.The bot challenge (Turnstile) did not verify server-side.
rate_limited429Too many submissions. Please wait a moment and try again.Too many requests from this IP and form. Respect the Retry-After header.
quota_exceeded429This form has reached its submission limit.The form reached its monthly submission ceiling.
idempotency_conflict409That idempotency key was already used with a different submission.The Idempotency-Key was reused with a different payload.
service_unavailable503We could not accept that submission. Please try again.A transient failure accepting the submission. Safe to retry.
Limits

What the endpoint will accept

Conservative on purpose, and raised on request rather than by accident. Nothing here is silently truncated: an oversized request is refused so you find out at integration time, not months later from a support ticket.

Rate limits

Burst (per IP + form)
5 / 30s
Sustained (per IP + form)
30 / 10 min
Account ceiling (per form)
600 / 10 min

A 429 carries a Retry-After header. Windows are short and recover on their own, so one burst from a shared office IP does not lock out a building.

Payload limits

Max body (excl. uploads)
256 KB
Max fields
100
Max field name
128 bytes
Max field value
20 KB
Max total text
200 KB
Max JSON depth
3

Idempotency

Send an Idempotency-Key header to make retries safe. Replaying the same key returns the original result with a 200. Reusing it with a different payload returns 409, so a bug that reuses keys surfaces instead of overwriting data. Keys are honored for 24 hours.

Accept path

How a submission is processed

Ordered cheap to expensive, so abuse is rejected before it costs anything. Once the transaction commits, the submission exists and we are obligated to it. Nothing after the commit can turn it back into an error.
  1. 01Resolve the form by key
  2. 02Check the form is enabled and the workspace is active
  3. 03Rate limit, before quota, so an attacker cannot burn your allowance
  4. 04Origin policy, when the mode requires it
  5. 05Content type and size limits
  6. 06Parse, and split out the reserved control fields
  7. 07Honeypot
  8. 08Turnstile, verified server-side
  9. 09Field validation
  10. 10Content scoring
  11. 11Quota check
  12. 12Commit the submission and its outbox row in one transaction

Copy a working snippet and go.

The HTML, React, Next.js, and cURL integrations in the quickstart already implement everything on this page.