Errors

The error body, what each code means, and when to try again.

The error body

An error answers with its HTTP status and this body. Errors cost no credits.

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "rate limit: 60 requests per 60 seconds; try again in 12 seconds",
    "retryable": true
  },
  "request_id": "req_1f8c2d3e4a5b6c7d"
}
  • code is stable: branch on it, never on the message.
  • message says what went wrong, for people; its wording can change.
  • retryable is whether the same call can work if you try again later.
  • request_id names the call. Quote it when you report a problem; a success carries it in meta.request_id.

Codes

CodeStatusRetryWhat it means
INVALID_PARAMS400NoA parameter is wrong; the message says which.
UNAUTHORIZED401NoThe x-api-key header is missing, or the key is wrong or revoked.
INSUFFICIENT_CREDITS402NoThe key's workspace is out of credits. See Credits.
LOGIN_REQUIRED403NoOnly a logged-in user can see it: private, age-restricted or members-only.
NOT_FOUND404NoIt doesn't exist, or was deleted.
RATE_LIMITED429YesToo many calls: wait as long as the Retry-After header says.
INTERNAL500NoOur bug. Quote the request_id when you report it.
LOOKUP_FAILED502YesThe request couldn't be completed. Try again later.
TEMPORARILY_UNAVAILABLE503YesBriefly unavailable. Try again in a moment.
EMPTY_PAGE503YesA next page came back empty. Try again, or start again from the first page.

Missing, private or empty

Three answers that look alike mean different things:

  • 404 NOT_FOUND: the profile, post or channel doesn't exist, or was deleted.
  • 403 LOGIN_REQUIRED: it exists, but only logged-in users can see it. Only logged-out, public pages are read, so trying again won't change the answer.
  • 200 with an empty data list: it exists and is public, but has nothing to list, such as a search without matches.

Treat them as answers about the thing you asked for, not as failures to retry.

Trying again

Try again only when retryable is true. Wait the seconds in Retry-After when the answer has it; otherwise back off: about 1, 2, then 4 seconds, with some randomness so parallel workers spread out. Stop after a few tries.

import os
import random
import time

import requests


def get(path, params=None, tries=4):
    for attempt in range(tries):
        response = requests.get(
            os.environ["API_URL"] + path,
            params=params,
            headers={"x-api-key": os.environ["API_KEY"]},
            timeout=120,
        )
        body = response.json()
        if response.ok:
            return body
        error = body["error"]
        if not error["retryable"] or attempt == tries - 1:
            raise RuntimeError(f"{error['code']}: {error['message']} ({body['request_id']})")
        wait = response.headers.get("Retry-After")
        time.sleep(int(wait) if wait else 2**attempt + random.random())

On this page