Skip to main content
Guides

Errors & rate limits

One envelope, stable codes, honest status — build your retry logic once.

The error envelope

Every non-2xx response carries the same JSON shape:

json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Slow down and retry."
  }
}

code is machine-readable and stable across releases; message is for humans and may change. Branch on the code, log the message.

Error codes

CodeHTTPMeaning
unauthorized401Missing, invalid, or revoked API key.
forbidden403The key's account can't act on this resource.
not_found404The resource doesn't exist — or belongs to another account (never disclosed which).
invalid_request400Malformed JSON or missing/invalid fields; the message says which.
rate_limited429Too many requests — honor the Retry-After header and back off.
quota_exceeded429No AI credits left this period. Upgrade the plan or redeem a coupon.
internal_error500Something failed on our side; safe to retry with backoff.

Rate limits

  • Each API key has a token-bucket limit: a burst of ~20 requests, refilling at ~120 requests/minute sustained.
  • A per-IP bucket applies on top, so one noisy host can't starve your other integrations.
  • On 429, the Retry-After header says how many seconds to wait. Honor it, add jitter, and back off exponentially on repeats.
rate_limited and quota_exceeded both use HTTP 429 but mean different things: the first is about request pace (wait and retry), the second is about AI credits (retrying won't help — upgrade or redeem a coupon at curateone.ai → Settings).

Idempotency & retries

  • All GETs are safe to retry.
  • POST /sites reserves a credit per call — on a network timeout, poll your recent jobs before re-creating, or you may start two generations.
  • DELETE /jobs/{id} is idempotent — cancelling twice is harmless.
  • internal_error (500) is transient; retry with backoff.