Skip to content

Errors & rate limits

Application errors return a compact JSON object:

{ "error": { "code": "forbidden", "message": "scope `posts:write` not granted" } }
Status error.code Meaning What to do
400 bad_request Malformed JSON, or title is empty. Fix the payload.
401 unauthorized Missing/non-Bearer Authorization, or unknown/revoked key. Send a valid ck_… key.
403 forbidden The key lacks the required scope. Use a key with the scope; message names it.
413 — Request body over 4 MiB. Split the content or shrink it.
415 — Missing/incorrect Content-Type. Send Content-Type: application/json.
422 unprocessable / — Body failed validation: missing blocks_json, wrong field type, or a key created before publishing was enabled. Fix the field; if the message says to re-create the key, do so.
429 rate_limited Over the key’s rate limit for that route. Back off; honor Retry-After.
404 not_found The resource does not exist on this site. Another tenant’s resource is indistinguishable from a missing one — deliberately. Check the id; do not retry.
5xx upstream Transient upstream/server error. Retry with backoff.

Route-specific meanings — 204 on media delete, 202 on media upload, 304 on a conditional read, 422 on an invalid leads filter — are documented on each reference page.

401:

{ "error": { "code": "unauthorized", "message": "missing Authorization header" } }

403:

{ "error": { "code": "forbidden", "message": "scope `posts:write` not granted" } }

422 (legacy key without publishing context):

{ "error": { "code": "unprocessable",
"message": "this api key was created before publishing was enabled; please re-create it in Configurações → Integrações API" } }

There is not one limit but four buckets, sized to what each route costs. A key pays every bucket that applies to the route it is calling, and the tightest one bites first.

Bucket Limit Applies to
Write / general 60 req/min per key Every route except the two public read routes
Public read 600 req/min per key GET /v1/posts, GET /v1/posts/{reference}
Public read per (key, IP) 60 req/min The same two routes, pk_ keys only
Leads 10 req/min per key GET /v1/leads, GET /v1/leads/{id} and GET /v1/export?resource=leads — layered on top of the general bucket

Reading is ten times cheaper than writing on purpose: one shared bucket would let a busy embedded grid eat the site owner’s publishing quota. The leads bucket is tighter than everything because leads are personal data — enough to page an inbox and pull a periodic export, not enough to scrape a contact base.

When you exceed a bucket you get 429 with a Retry-After header (seconds):

HTTP/1.1 429 Too Many Requests
Retry-After: 7
{ "error": { "code": "rate_limited", "message": "too many requests" } }

Client guidance:

  • Respect Retry-After — sleep for that many seconds before retrying.
  • For bulk work, prefer POST /v1/import over a loop of POST /v1/posts: one request instead of hundreds, and it is idempotent per line. If you do loop, pace requests to ≤ 1/second.
  • For reads, send back the ETag as If-None-Match. A 304 is the cheapest possible poll.
  • A request blocked by a key restriction (IP, region, time window) is refused before the bucket is charged, so a 403 costs no quota.
  • Need a higher limit? Ask — limits are per-key and can be raised.

429 and 5xx are safe to retry. Send an Idempotency-Key header on POST /v1/posts so a retry returns the original response instead of creating a duplicate post. See Idempotency. A request that errored caches nothing, so retrying after an error correctly re-runs it.