Errors & Rate Limits
Error shape
Every failed request returns the same JSON shape:
{ "error": "email: Invalid email", "code": "VALIDATION_ERROR", "details": { "fieldErrors": { "email": ["Invalid email"] }, "formErrors": [] } }erroris always a human-readable string. Sendbox gives the real, specific reason (e.g. the exact SMTP error from a failed send) wherever it is knowable.codeis stable and machine-readable:VALIDATION_ERROR,BAD_REQUEST,UNAUTHORIZED,FORBIDDEN,NOT_FOUND,CONFLICT,PAYLOAD_TOO_LARGE,RATE_LIMITED,UPSTREAM_ERROR,INTERNAL_ERROR, and so on. Branch oncode, not on the text.detailsis present when there is more to say, e.g. the per-field messages of a validation error.
Some endpoints return extra fields next to the error (for example a failed test send also returns
smtpResponseCode).
Status codes
| Code | Meaning |
|---|---|
400 | Invalid request body/params |
401 | Missing, invalid, or expired credentials |
403 | Authenticated, but not allowed to do this (e.g. an API key hitting an account-management route, or a non-owner attempting an owner-only action) |
404 | Not found — including when a resource exists but belongs to a different workspace, so existence is never leaked across tenants |
409 | Conflict with current state (e.g. deleting a mailbox with real send history) |
413 | Request body too large |
429 | Rate limited, or an AI-credit limit reached |
502 | An upstream operation failed (e.g. an SMTP send attempt) |
Rate limits
- A request with a valid API key counts against that key’s limit: 300 requests/minute per key. Keys never share a limit, even when your integration calls from a shared IP (Zapier, Make, n8n).
- A signed-in dashboard session counts against 600 requests/minute per user.
- Anything else counts against 200 requests/minute per IP.
- Some routes (login/signup, mailbox test-send) have their own tighter limits on top.
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429
also has Retry-After (seconds) and code: "RATE_LIMITED". Over MCP, each tool call counts
against the same per-key limit.
Pagination
Most list endpoints return { ..., page, limit, total } and take page and limit query
parameters.
These four return a plain JSON array instead, and put the totals in headers:
GET /campaigns, GET /mailboxes, GET /domains, GET /crm/pipeline. They also take page and
limit.
| Header | Meaning |
|---|---|
X-Total-Count | Rows across all pages |
X-Page | The page returned (starts at 1) |
X-Limit | The page size used |
Without parameters you get the first page, which is large enough for a normal workspace
(500 campaigns, 5000 mailboxes, 2000 domains, 200 pipeline rows). Compare X-Total-Count with what
you received to know whether there is more.