All pages

Rate limits & errors

What to expect when you hit a limit, and how every error is shaped so your client can handle it without guessing.

Limits

Reads are capped at 120 requests per minute per key, counted across all of /v1 on a rolling window. Go over and you get a 429 with a Retry-After: 60 header. Design for that number; back off when you see it rather than retrying immediately.

Failed authentications are capped separately at 30 per minute per address, which also answers 429. That budget is spent by rejected keys, not by successful calls, and it is shared by everyone calling from the same address.

Reads never consume your article allowance. That meters articles your newsroom writes, and a pull costs nothing to run - which is why pulling the same article twice is not something you need to engineer around.

The cheapest way to stay well under the limit is not to re-fetch what you already have: keep the newest created_at you have stored and pass it as ?since= on the next run. See List articles.

The error envelope

Every error response is JSON, shaped the same way regardless of status code. That holds for framework-level failures too - a mistyped path or a wrong verb comes back in this shape, not as an HTML error page.

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded (120 requests/minute)"
  }
}

Branch on error.code, which is stable, rather than on the message, which is written for a human reading a log.

Status codes

StatusCodeMeaning
401invalid_keyMissing, invalid, or revoked API key.
404not_foundNo such edition or article on your account, a path segment that is not a UUID, or a /v1 path that does not exist.
405method_not_allowedWrong verb. Every /v1 endpoint is a GET.
422invalid_paramsA parameter did not parse: status other than draft or published, or a since that is not ISO-8601.
429rate_limitedOver a limit. Carries Retry-After: 60.
500errorSomething broke on our side.
501not_implementedA retired endpoint. Only /v1/exports answers this.
503unavailableThe API is misconfigured or the data store could not be reached. Retry.

There is no 403. Asking for something that belongs to another account returns 404, the same answer as something that does not exist, because a 403 would confirm it does.

Retired endpoints

The bulk export endpoints - /v1/exports and its job and download paths - are not part of this API. They answer 501 rather than 404, so a client written against the older contract gets a parseable answer instead of something that reads like a missing edition.

{
  "error": {
    "code": "not_implemented",
    "message": "Export is not available on this API. Export your content from the dashboard."
  }
}

A single archive of everything you own is a dashboard job: Settings → Account → Download everything. The four endpoints above already serve your editions, your articles and their images to your own code, which is what an integration actually needs.