Error Reference
Every error InferKit returns has three parts:
{
"error": {
"code": "rate_limited", // stable, machine-readable cause
"message": "Rate limit exceeded", // human-readable
"request_id": "9f3c12a8-…-…" // correlation id ("reference")
}
}
code— branch on this in your integration. It is stable; messages may change.request_id— the reference number. It appears in the end-user widget (Ref: …), in this error body, and on every server log line for that request. Quote it to InferKit support and we can pin the exact failure.
In the SDK, your onError callback (and the 'error' event) receive the normalized
object { code, message, request_id, reference }. reference is the request_id when the
failure reached the server, or a generated c-… id for purely client-side failures
(e.g. WebGPU unavailable, network down before any request).
Who sees what
- End user (visitor): a friendly message +
Ref: <reference>. Never codes/internals. - Developer: the full
{ code, message, request_id, reference }— handle programmatically. - InferKit/support:
request_id→ exact log line (logscode+ full context).
Code catalog
| HTTP | code | Cause | Typical action |
|---|---|---|---|
| 400 | bad_request | malformed request | fix the request |
| 401 | invalid_api_key | missing/bad key | check the publishable key |
| 401 | invalid_token | dashboard session expired | re-authenticate |
| 401 | invalid_credentials | wrong email/password | — |
| 401 | bot_challenge_required | needs a Turnstile session | SDK handles automatically |
| 402 | upgrade_required | remote inference on Free tier | upgrade / stay local |
| 402 | quota_exceeded | monthly token cap reached | upgrade or wait for reset |
| 403 | forbidden | your org role lacks this capability | ask an owner/admin |
| 403 | email_not_verified | account email not yet verified | verify via the emailed link |
| 403 | key_revoked | key was revoked | issue a new key |
| 403 | key_suspended | auto-suspended for abuse | reactivate in dashboard |
| 403 | key_expired | rotated token past its grace | use the current key |
| 403 | origin_not_allowed | Origin not in domain allowlist | add the domain |
| 403 | ip_not_allowed | secret-key IP not allowlisted | add the IP |
| 403 | direct_origin_blocked | bypassed the edge (origin lock) | call via the public hostname |
| 403 | challenge_failed | Turnstile verification failed | retry the challenge |
| 404 | not_found | no such route/resource | check the path |
| 409 | email_in_use | signup email exists | log in instead |
| 429 | rate_limited | RPM / per-IP limit | back off (Retry-After) |
| 503 | bot_protection_unconfigured | Turnstile not set up server-side | InferKit config |
| 503 | billing_unconfigured | Stripe not set up server-side | InferKit config |
| 503 | email_unconfigured | email provider not set up server-side | InferKit config |
| 503 | provider_unavailable | upstream model error | retry; report request_id |
| 500 | internal_error | unexpected | report request_id |
5xx responses return a generic message (details stay in logs, keyed by
request_id). 4xx responses return a specific, safe-to-display message.
Handling examples (SDK)
InferKit.init({
apiKey: 'ik_pub_live_…',
onError: (e) => {
switch (e.code) {
case 'upgrade_required':
case 'quota_exceeded':
showUpgradePrompt(); break;
case 'rate_limited':
// back off and retry later
break;
default:
console.error(`InferKit error [${e.code}] ref=${e.reference}`, e.message);
}
},
});
Keep this catalog in sync with packages/api/src/lib/errors.js (ERROR_CODES) and the
SDK normalization in packages/sdk/src/util/errors.js.