Errors
Every status code the platform returns, what causes it, and what to do about it.
The shape of an error
Chat, audio and every other model-shaped endpoint fail with the same envelope, checked directly against this deployment:
JSON
{
"error": {
"message": "human-readable, safe to show as-is",
"type": "invalid_request_error",
"param": "messages",
"code": "400"
}
}Two shapes, not one
PDF and the sandboxed interpreter are pass-through endpoints: the gateway forwards the underlying service's own response unchanged, and that service is a plain FastAPI app, so its errors look like
{"detail": "message"} instead. A 404 for a mistyped path is the same — {"detail": "Not Found"}. Check for error.message first and fall back to detail rather than assuming one shape everywhere.Reference
| Status | When | Do this |
|---|---|---|
| 400 Bad Request | Malformed JSON; a missing required field (messages); a model id that does not exist; for /v1/pdf, HTML with a remote reference. | Fix the request. Call GET /v1/models if the model id is in question — see Models. |
| 401 Unauthorized | No Authorization header; a key that does not exist or was revoked; or a key LiteLLM has blocked because your organisation's balance is exhausted or its monthly cap was reached. | Check the key, then check the balance in the console's Billing page — see Billing. |
| 404 Not Found | A path that does not exist. | Check the path against the endpoint shown on each doc page. |
| 405 Method Not Allowed | The right path, the wrong verb — e.g. GET on /v1/chat/completions. | Check the Allow header on the response for the accepted verbs. |
| 429 Too Many Requests | An IP address over its edge budget, or a key over its rpm/tpm — see Rate limits. | Read retry-after and back off with jitter. Safe to retry. |
| 500 Internal Server Error | A backend failed on your specific input — a corrupt upload, an edge case in audio decoding. | Retry once. If it repeats with the same input, that input is the cause — change it rather than retrying again. |
| 502 Bad Gateway | The router could not reach the backend a request was classified to, or that backend errored answering it. | Retry with backoff — see Rate limits for the same helper. |
403 is used by the console's own management API for role checks inside an organisation ("requires admin role") — it has not been observed from /v1 itself, which either accepts your key or answers 401.Reading one
curl -s https://api.console.larsa.larsima.com/v1/chat/completions \
-H "Authorization: Bearer $LARSA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "not-a-model", "messages": []}' \
| jq -r '.error.message // .detail'import os
import requests
resp = requests.post(
"https://api.console.larsa.larsima.com/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['LARSA_API_KEY']}"},
json={"model": "larsa-general", "messages": [{"role": "user", "content": "hi"}]},
)
if not resp.ok:
body = resp.json()
message = (body.get("error") or {}).get("message") or body.get("detail")
raise RuntimeError(f"{resp.status_code}: {message}")const res = await fetch("https://api.console.larsa.larsima.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LARSA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "larsa-general",
messages: [{ role: "user", content: "hi" }] }),
});
if (!res.ok) {
const body = await res.json();
throw new Error(`${res.status}: ${body.error?.message ?? body.detail}`);
}interface ErrorEnvelope {
error?: { message: string; type: string; param: string | null; code: string };
detail?: string;
}
const res = await fetch("https://api.console.larsa.larsima.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LARSA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "larsa-general",
messages: [{ role: "user", content: "hi" }] }),
});
if (!res.ok) {
const body = (await res.json()) as ErrorEnvelope;
throw new Error(`${res.status}: ${body.error?.message ?? body.detail}`);
}package main
import (
"encoding/json"
"fmt"
"net/http"
"strings"
)
type errorEnvelope struct {
Error *struct{ Message string `json:"message"` } `json:"error"`
Detail string `json:"detail"`
}
func readError(resp *http.Response) error {
var e errorEnvelope
json.NewDecoder(resp.Body).Decode(&e)
msg := e.Detail
if e.Error != nil {
msg = e.Error.Message
}
return fmt.Errorf("%d: %s", resp.StatusCode, strings.TrimSpace(msg))
}async fn check(resp: reqwest::Response) -> Result<reqwest::Response, String> {
if resp.status().is_success() {
return Ok(resp);
}
let status = resp.status();
let body: serde_json::Value = resp.json().await.unwrap_or_default();
let message = body["error"]["message"].as_str()
.or_else(|| body["detail"].as_str())
.unwrap_or("unknown error");
Err(format!("{}: {}", status, message))
}import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.*;
static void checkForError(HttpResponse<String> resp) throws Exception {
if (resp.statusCode() < 400) return;
JsonNode body = new ObjectMapper().readTree(resp.body());
JsonNode error = body.path("error").path("message");
String message = error.isMissingNode() ? body.path("detail").asText()
: error.asText();
throw new RuntimeException(resp.statusCode() + ": " + message);
}using System.Text.Json;
async Task EnsureOkAsync(HttpResponseMessage resp) {
if (resp.IsSuccessStatusCode) return;
var body = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement;
var message = body.TryGetProperty("error", out var err)
? err.GetProperty("message").GetString()
: body.GetProperty("detail").GetString();
throw new Exception($"{(int)resp.StatusCode}: {message}");
}<?php
function ensureOk(int $status, string $rawBody): void {
if ($status < 400) return;
$body = json_decode($rawBody, true);
$message = $body["error"]["message"] ?? $body["detail"] ?? "unknown error";
throw new RuntimeException("{$status}: {$message}");
}require "json"
def ensure_ok!(response)
return if response.code.to_i < 400
body = JSON.parse(response.body)
message = body.dig("error", "message") || body["detail"] || "unknown error"
raise "#{response.code}: #{message}"
end