Developer guideReference
Errors
Couryo API error format, types, HTTP statuses and every code with cause and fix.
Every error has the same shape. The code is stable: you can rely on it in your code. Each code always has the same type and the same HTTP status.
{
"error": {
"type": "invalid_request",
"code": "domain_not_verified",
"message": "The domain example.com is not verified in this project yet.",
"param": "from",
"doc_url": "https://couryo.com/docs/errors#domain_not_verified",
"request_id": "req_6f2a9c1e7b0k3m4n"
}
}| Field | What it is |
|---|---|
type |
the error category (table below) |
code |
the exact reason, stable |
message |
a human explanation, in English or Portuguese following Accept-Language (Portuguese by default; send Accept-Language: en) |
param |
the offending field, when there is one (for example to[1] or emails[2].from) |
doc_url |
a link to the explanation of this code |
request_id |
the request ID, same as the Request-Id header; send it to support if you need help |
Types#
type |
HTTP | When |
|---|---|---|
invalid_request |
400 or 422 | the request has a problem you can fix |
authentication |
401 | missing or invalid key |
permission |
403 | the key or the plan does not allow this |
not_found |
404 | the resource or route does not exist |
conflict |
409 | a conflict, such as a reused idempotency key |
rate_limited |
429 | too many calls in a short time |
tier_limit |
429 | a limit of your level, your plan or your spending limit |
api_error |
500 or 503 | a problem on our side; you can retry |
Codes#
code |
type |
HTTP | Summary |
|---|---|---|---|
missing_field |
invalid_request |
400 | a required field is missing |
invalid_field |
invalid_request |
400 | a field has an invalid value |
invalid_json |
invalid_request |
400 | the body is not JSON |
batch_too_large |
invalid_request |
400 | more than 100 emails in a batch |
invalid_cloudflare_token |
invalid_request |
400 | Cloudflare token refused |
cloudflare_zone_not_found |
invalid_request |
400 | the token cannot reach the domain's zone |
domain_not_verified |
invalid_request |
422 | the from domain is not verified |
recipient_suppressed |
invalid_request |
422 | every recipient is suppressed |
missing_api_key |
authentication |
401 | no key |
invalid_api_key |
authentication |
401 | invalid key |
not_authenticated |
authentication |
401 | dashboard call without a session |
insufficient_scope |
permission |
403 | the key's scope is not enough |
ip_not_allowed |
permission |
403 | IP outside the key's list |
sandbox_recipient |
permission |
403 | level 0 account sending outside the account |
domain_limit_reached |
permission |
403 | the account already has the plan's domains |
forbidden_origin |
permission |
403 | dashboard write from another site |
resource_not_found |
not_found |
404 | unknown ID |
route_not_found |
not_found |
404 | unknown route |
idempotency_conflict |
conflict |
409 | same idempotency key, different body |
idempotency_in_progress |
conflict |
409 | the same request is still running |
domain_exists |
conflict |
409 | domain already registered |
already_suppressed |
conflict |
409 | address already on the list |
last_project |
conflict |
409 | trying to delete the only project |
rate_limited |
rate_limited |
429 | too many calls per second |
daily_limit_reached |
tier_limit |
429 | daily limit reached |
monthly_limit_reached |
tier_limit |
429 | monthly quota reached |
spend_limit_reached |
tier_limit |
429 | overage reached your spending limit |
sending_paused |
tier_limit |
429 | sending paused, with the reason |
internal_error |
api_error |
500 | an error on our side |
billing_unavailable |
api_error |
503 | billing unavailable right now |
missing_field#
A required field is missing, named in param. To send, from, to, subject and one of html or text are required (with neither, param is html).
invalid_field#
The field named in param has a value we do not accept: a malformed address, more than 50 recipients across to, cc and bcc, a scheduled_at in the past or more than 30 days ahead, a webhook URL without https://. The message says what is wrong. In a batch, param includes the index: emails[2].to[0].
invalid_json#
The body is not valid JSON. Check Content-Type: application/json and your quotes.
batch_too_large#
The batch has more than 100 emails. Split it into smaller calls.
invalid_cloudflare_token#
Cloudflare did not accept the token. It needs the Zone > DNS > Edit permission for the domain. See Domains and DNS.
cloudflare_zone_not_found#
The token is valid but does not give access to the domain's zone on Cloudflare. Create the token including that zone.
domain_not_verified#
The from domain is not verified in the key's project. Verify it in Domains and DNS and try again. ck_test_ keys do not need a verified domain.
recipient_suppressed#
Every recipient is on the suppression list, because of a hard bounce, a complaint, an unsubscribe or a manual entry. param points to the first one (to[n]). If only some recipients are suppressed, the email goes to the others and the timeline gets a suppressed event for each skipped address.
missing_api_key#
The Authorization: Bearer ck_... header is missing. See Authentication.
invalid_api_key#
The key does not exist, was deleted or was copied only in part. Create a new one in the dashboard if needed.
not_authenticated#
A dashboard call (/api) without a session. Sign in again at app.couryo.com. It never happens on the public API (/v1).
insufficient_scope#
The key is not allowed to do this. For example, a send key trying to read emails, which needs read. The message says which scope is missing.
ip_not_allowed#
The key has an allowed IP list and the call came from another address. Adjust the list in the dashboard.
sandbox_recipient#
The account is on level 0 (sandbox), or the sender is teste@sandbox.couryo.com, and that path only sends to the account's own addresses. Verify a domain to move to level 1. See Limits and levels.
domain_limit_reached#
The account already has the number of domains its plan allows: Free 1, Pro 10, Scale 50, Enterprise unlimited (across all projects). Delete a domain you do not use or change plans. The message gives the current limit and the next plan's.
forbidden_origin#
A dashboard write came from another site (CSRF protection). It never happens on the public API.
resource_not_found#
The ID does not exist in this project, or belongs to another project. The message says which resource was not found.
route_not_found#
The path does not exist. Check the method and the URL, for example POST /v1/emails.
idempotency_conflict#
The same Idempotency-Key was used in the last 24 hours with a different body. Use a new key for a new request. See Idempotency.
idempotency_in_progress#
A request with the same Idempotency-Key is still being processed. Wait for Retry-After (1 second) and repeat: you get the first request's response.
domain_exists#
This domain is already registered on Couryo, in this or another account. If it is yours and lives in another account, delete it there first.
already_suppressed#
The address is already on the suppression list for that stream.
last_project#
The account needs at least one project. Create another one before deleting this one.
rate_limited#
Too many calls in a short time (the default is 10 per second per key). Wait the seconds in the Retry-After header and retry with the same Idempotency-Key. The RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers show your headroom.
daily_limit_reached#
You reached your trust level's daily limit, or the Free plan's (100 per day). The message gives the limit, how much was sent and what is missing to move up a level. The counter resets at midnight in your account's time zone.
monthly_limit_reached#
You reached the monthly quota: 3,000 on the Free plan and also on level 1 of any plan. Sending resumes next month (on a paid plan, next period). On level 1, the quota goes up when the account reaches level 2; on Free, when you subscribe to Pro or Scale.
spend_limit_reached#
This month's overage reached the spending limit you set under Billing. All sending stops (transactional and marketing) until you raise the limit or the period renews. Nothing is charged above the cap.
sending_paused#
Sending is paused. On a regular pause, marketing stops and transactional email keeps going with up to 30 per day; on a full pause, everything stops. The message brings the reason, the numbers and how to fix it, and the dashboard has a Request review button. See Limits and levels.
internal_error#
A problem on our side. The request was not processed. Retry with the same Idempotency-Key: there is no risk of a duplicate.
billing_unavailable#
Billing is unavailable right now (checkout or payment portal). Try again in a few minutes; sending email is not affected.