Errors
All errors follow this shape:
{
"error": "Human-readable description of what went wrong"
}
Status codes
| Status | Meaning |
|---|---|
400 | Bad request — invalid body, missing required field, or failed validation |
401 | Unauthorized — missing or invalid token |
403 | Forbidden — authenticated, but not allowed to perform this action |
404 | Not found |
409 | Conflict — e.g. duplicate slug or email |
429 | Too many requests — rate limit exceeded |
500 | Internal server error |
Tier-upgrade 403
When a user tries to fetch a premium deal they can't see, you get a richer envelope so your UI can show an upgrade prompt:
{
"error": "This deal requires a premium subscription",
"upgrade_required": true
}
Handling errors
Treat anything in the 4xx range as a fixable client problem (bad input, expired token, missing record). Treat 5xx as transient — retry with exponential backoff up to ~3 attempts.
async function callApi(path, options) {
const res = await fetch(`https://perks.founderpass.com${path}`, options);
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `HTTP ${res.status}`);
}
return res.json();
}
:::warning Don't retry 4xx
A 4xx response means the request was understood and rejected. Retrying the same request will return the same error. Fix the input first.
:::