Limits & responses

Nothing here fails mysteriously. Every limit is printed in advance, every refusal says which limit you met and what to do about it, and hitting one never touches what you have already collected.

The deal

LimitFreeWhat it counts
Stored records10,000Every stored list signup and form submission, across every project. Lifetime, not monthly.
Endpoints10Lists and forms combined, across unlimited projects.
Per-IP rate10 / minPer address, per endpoint. Protects your allowance from one noisy source.
Endpoint daily cap1,000 / dayA circuit breaker per endpoint, editable in its settings. Bounds what a botnet can cost you in a day.
ExportunlimitedCSV export consults no plan and no limit, ever — including at the fence.

What counts as a record

A record is one stored row: a list signup or a stored form submission. There is no deduplication across lists — the same person on three lists is three records, because that is three things you can email separately.

  • Repeat signups don’t count. The same address twice on one list stores one record; the second submission answers 200 and costs nothing.
  • Forwarded form submissions don’t count. A form in forward mode stores nothing at all, so it consumes no allowance.
  • Spam you never see doesn’t count. Honeypot and time-trap catches are discarded before anything is written.
  • Deleting frees the allowance. Delete records and the count goes down, immediately and permanently.

What happens at the fence

At 10,000 records, collection pauses. That is the only thing that pauses. Reading, searching, exporting and deleting all keep working exactly as before, on the free plan, forever.

THE PROMISE

Your data is never held hostage. Export is free at any point, including — especially — when you are over the limit and deciding what to do about it.

Collect requests over the limit return account_over_record_limit with your current count, so the site posting to you can tell a visitor something true instead of showing a generic failure.

The shape of a refusal

Every refusal, at every surface, is a flat JSON object with an error code and a human message. Beyond those two, the fields vary by code — a rate limit carries retry_after, a record-limit refusal carries your usage. Most carry a docs_url, and the ones you can act on carry a dashboard_url.

Branch on error, show message. It is written to be read by the person who hit it.

Every response, printed

account_over_record_limit · 409

The account has reached its stored-record limit. Nothing is lost; collection resumes the moment records are deleted or the plan changes.

409 response
{
  "error": "account_over_record_limit",
  "message": "Your form isn't broken — this account has reached its free limit of 10,000 stored records. Nothing has been lost, and export is always free. Delete records or upgrade to resume collection.",
  "records_used": 10000,
  "records_limit": 10000,
  "dashboard_url": "https://mailcollectly.com/records",
  "upgrade_url": "https://mailcollectly.com/settings",
  "docs_url": "https://mailcollectly.com/docs/limits"
}

endpoint_limit_reached · 409

Returned when creating an eleventh endpoint on the free plan. Existing endpoints and their records are unaffected.

409 response
{
  "error": "endpoint_limit_reached",
  "message": "This account is using all 10 of its free endpoints. Delete an endpoint you no longer collect through, or upgrade — nothing already collected is affected either way.",
  "endpoints_used": 10,
  "endpoints_limit": 10,
  "dashboard_url": "https://mailcollectly.com/projects",
  "upgrade_url": "https://mailcollectly.com/settings",
  "docs_url": "https://mailcollectly.com/docs/limits"
}

endpoint_paused · 409

The endpoint was paused from its detail page. Resume it and the same request succeeds — nothing about the integration needs to change.

409 response
{
  "error": "endpoint_paused",
  "message": "This endpoint is paused, so nothing was stored. Existing records are untouched and export still works. Resume it from the dashboard to start collecting again.",
  "endpoint": "lst_a495ba",
  "dashboard_url": "https://mailcollectly.com/projects",
  "docs_url": "https://mailcollectly.com/docs/limits"
}

rate_limited · 429

Either the per-IP limit (10 per minute, per endpoint) or the endpoint’s daily cap. retry_after is seconds until that window rolls over — wait it out rather than retrying immediately.

429 response
{
  "error": "rate_limited",
  "message": "Too many submissions from this source in a short window. This protects your allowance from bots. Wait a moment and try again.",
  "retry_after": 37,
  "docs_url": "https://mailcollectly.com/docs/limits"
}

domain_not_allowed · 403

A browser posted from a host that isn’t on the endpoint’s allowed list. Hosts are matched exactly, never as substrings. Requests with no Origin or Referer — curl, server-to-server — are always allowed.

403 response
{
  "error": "domain_not_allowed",
  "message": "This endpoint doesn't accept submissions from that domain. Add the domain to the endpoint's allowed list in the dashboard, or post from a domain already on it.",
  "origin": "someone-elses-site.com",
  "dashboard_url": "https://mailcollectly.com/projects",
  "docs_url": "https://mailcollectly.com/docs/limits"
}

captcha_failed · 403

The endpoint has Turnstile switched on and the challenge didn’t pass. reason is one of missing, rejected, unconfigured or unavailable, and fix says whose problem it is — all four are explained here.

403 response
{
  "error": "captcha_failed",
  "message": "The captcha challenge didn't pass. Reload the page and try again — this usually means the challenge expired before the form was submitted.",
  "reason": "rejected",
  "fix": "retry",
  "docs_url": "https://mailcollectly.com/docs/spam-protection"
}

invalid_email · 422

The address failed syntax validation, or came from a disposable-email provider (reason: "disposable"). Lists require an address; forms only validate one if it’s sent.

422 response
{
  "error": "invalid_email",
  "message": "That doesn't look like a valid email address.",
  "reason": "syntax",
  "docs_url": "https://mailcollectly.com/docs/limits"
}

invalid_request · 400

The management API couldn’t act on the request as sent — a missing field, a value out of range, a body that isn’t JSON. field names the one to look at where there is a single culprit. On the collect surface the same code means the body couldn’t be parsed at all.

400 response
{
  "error": "invalid_request",
  "message": "kind must be \"list\" or \"form\".",
  "docs_url": "https://mailcollectly.com/docs/api",
  "field": "kind"
}

not_found · 404

No endpoint with that ID. Almost always a typo in a form action, or an endpoint that has since been deleted.

404 response
{
  "error": "not_found",
  "message": "No endpoint with that ID. Check the ID in your form action or fetch call against the one shown in the dashboard.",
  "endpoint": "lst_typo123",
  "dashboard_url": "https://mailcollectly.com/projects"
}

unauthorized · 401

Reserved for the management API and MCP server, which authenticate with a key. The collect endpoint never needs one — it is public by design.

401 response
{
  "error": "unauthorized",
  "message": "This request needs a valid API key. Create one in Settings → API, and send it as `Authorization: Bearer mc_live_…`.",
  "docs_url": "https://mailcollectly.com/docs/api"
}

Retrying

  • 429 — wait retry_after seconds. The windows are fixed, so retrying sooner just burns another attempt.
  • 409 — nothing to retry. Something has to change first: delete records, resume the endpoint, or upgrade.
  • 403 / 422 — the request itself needs to change. A captcha reason of unavailable is the exception: that one is transient and carries a retry_after.
NEXTSpam protectionThe nine checks every submission passes through, in order, and how to tune the ones that are yours to set.