Errors
Every production integration needs a clear strategy for handling failures. SolRouter uses standard HTTP status codes and JSON error bodies so your application can respond consistently when a request is invalid, a model is unavailable, a balance is exhausted, or an upstream provider is temporarily failing.
This page explains the error format, common status codes, retry strategy, and operational debugging guidance.
Base URL
https://api.solrouter.io/ai
Error response format
When a request fails, the API returns a non-2xx HTTP status code and a JSON response body.
Typical error body
{
"error": {
"message": "insufficient balance — please top up to use paid models",
"type": "payment_required",
"code": "insufficient_balance"
}
}
Common fields
| Field | Type | Description |
|---|---|---|
error.message | string | Human-readable explanation of the failure |
error.type | string | Broad error category |
error.code | string | Machine-friendly error identifier |
Not every upstream provider uses exactly the same error vocabulary, but SolRouter normalizes failures into a consistent structure whenever possible.
HTTP status codes
400 Bad Request
The request body is malformed, incomplete, or contains unsupported fields.
Typical causes:
- missing
model - missing
messages - invalid JSON
- unsupported parameter shape
- invalid multimodal block format
Example:
{
"error": {
"message": "invalid request body",
"type": "invalid_request_error",
"code": "invalid_request_error"
}
}
401 Unauthorized
Authentication failed.
Typical causes:
- missing
Authorizationheader - malformed API key
- revoked API key
- invalid session token in protected UI flows
Example:
{
"error": {
"message": "authorization required",
"type": "authentication_error",
"code": "invalid_api_key"
}
}
402 Payment Required
The account does not have enough balance for the selected paid model.
Typical causes:
- zero or negative balance
- attempting to call a paid model without topping up
- free-tier-only workflows accidentally routed to paid models
Example:
{
"error": {
"message": "insufficient balance — please top up to use paid models",
"type": "payment_required",
"code": "insufficient_balance"
}
}
404 Not Found
The requested path or model could not be found.
Typical causes:
- wrong endpoint path
- typo in model ID
- retired or unavailable model
- route mismatch in your application
Example:
{
"error": {
"message": "model not found",
"type": "invalid_request_error",
"code": "model_not_found"
}
}
408 Request Timeout
The request took too long to complete.
Typical causes:
- upstream provider timeout
- network instability
- very large or slow generation request
409 Conflict
The request conflicts with current server state.
This is less common, but can happen in some API workflows involving concurrent updates or stateful backend actions.
422 Unprocessable Entity
The request body is syntactically valid JSON, but semantically invalid.
Typical causes:
- wrong field types
- invalid enum values
- malformed tool schema
- invalid structured output schema
Example:
{
"error": {
"message": "response_format.json_schema.schema must be a valid JSON Schema object",
"type": "validation_error",
"code": "invalid_schema"
}
}
429 Too Many Requests
The client is being rate limited.
Typical causes:
- too many requests in a short interval
- free model burst limits
- provider-side quota throttling
Example:
{
"error": {
"message": "rate limit exceeded",
"type": "rate_limit_error",
"code": "rate_limit_exceeded"
}
}
500 Internal Server Error
A server-side failure occurred before the request could complete.
Typical causes:
- internal proxy error
- logging or persistence failure
- malformed upstream response
- unexpected application exception
502 Bad Gateway
SolRouter could not successfully reach or interpret the upstream provider response.
Typical causes:
- upstream provider outage
- malformed upstream payload
- unreachable provider
- transient proxy or TLS issues
Example:
{
"error": {
"message": "upstream unreachable",
"type": "api_error",
"code": "upstream_unreachable"
}
}
503 Service Unavailable
The service or provider is temporarily unavailable.
Typical causes:
- maintenance window
- provider outage
- severe transient upstream instability
Common error codes
| Code | Meaning |
|---|---|
invalid_api_key | API key is missing, malformed, expired, or revoked |
invalid_request_error | General request formatting or payload issue |
insufficient_balance | Account has no credit for the requested paid model |
model_not_found | Unknown, retired, or unsupported model ID |
context_length_exceeded | Prompt plus requested output exceeds model context window |
rate_limit_exceeded | Request frequency exceeded current allowed rate |
invalid_schema | Structured output or tool schema is invalid |
tool_execution_failed | Your application-side tool execution failed |
upstream_unreachable | SolRouter could not reach the selected provider |
validation_error | Request field values are invalid despite valid JSON syntax |
Your client code should key behavior off status codes first, then use error.code for more precise handling.
Insufficient balance errors
If you request a paid model while your balance is depleted, the API returns 402 Payment Required.
Example
{
"error": {
"message": "insufficient balance — please top up to use paid models",
"type": "payment_required",
"code": "insufficient_balance"
}
}
How to handle it
- show a clear top-up prompt in your UI
- offer a fallback to a free model when appropriate
- prevent repeated retries until balance changes
- surface current account balance in your application settings or dashboard
Fallback strategy example
async function chatWithFallback(prompt: string) {
const primary = await fetch("https://api.solrouter.io/ai/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SOLROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: prompt }],
}),
});
if (primary.status === 402) {
const fallback = await fetch("https://api.solrouter.io/ai/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SOLROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "arcee-ai/trinity-mini:free",
messages: [{ role: "user", content: prompt }],
}),
});
return fallback.json();
}
return primary.json();
}
Context length errors
Every model has a maximum context window. If your prompt is too large, or if your prompt plus expected output exceeds that window, the request may fail with a context-length-related error.
Example
{
"error": {
"message": "This model's maximum context length is 128000 tokens. Your request used 134521 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded"
}
}
How to fix it
- trim older conversation history
- reduce document size
- lower
max_tokensormax_completion_tokens - switch to a larger-context model
- summarize previous turns instead of replaying them fully
- reduce tool or schema verbosity when possible
Example trimming strategy
type Message = {
role: "system" | "user" | "assistant" | "tool";
content: string;
};
function trimMessages(messages: Message[], maxMessages = 12): Message[] {
const systemMessages = messages.filter((m) => m.role === "system");
const rest = messages.filter((m) => m.role !== "system");
return [...systemMessages, ...rest.slice(-maxMessages)];
}
Retry strategy
Some failures are retryable. Others are not.
Retryable vs non-retryable
| Status / code | Retry? | Notes |
|---|---|---|
400 | No | Fix request payload |
401 | No | Fix credentials |
402 | No | Top up balance or change model |
404 | No | Fix endpoint or model ID |
408 | Yes | Use backoff |
409 | Sometimes | Depends on operation |
422 | No | Fix request semantics |
429 | Yes | Retry with exponential backoff |
500 | Yes | Usually safe to retry idempotent requests |
502 | Yes | Often transient upstream failure |
503 | Yes | Usually temporary outage |
Exponential backoff example
async function requestWithRetry(makeRequest: () => Promise<Response>, retries = 3) {
let attempt = 0;
while (true) {
const response = await makeRequest();
if (response.ok) {
return response;
}
const retryable =
response.status === 408 ||
response.status === 429 ||
response.status === 500 ||
response.status === 502 ||
response.status === 503;
if (!retryable) {
return response;
}
attempt += 1;
if (attempt > retries) {
return response;
}
const delayMs = 500 * 2 ** (attempt - 1);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
Python retry example
import time
import httpx
def request_with_retry(url: str, headers: dict, payload: dict, retries: int = 3):
attempt = 0
while True:
response = httpx.post(url, headers=headers, json=payload, timeout=60.0)
if response.status_code < 400:
return response
retryable = response.status_code in {408, 429, 500, 502, 503}
if not retryable:
return response
attempt += 1
if attempt > retries:
return response
delay = 0.5 * (2 ** (attempt - 1))
time.sleep(delay)
When not to retry
Do not blindly retry:
- malformed requests
- authentication failures
- insufficient balance errors
- schema validation errors
- model-not-found errors
Retries will not fix these problems and may just increase load and logs.
Debugging failed requests
When a request fails, the fastest way to diagnose it is to log enough context to reproduce it without logging secrets.
Recommended logging fields
- timestamp
- request ID if available
- model ID
- status code
error.codeerror.message- whether streaming was enabled
- approximate prompt size
- retry attempt number
- environment (
dev,staging,prod)
Safe logging example
function logApiError(status: number, body: unknown, model: string) {
console.error("SolRouter request failed", {
status,
model,
body,
timestamp: new Date().toISOString(),
});
}
Avoid logging
Do not log:
- raw API keys
- bearer tokens
- private user documents
- unredacted invoice or ID images
- full sensitive prompts in production unless necessary
If you need reproducibility, log structured metadata and optionally store redacted payloads in a secure internal system.
Parsing error responses safely
Your client should not assume the error body always matches one exact shape. Parse defensively.
TypeScript example
type ApiError = {
error?: {
message?: string;
type?: string;
code?: string;
};
};
async function parseError(response: Response): Promise<string> {
try {
const body = (await response.json()) as ApiError;
return body.error?.message ?? `Request failed with status ${response.status}`;
} catch {
return `Request failed with status ${response.status}`;
}
}
Python example
def parse_error(response):
try:
body = response.json()
return body.get("error", {}).get("message") or f"Request failed with status {response.status_code}"
except Exception:
return f"Request failed with status {response.status_code}"
Streaming-specific errors
Streaming requests can fail in two different phases:
- before the stream starts
- after the stream has already begun
Before the stream starts
You will receive a normal HTTP error response such as 401, 402, 429, or 502.
After the stream starts
The connection may:
- close early
- terminate unexpectedly
- stop before
[DONE] - end without final usage data
How to handle streaming failures
- check initial HTTP status before reading the stream
- treat early stream termination as retryable when appropriate
- reconstruct partial content carefully
- mark interrupted output as incomplete in your UI
- do not assume the last chunk always arrives
Example guard
const response = await fetch("https://api.solrouter.io/ai/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SOLROUTER_API_KEY}`,
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
body: JSON.stringify({
model: "openai/gpt-4o-mini",
stream: true,
messages: [{ role: "user", content: "Write a short summary." }],
}),
});
if (!response.ok) {
throw new Error(`Stream request failed: ${response.status}`);
}
if (!response.body) {
throw new Error("Missing response body");
}
For more detail, see Streaming.
Authentication failures
Authentication-related failures are almost always 401 Unauthorized.
Causes
- missing
Authorizationheader - header missing
Bearerprefix - key is revoked
- key is invalid
- key is truncated
- wrong environment variable loaded
Correct header format
Authorization: Bearer sr_your_api_key
Common mistake
Wrong:
Authorization: sr_your_api_key
Correct:
Authorization: Bearer sr_your_api_key
Quick verification checklist
- does the key start with
sr_? - is the value being loaded from the expected environment variable?
- are there leading/trailing spaces or newline characters?
- has the key been revoked in the account dashboard?
Operational best practices
Use idempotent retries
For generation requests, retrying a failed call is often acceptable, but be careful with workflows that trigger side effects through tool calling or your own application logic.
Separate user-facing and internal error messages
Show a clean, actionable message to users, but keep detailed diagnostic data in logs.
User-facing:
The request could not be completed right now. Please try again in a moment.
Internal log:
{
"status": 502,
"code": "upstream_unreachable",
"model": "anthropic/claude-sonnet-4",
"retry_attempt": 2
}
Add circuit breakers for persistent upstream failures
If a specific model is repeatedly failing, temporarily switch traffic to:
- a fallback model
- a free backup model
- a different provider family
Validate requests before sending
Prevent avoidable failures by validating:
- required fields
- structured output schema shape
- tool definition schema
- multimodal content structure
- maximum message sizes
Track error rates by model
A rising error rate for one model often means:
- provider degradation
- model retirement
- request pattern mismatch
- quota or routing issues
Production checklist
Before shipping your integration, make sure you:
- handle non-
2xxstatuses explicitly - parse error bodies defensively
- retry only retryable failures
- log status, code, and model safely
- show helpful user-facing messages
- guard against context overflow
- handle streaming interruptions
- validate schemas before sending
- support fallback models for high-availability paths
Minimal resilient request wrapper
type ChatSuccess = {
choices: Array<{
message: {
content: string | null;
};
}>;
};
type ChatFailure = {
error?: {
message?: string;
type?: string;
code?: string;
};
};
export async function safeChat(prompt: string) {
const response = await fetch("https://api.solrouter.io/ai/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SOLROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
}),
});
const body = (await response.json().catch(() => ({}))) as ChatSuccess & ChatFailure;
if (!response.ok) {
return {
ok: false,
status: response.status,
code: body.error?.code ?? "unknown_error",
message: body.error?.message ?? `Request failed with status ${response.status}`,
};
}
return {
ok: true,
content: body.choices[0]?.message?.content ?? "",
};
}
Next steps
- API Reference — full request and response schema details
- Streaming — handle incremental output and stream interruptions
- Tool Calling — understand tool execution failures and safe validation
- Structured Output — avoid schema and parsing issues
- Token Counting — prevent context length failures