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

FieldTypeDescription
error.messagestringHuman-readable explanation of the failure
error.typestringBroad error category
error.codestringMachine-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 Authorization header
  • 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

CodeMeaning
invalid_api_keyAPI key is missing, malformed, expired, or revoked
invalid_request_errorGeneral request formatting or payload issue
insufficient_balanceAccount has no credit for the requested paid model
model_not_foundUnknown, retired, or unsupported model ID
context_length_exceededPrompt plus requested output exceeds model context window
rate_limit_exceededRequest frequency exceeded current allowed rate
invalid_schemaStructured output or tool schema is invalid
tool_execution_failedYour application-side tool execution failed
upstream_unreachableSolRouter could not reach the selected provider
validation_errorRequest 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_tokens or max_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 / codeRetry?Notes
400NoFix request payload
401NoFix credentials
402NoTop up balance or change model
404NoFix endpoint or model ID
408YesUse backoff
409SometimesDepends on operation
422NoFix request semantics
429YesRetry with exponential backoff
500YesUsually safe to retry idempotent requests
502YesOften transient upstream failure
503YesUsually 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.code
  • error.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:

  1. before the stream starts
  2. 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 Authorization header
  • header missing Bearer prefix
  • 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-2xx statuses 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