Errors & rate limits
Error envelope
Section titled “Error envelope”Application errors return a compact JSON object:
{ "error": { "code": "forbidden", "message": "scope `posts:write` not granted" } }Status codes
Section titled “Status codes”| 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.
Examples
Section titled “Examples”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" } }Rate limiting
Section titled “Rate limiting”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 RequestsRetry-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/importover a loop ofPOST /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
ETagasIf-None-Match. A304is the cheapest possible poll. - A request blocked by a key restriction (IP, region, time window) is refused
before the bucket is charged, so a
403costs no quota. - Need a higher limit? Ask — limits are per-key and can be raised.
Retrying safely
Section titled “Retrying safely”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.