All docs
3 min read Last updated:

Rate limits

Every form has a per-IP rate limit. The same IP submitting the same form too fast gets a 429 Too Many Requests response. Real users almost never hit it; bots hammering one form do.

Limits are enforced before validation, before captcha, before anything expensive. A blocked request is cheap.

Default limits

Two limits apply to every form, independent of plan:

Scope Submissions Window
Per IP, per form 20 60 seconds
Per team (all forms combined) 120 60 seconds

The per-IP limit is the polite default for a single submitter. The per-team ceiling is the safety net against a single leaked endpoint being abused from many IPs at once.

These defaults are sane for a typical contact or signup form. If you run a campaign that legitimately bursts above 20 submissions per minute from the same IP (rare), captcha tuning is usually the better lever than raising the rate limit - see Captcha.

What a 429 looks like

http
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json

{
  "error": "rate_limited",
  "message": "Too many submissions from this IP. Try again in 42 seconds.",
  "retry_after": 42
}

The Retry-After header is in seconds and matches the retry_after field in the JSON body. Clients should respect it - retrying earlier just resets the cooldown.

For browser-side submissions, the response includes CORS headers so your fetch handler can read the body. Display a friendly "you're going too fast" message rather than a generic error.

What doesn't count

  • Authenticated REST API calls (those use a separate per-token limit).
  • Webhook deliveries from Formspring to your endpoint (those are throttled by the queue).
  • Step-validation requests (e.g. validate-step previews) - only completed submissions count.

If you're consistently hitting the per-team ceiling on legitimate traffic, the bottleneck is volume across all your forms combined - increase per-form captcha strength or contact support before tuning anything else.

Handling 429 in your code

If you submit from JavaScript, parse the Retry-After header and back off:

js
async function submitForm(formId, data) {
  const res = await fetch(`https://formspring.io/f/${formId}`, {
    method: 'POST',
    body: data
  });

  if (res.status === 429) {
    const retryAfter = Number(res.headers.get('Retry-After') ?? 30);
    throw new RateLimitError(`Try again in ${retryAfter}s`, retryAfter);
  }

  return res.json();
}

For server-side submissions, treat 429 as a hard failure - don't loop. If you're hitting the limit from a server (where retries are cheap), you're hitting the wrong limit; raise the per-form cap or use the authenticated REST API instead.

What's next