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"
}codeis stable: branch on it, never on the message.messagesays what went wrong, for people; its wording can change.retryableis whether the same call can work if you try again later.request_idnames the call. Quote it when you report a problem; a success carries it inmeta.request_id.
Codes
| Code | Status | Retry | What it means |
|---|---|---|---|
INVALID_PARAMS | 400 | No | A parameter is wrong; the message says which. |
UNAUTHORIZED | 401 | No | The x-api-key header is missing, or the key is wrong or revoked. |
INSUFFICIENT_CREDITS | 402 | No | The key's workspace is out of credits. See Credits. |
LOGIN_REQUIRED | 403 | No | Only a logged-in user can see it: private, age-restricted or members-only. |
NOT_FOUND | 404 | No | It doesn't exist, or was deleted. |
RATE_LIMITED | 429 | Yes | Too many calls: wait as long as the Retry-After header says. |
INTERNAL | 500 | No | Our bug. Quote the request_id when you report it. |
LOOKUP_FAILED | 502 | Yes | The request couldn't be completed. Try again later. |
TEMPORARILY_UNAVAILABLE | 503 | Yes | Briefly unavailable. Try again in a moment. |
EMPTY_PAGE | 503 | Yes | A 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.200with an emptydatalist: 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())