Rate limits
How fast one visitor can post to a form, what an over-limit request gets back, and how to send higher volumes from a server.
The limits
Every form endpoint is rate limited, with nothing to switch on and nothing to configure. A browser post is counted per IP address and form, and has to be inside both the burst and the sustained window to be let through. A call made with an API key has a single window of its own:
| Window | Allows | Catches |
|---|---|---|
| Burst | 5 per 30 seconds | Rapid-fire scripts and a finger stuck on the submit button. |
| Sustained | 30 per 10 minutes | Slow, steady automation that stays under the burst limit. |
| API key | 300 per 1 minute | Server-to-server calls, counted per key and form instead of per IP. |
The limits are per form, so a busy visitor on one of your forms is never throttled on another. Every request counts, including ones that are later refused as spam or fail validation.
How the windows work
- A window starts with the first request and resets when it expires. It does not slide forward with each new request, so a caller who waits out the window starts clean.
- The rate limit is checked before your quota. A flood of requests is turned away without using a single unit of your monthly allowance.
- Nothing is blocked permanently. Windows are short on purpose, so one burst from a shared office or school network recovers on its own.
Handling a 429
An over-limit request gets 429 with the code rate_limited and a Retry-After header giving the seconds until the window that tripped resets. A browser doing a plain HTML post sees a short error page instead of JSON.
const form = document.querySelector('form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const response = await fetch('https://mtform.co/f/your-form-key', {
method: 'POST',
headers: { Accept: 'application/json' },
body: new FormData(form),
});
if (response.status === 429) {
const body = await response.json();
const wait = Number(response.headers.get('Retry-After') ?? 30);
if (body.error.code === 'rate_limited') {
alert('Sent too quickly. Try again in ' + wait + ' seconds.');
} else {
alert(body.error.message);
}
return;
}
if (response.ok) form.reset();
});{
"ok": false,
"error": {
"code": "rate_limited",
"message": "Too many submissions. Please wait a moment and try again."
},
"request_id": "0b7e4c1a-9f2d-4a8e-b3c5-6d1f2e8a7b90"
}Check the code, not only the status. quota_exceeded is also a 429, but it means the workspace has used its monthly allowance, carries no Retry-After, and waiting a few seconds will not fix it.
Retry-After value, and send an Idempotency-Key so a retry can never create a second submission. See limits and idempotency.Sending from your own server
If your backend forwards submissions, every one arrives from the same IP address and the burst window of 5 per 30 seconds will throttle it. Post to the authenticated endpoint with an API key instead: it is limited per key and form at 300 per 1 minute, and it skips the browser-only checks. Create a key under Settings in the dashboard, then follow server-side submissions.