Docs/Resources

Error Handling

Error response format, common error codes, and a retry-handling example.

The golden rule: branch on the HTTP status code, not on error text. The text is there to help humans and can change.

What an error looks like

{
  "error": {
    "message": "Rate limit exceeded. Please upgrade your plan.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded"
  }
}

Some errors include extra fields: upgradeRequired, requiredPlan and currentPlan when a feature needs a higher plan, or limitExceeded, currentCount and maxChats when you hit a quota. Treat them as helpful detail, not a stable contract.

Status codes

StatusCode / causeWhat to do
401Missing, malformed, disabled or expired API key ("API key required", "Invalid API key format", "API key is disabled", "API key has expired")Fix or replace the key. Expired keys are switched off on that request.
403Plan gate: upgradeRequiredThe feature needs a higher plan (e.g. GPU Tunnels need Pro). The body has requiredPlan and currentPlan. Note: GPU Tunnel / hosted-model management is dashboard-only, so an API-key caller gets 401 there, not 403.
429rate_limit_exceededMonthly quota or short burst limit. A quota 429 has no Retry-After and resets next month. See Plan Limits.
402INSUFFICIENT_CREDITSThe account is out of credits.
400INVALID_REQUESTThe request body is malformed or missing required fields.
400CONTEXT_LENGTH_EXCEEDEDThe message is longer than the model's context window.
404CONVERSATION_NOT_FOUNDThe RAG conversation ID doesn't exist.
503MODEL_NOT_AVAILABLEThe model is temporarily unavailable. Retry, or try another model.
500INTERNAL_ERRORSomething went wrong on our side. Retry.

Handle errors in code

Retry only 429 and 5xx, a limited number of times, with jitter, and respect Retry-After when it's there. Surface everything else.

JavaScript
const MAX_RETRIES = 4;

async function handleTarqaRequest(apiKey, requestData, attempt = 0) {
  const response = await fetch('https://api.tarqaai.com/api/v1/chat/completions', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(requestData),
  });
  if (response.ok) return response.json();

  const body = await response.json().catch(() => ({}));
  const detail = body.error?.message || body.error || `HTTP ${response.status}`;

  // Retryable: rate limits and temporary server errors
  if ((response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
    const retryAfter = Number(response.headers.get('Retry-After'));
    const backoff = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter * 1000
      : Math.min(2 ** attempt * 1000, 30000) + Math.random() * 1000; // jitter
    await new Promise(r => setTimeout(r, backoff));
    return handleTarqaRequest(apiKey, requestData, attempt + 1);
  }

  // Not retryable: tell the caller
  if (response.status === 401) throw new Error('Auth failed — check your API key.');
  if (response.status === 403) throw new Error(`Plan gate: ${detail}. Upgrade required.`);
  throw new Error(`API error ${response.status}: ${detail}`);
}

Safe retries (Idempotency-Key)

Add an Idempotency-Key header (any unique string, like a UUID) to POST /v1/chat/completions or POST /v1/ask. If you retry with the same key and the same body, you get the original response back instead of running the request again. Replays carry Idempotency-Replayed: true.

StatusMeaning
200Replayed: identical to the first response, plus Idempotency-Replayed: true.
409The first request with this key is still running. Wait a moment and retry.
422This key was already used with a different body. Use a new key.
Limits of idempotency

Keys are remembered for 24 hours; after that a repeat runs again. Streaming requests aren't covered: the header is ignored, so a retried stream re-runs tool calls and counts usage again, and tokens from a stream that dropped midway are still billed. Treat a dropped stream as possibly billed.

Errors inside a stream

After a stream starts (HTTP 200), an error arrives as a message in the stream, and its shape depends on the endpoint:

data: {"error":{"message":"upstream provider error","type":"api_error"}}

Check every message for these before treating it as text, and handle the connection simply dropping (no [DONE], no error message). Streams can't be resumed (there's no Last-Event-ID), so retry the whole request.

Checklist

  • Log the request ID: support will ask for it.
  • Check the status code first.
  • Validate your request before sending it.
  • Watch error rates in Monitor → Analytics.
  • Show users a friendly message, not the raw error.