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
| Limit | Free | What it counts |
|---|---|---|
| Stored records | 10,000 | Every stored list signup and form submission, across every project. Lifetime, not monthly. |
| Endpoints | 10 | Lists and forms combined, across unlimited projects. |
| Per-IP rate | 10 / min | Per address, per endpoint. Protects your allowance from one noisy source. |
| Endpoint daily cap | 1,000 / day | A circuit breaker per endpoint, editable in its settings. Bounds what a botnet can cost you in a day. |
| Export | unlimited | CSV 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.
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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.