Less boo. More booked. Halloween offer: 30 days free on any paid plan. Claim 30 days freeEvery new account gets one free mailbox for warmup and replies. No domain to buy. See how it works

Skip to content

Errors & Rate Limits

Reference5 min read3 sections

This page documents the two things that apply the same way across almost every endpoint in this API, so each reference page doesn't have to repeat them. Endpoint pages link back here rather than restating this.

Reference

#Error response shape

Almost every error response from the authenticated endpoints is JSON in this shape, with a matching HTTP status code. The public, unauthenticated endpoints (for example POST /public/deliverability-check and POST /contact) are an exception: they return a bare string such as { "error": "invalid_body" }.

json
{
  "error": {
    "code": "bad_request",
    "message": "invalid_body",
    "details": null
  }
}
FieldTypeDescription
error.codestringA stable, machine-readable error code, safe to branch on in your integration (see the table below).
error.messagestringA short human-readable reason. Sometimes a slug like invalid_body, sometimes a full sentence, treat it as debugging context, not something to parse.
error.detailsobject \| nullExtra structured context when relevant (e.g. quota numbers, validation field errors). Always present in the response, null when there's nothing to add.

Common error.code values and their status:

CodeStatusMeaning
bad_request400Malformed request body/query, often paired with field errors in details.
unauthorized401Missing, invalid, or revoked API key.
forbidden403Authenticated, but not allowed to access this resource.
account_suspended403The account is suspended. Returned on every request, including ones made with an API key.
channel_not_included403Your plan doesn't include the channel (email/LinkedIn/WhatsApp) this action needs.
agency_plan_required403This feature (API keys, webhooks) requires the Agency plan.
not_found404The resource doesn't exist, or doesn't belong to you.
conflict409The request conflicts with the resource's current state.
payment_required / quota_exhausted / plan_capacity / campaign_lead_limit / billing_locked402A plan limit or monthly allowance is used up (the code varies by endpoint), or the workspace's billing needs attention (billing_locked).
too_many_requests429Rate limit exceeded, see below.
service_unavailable503A feature or an upstream service is not available right now.
internal_error500Something went wrong on Warmerly's end.

Known inconsistency: an unhandled exception in a route (a bug, not an expected error) falls back to a bare { "error": "server_error" }, a string, not the object shape above, with status 500. This is rare (every documented error case in these docs uses the object shape) but if you're strictly parsing error.code, guard against error being a string.

There is currently no request ID or correlation ID field in error responses. If you need to reference a specific failed request when contacting support, include the timestamp and endpoint instead.

Reference

#Rate limits

Rate limiting happens at up to three layers, depending on the endpoint:

#1. Per-API-key limit (applies to every key)

Every API key has its own per-minute request limit, checked on every authenticated request regardless of endpoint. Default 60 requests/minute, configurable up to a cap of 120 requests/minute per key when you create it. Exceeding it returns:

json
{ "error": { "code": "too_many_requests", "message": "rate_limit_exceeded", "details": null } }

with HTTP status 429.

#2. Per-endpoint limits (a small number of endpoints)

Some endpoints layer an additional, tighter limit on top of the per-key limit, because their abuse case (scraping, credential stuffing) isn't covered by a generous per-minute number. Other endpoints, such as sign-in, contact forms and workspace invites, have limits of their own too. This table lists two that matter most to API users; where a limit applies to an endpoint covered in these docs, its page says so:

EndpointLimitKeyed by
GET /leads/search120 requests / 5 minutesAuthenticated user
POST /public/deliverability-check10 requests / 60 minutesIP address
POST /public/deliverability-check with check: "blocklist"the limit above, plus 400 requests / 60 minutes across all callersIP address, then site-wide

GET /leads/search returns the 429 shape above. POST /public/deliverability-check returns { "error": "too_many_requests" } with a Retry-After header in seconds.

#3. Unauthenticated request backstop

Requests that carry no session cookie and no X-Api-Key header (i.e. fully unauthenticated calls, such as POST /contact) are capped at 120 requests/minute per IP as a coarse flood backstop, independent of the limits above. A request that sends only an Authorization: Bearer header (the Android app) does not count as carrying credentials here, so it is counted against this limit too. The check runs in Warmerly's request middleware before the route handler, keeps its count in memory on each server instance, and returns the 429 shape with Retry-After: 60. The MCP endpoint and the mail add-on endpoints are exempt.

#What isn't separately rate-limited

Session-cookie (dashboard) requests from a logged-in user have no dedicated per-user rate limit beyond the unauthenticated-IP backstop above (which doesn't apply to them once logged in), the practical ceiling for browser-driven dashboard use is whatever each page's own endpoints enforce. If you're building an integration, use an API key rather than reusing a dashboard session, both for correctness and so you get a predictable, documented limit.

Reference

#Plan quotas vs. rate limits

Don't confuse a rate limit (how fast you can call an endpoint) with a plan quota (how much of a metered resource: verifications, lead exports, email-finder lookups, you can use in a calendar month). Quota exhaustion returns 402 (with error.code quota_exhausted or payment_required, depending on the endpoint), not 429. Monthly allowances reset on the 1st of the month, not on your subscription's renewal date and not on a rolling window, and they do not roll over. Each metered resource has its own usage endpoint (e.g. GET /verify/usage, GET /leads/quota), and GET /usage/quotas returns every one of them in a single call.

Was this page helpful?