Errors
Errors always have the same shape, so you can handle them in one place.
{
"error": {
"type": "rate_limit_exceeded",
"message": "You have exceeded 30 requests per minute on this key...",
"docs": "https://dev.rafiqai.app/docs/errors#rate_limit_exceeded",
"limit_per_minute": 30,
"retry_after_seconds": 42
}
}
Branch on error.type, never on the message — messages are written for humans and
will be improved over time.
| Status | Type | What to do |
|---|---|---|
| 400 | invalid_json | The body was not valid JSON. Check your serialisation and Content-Type. |
| 400 | missing_parameter | A required field is absent. error.parameter names it. |
| 400 | invalid_parameter | A field was the wrong shape or out of range. |
| 400 | parameter_too_long | Shorten it; error.max gives the limit. |
| 400 | range_too_large | Ask for fewer verses per call. |
| 401 | missing_api_key | Add the Authorization: Bearer header. |
| 401 | invalid_api_key | The key is wrong or revoked. Create a new one. |
| 403 | account_suspended | Contact us from the portal. |
| 404 | not_found | The thing you asked for does not exist — a surah/ayah out of range, or an unresolvable city. |
| 404 | unknown_endpoint | Check the path against this reference. |
| 429 | rate_limit_exceeded | Too many requests this minute. Back off and retry — see below. |
| 429 | quota_exceeded | Monthly quota spent. It resets on the 1st, or ask us to raise it. |
| 503 | service_unavailable | A dependency is down. Retry with backoff. |
| 503 | mode_unavailable | That mode is not enabled on this deployment. |
| 500 | internal_error | Our fault. It is not charged against your quota. Retry once; if it persists, tell us. |
Retrying properly
Retry 429, 500 and 503. Do not retry 4xx
errors caused by your own request — they will fail identically every time.
On a 429, honour the Retry-After header. Otherwise use exponential
backoff with jitter: a fleet of clients all retrying after exactly two seconds is a second outage
arriving on schedule.
async function callRafiq(body, attempt = 0) {
const res = await fetch("https://api.rafiqai.app/v1/chat", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.RAFIQ_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (res.status === 429 || res.status >= 500) {
if (attempt >= 4) throw new Error("Rafiq unavailable after retries");
const retryAfter = Number(res.headers.get("Retry-After")) || 0;
const backoff = retryAfter * 1000 || (2 ** attempt * 500 + Math.random() * 500);
await new Promise((r) => setTimeout(r, backoff));
return callRafiq(body, attempt + 1);
}
const json = await res.json();
if (!res.ok) throw new Error(json.error?.message ?? "Request failed");
return json;
}