Skip to content

Guide: Error Handling

Every Intentgine API error returns a consistent JSON shape:

{
"error": "error_code",
"message": "Human-readable description",
"code": 400
}

Validation errors include an additional details field with per-field messages:

{
"error": "validation_error",
"message": "Validation failed",
"code": 400,
"details": {
"query": "Query must be 500 characters or less."
}
}
CodeErrorMeaningAction
400bad_requestInvalid input or missing fieldsFix the request. Check message for details.
400validation_errorField value exceeds limits or is malformedFix the flagged fields. Check details for per-field messages.
401unauthorizedInvalid or missing tokenVerify your JWT is valid and not expired. Re-exchange your API key via POST /v1/auth if needed.
402payment_requiredRequest balance exhaustedPurchase an overage pack or upgrade your plan in the Console.
403forbiddenApp suspended or action not allowedCheck your App status in the Console.
404not_foundResource not found (bank, toolset, classification set)Verify the signature or ID exists and is assigned to your App.
413payload_too_largeRequest body exceeds size limitReduce payload size. See Handling Large Payloads.
429too_many_requestsRate limit exceededBack off and retry. Use Retry-After header.
500internal_errorServer errorRetry with backoff. Contact support if persistent.
502bad_gatewayUpstream provider errorRetry with backoff.
503service_unavailableService temporarily downRetry after the Retry-After header value (default 30s).

Rate limits are per-minute and proportional to your plan tier. When you hit the limit, the response includes headers to guide your retry:

{
"error": "too_many_requests",
"message": "Rate limit exceeded",
"code": 429
}

Headers:

  • Retry-After — seconds until the limit resets
  • X-RateLimit-Remaining — 0 when limited
  • X-RateLimit-Reset — Unix timestamp of next window
async function callWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status === 429 || res.status >= 500) {
if (attempt === maxRetries) throw new Error(`Failed after ${maxRetries} retries`);
const retryAfter = res.headers.get('Retry-After');
const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
await new Promise(r => setTimeout(r, delay));
continue;
}
return res;
}
}

When your request balance hits zero, all API calls return 402 until you add credits:

{
"error": "payment_required",
"message": "Monthly request limit reached. Upgrade your plan or purchase additional credits.",
"code": 402
}

To handle this proactively, monitor requests_remaining in every successful response’s metadata:

const data = await res.json();
if (data.metadata?.requests_remaining < 1000) {
alertOps('Intentgine credits running low', data.metadata.requests_remaining);
}

Validation errors use the validation_error code and include a details object with per-field messages:

{
"error": "validation_error",
"message": "Validation failed",
"code": 400,
"details": {
"query": "Query must be 500 characters or less. Keep it concise and focused. (650/500 characters). See https://docs.intentgine.dev/guides/large-payloads/ for strategies to handle large inputs."
}
}

See the Handling Large Payloads guide for strategies to reduce input size.

  • Retry on 429, 500, 502, 503 with exponential backoff
  • Don’t retry on 400, 401, 402, 403, 404 — these require a fix, not a retry
  • Monitor requests_remaining to avoid hitting 402 unexpectedly
  • Log error responses for debugging — include the error code and message
  • Set timeouts on your HTTP client (recommended: 30s for resolve, 60s for batch)