Guides
Error handling
What an error looks like, every status you can get, and how to retry without making it worse.
#The shape of an error
Every error is JSON with one field, error, in words a person can read.
There are no numeric error codes to look up; the HTTP status says the kind of problem
and the text says the specific one.
{ "error": "Unknown or revoked key" }
#Statuses
| Status | Meaning | What to do |
|---|---|---|
200 | Fine | |
400 | A body we could not read, or a field that is not right | Fix the request. Do not retry as is |
401 | No key, a malformed key, or a revoked one | Check the header. Make a new key if this one was revoked |
402 | Over a plan allowance: pages, paid links, poplinks, or publishing | Upgrade, or remove something first |
403 | A read key asked to change something, or the plan has no API | Use an edit key made by the account owner |
404 | A page or slug the key cannot see | Check the slug against /api/v1/pages |
405 | Wrong method | Reads are GET; the scan and new things are POST; changes are PATCH; removing a link is DELETE |
409 | A risky field without "confirm": true (the body lists them in needs_confirm), a page with no live domain, or an address that is taken | Add confirm if you mean it, or fix what the message says |
429 | Over the limit | Wait the number of seconds in retry-after, then try again |
500 | Our fault | Retry with backoff; write to us if it keeps happening |
#Rate limits
Sixty requests an hour per key, and inside that, twenty scans an hour. A 429 carries
a retry-after header in seconds. There are no per-plan tiers; the limits
are the same on every paid plan.
HTTP/1.1 429 Too Many Requests
retry-after: 1840
content-type: application/json
{ "error": "Sixty requests an hour per key. Wait 1840 seconds." }
#Retrying
Honour retry-after on a 429. Back off on a 5xx. Never retry a 4xx other
than 429; the same request will fail the same way.
async function call(url, key, tries = 3) {
for (let i = 0; i < tries; i++) {
const r = await fetch(url, { headers: { Authorization: 'Bearer ' + key } });
if (r.status === 429) {
const wait = Number(r.headers.get('retry-after') || 60);
await new Promise((ok) => setTimeout(ok, wait * 1000));
continue;
}
if (r.status >= 500 && i < tries - 1) {
await new Promise((ok) => setTimeout(ok, 1000 * 2 ** i));
continue;
}
if (!r.ok) throw new Error((await r.json()).error);
return r.json();
}
}import time, requests
def call(url, key, tries=3):
for i in range(tries):
r = requests.get(url, headers={'Authorization': f'Bearer {key}'})
if r.status_code == 429:
time.sleep(int(r.headers.get('retry-after', 60)))
continue
if r.status_code >= 500 and i < tries - 1:
time.sleep(2 ** i)
continue
if not r.ok:
raise RuntimeError(r.json()['error'])
return r.json()
#Still stuck
Write to support@peekin.bio with the request you made, minus the key, and the response you got. A person reads it.