Skip to content
Documentation for: Echo
HUuptools.io
Markdown

Errors and limits

Every error answer has the same JSON shape:

{"error":{"code":"validation","message":"Please check the highlighted fields.","fields":{"email":"Enter a valid email address."}}}
Key Meaning
code A stable, machine-readable code. Branch on this.
message A human-readable text in the language of Accept-Language (en or hu). It can change, so don’t parse it.
fields Only for errors about the request’s content: the problem per key, as human-readable text. Nested keys use dots (fields.rendelesek_szama, content.html, requests.0).

The same request with Accept-Language: hu:

{"error":{"code":"validation","message":"Ellenőrizd a megjelölt mezőket.","fields":{"email":"Adj meg érvényes e-mail-címet."}}}

A method the path doesn’t take answers 405 with the Allow header:

Terminal window
curl -i -X PATCH "$ECHO_API/groups" -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" -d '{"name":"x"}'
HTTP/1.1 405 Method Not Allowed
Allow: OPTIONS, GET, POST
Content-Type: application/json
{"error":{"code":"method_not_allowed","message":"This method is not allowed here."}}

A body that isn’t valid JSON, or isn’t one JSON object, answers 400:

Terminal window
curl -X POST "$ECHO_API/groups" -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" -d '{"name":'
{"error":{"code":"invalid_json","message":"The request body is not a valid JSON object."}}

A value of the wrong type is 422 validation with the key in fields, for example {"name":5} gives "fields":{"name":"Invalid value."}.

Status Meaning
200, 201, 202, 204 Done; created; accepted for background work; done without a body.
400 The body isn’t valid JSON or isn’t one JSON object (invalid_json).
401 No valid token (unauthenticated).
403 The token can’t do this (forbidden), or the address, the workspace or two-factor rules stop it.
404 Unknown ID or path (not_found).
405 The path exists but not with this method (method_not_allowed). The Allow header lists the methods it takes.
409 The request conflicts with the current state (see the codes below).
413 The body is too large (too_large).
415 The body isn’t application/json (unsupported_media_type).
422 The content was understood but refused (validation and others below).
429 Rate limit reached (rate_limited).
503 A list or count took too long (list_timeout). Narrow the filter and try again.
Code Status When
unauthenticated 401 The token is missing, unknown or revoked, or its creator left the workspace or was disabled.
ip_not_allowed 403 The token is limited to other IP addresses.
workspace_suspended 403 The workspace is suspended.
twofa_enforced 403 The workspace requires two-factor authentication and the token’s creator hasn’t set it up. The token works again once they do.
forbidden 403 The token’s access level or its creator’s role doesn’t allow this.
not_found 404 The ID doesn’t exist in this workspace, or the path is unknown.
method_not_allowed 405 The path doesn’t take this method; see the Allow header.
invalid_json 400 The body isn’t valid JSON or isn’t one JSON object.
validation 422 The request’s content is wrong; see fields.
unsupported_media_type 415 Send Content-Type: application/json.
too_large 413 Over 1 MB (10 MB for an import).
rate_limited 429 See Rate limits.
name_taken 409 A group or field with this name exists.
in_use 409 A segment, campaign or workflow uses the group or segment you want to delete; the message names them.
builtin_field 409 Built-in fields can’t be renamed or deleted.
webhook_limit 409 The workspace has 20 webhooks.
unknown_tags 422 Campaign content uses merge tags that don’t exist.
not_editable 409 The campaign isn’t in a state that allows this (not a draft, still sending, or content from another editor).
editor_chosen 409 A concurrent request chose another editor for the draft.
recently_sent 409 A campaign sent within 30 days can’t be deleted yet.
not_active 409 The workflow is a draft (pause) or isn’t active (add subscribers).
list_timeout 503 The list took too long.

Treat unknown codes by their HTTP status: new codes may be added.

Bucket Limit Counts
General 120 requests a minute per token Every authenticated request, including batch envelopes and each request in a batch.
Import 5 a minute per token POST /groups/{id}/import-subscribers, which also counts in the general bucket.

The limit is per calendar minute (it resets at the start of each minute), per token. Answers that count carry:

Header Meaning
X-RateLimit-Limit The bucket’s limit. On an import answer it shows the import bucket.
X-RateLimit-Remaining Requests left in this minute.
Retry-After On 429 only: seconds until the next minute.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 18
X-Ratelimit-Limit: 120
X-Ratelimit-Remaining: 0
{"error":{"code":"rate_limited","message":"Too many requests with this API token. Wait a minute and try again, or send several requests in one batch."}}

Answers that don’t count have no rate limit headers: a failed authentication, a request from a refused IP address, and GET /openapi.yaml.

Good practice: read X-RateLimit-Remaining, wait Retry-After seconds after a 429, use batch requests and the bulk import for many changes, and use separate tokens for separate systems (each token has its own limits).

What Limit
Request body 1 MB; bulk import 10 MB
Page size (limit) 1 to 100, default 25
Subscriber activity page 50 events
Bulk import 1 to 50,000 rows per request
Batch 1 to 50 requests
Add subscribers to a workflow 1 to 100 per request
Test emails 1 to 5 addresses per request; 50 per workspace in any 24 hours
Webhooks 20 per workspace; up to 100 events per batched delivery; 10 seconds to answer
API tokens 50 active per workspace; 20 IP addresses or ranges per token
Names Webhook and token names up to 100 characters; campaign name and subject up to 255