Kasimir Docs DEEN To the app →

API: Errors, Status Codes & Limits

Complete reference of HTTP status codes, the OpenAI-compatible error shape, and the budget and size limits of the public Kasimir API.

The public Kasimir API is OpenAI-compatible: errors are returned in the same JSON shape as OpenAI, so off-the-shelf OpenAI SDKs surface them as a normal exception or error object. This chapter describes exactly which status codes the /api/v1/* endpoints throw, what they mean, how the error object is structured, and which hard limits (budget, payload sizes) apply — all derived directly from the source code.

Error shape

Every error from the OpenAI-compatible endpoints (POST /v1/chat/completions, POST /v1/embeddings, GET /v1/models) is translated into the OpenAI schema by server/utils/apiError.ts (toOpenaiError):

{
  "error": {
    "message": "Model \"foo\" not found",
    "type": "not_found_error",
    "code": null
  }
}

Field

Content

message

Plain text from the thrown createError's statusMessage; "Internal error" for unclassified errors.

type

Derived from the status code (see table below).

code

Always null (Kasimir issues no machine-readable error codes in Phase 1).

The type value is mapped strictly from the status code:

Status

type

401

authentication_error

402

insufficient_quota

403

permission_error

404

not_found_error

≥ 500

api_error

anything else (e.g. 400, 409)

invalid_request_error

ℹ️

The HTTP status code and the body's type are always consistent — toOpenaiError sets both from the same source (setResponseStatus + mapping).

⚠️

Streaming edge case: if an error occurs after the SSE stream has started (started = true in chat/completions.post.ts), it can no longer be reshaped into an OpenAI error object. The stream simply aborts. Treat an unexpectedly truncated stream as an error on the client.

Status codes in detail

The following table lists every status code thrown by the /api/v1/* surface, with the triggering code location and its meaning.

Status

type

When

Source

400

invalid_request_error

Body violates the Zod schema (model/messages/input missing or mistyped); missing router id; assistant with no model

*.post.ts (safeParse), assistants/[id]/run.post.ts

401

authentication_error

No Authorization: Bearer … header; unknown key; revoked key (revoked_at)

apiKeyAuth.ts

402

insufficient_quota

Company monthly budget reached/exceeded

apiUsage.ts (assertCompanyBudget)

403

permission_error

Key lacks the required scope (e.g. chat:write); model is non-EU and the company hasn't enabled non-EU

apiKeyAuth.ts, apiModelResolve.ts

404

not_found_error

Model slug unknown/inactive/wrong purpose; model blocked via disabled_model_slugs; assistant does not exist or belongs to another company

apiModelResolve.ts, assistants/[id]/run.post.ts

409

invalid_request_error

Key management only: name already taken (23505)

company/api-keys.post.ts

502

api_error

Upstream (LiteLLM) returns no success and no own status — fallback `

5xx (passed through)

api_error

Upstream error with its own status (res.status) — e.g. LiteLLM 500

chat/completions.post.ts, embeddings.post.ts

500

api_error

Any unclassified error (default in toOpenaiError)

apiError.ts

💡

402 does not mean "too expensive for this request", it means "company monthly budget used up". Spend sums both API and in-app chat usage for the current calendar month (RPC company_month_spend_eur).

401 – Authentication

authenticateApiKey (server/utils/apiKeyAuth.ts) throws 401 in three cases:

  • No bearer tokenmessage: "Missing bearer token"

  • Invalid key — hash not found in company_api_keysmessage: "Invalid API key"

  • Revoked keyrevoked_at is set — message: "API key revoked"

Keys have the format kasi_<32 chars> and are stored only as a SHA-256 hash; the plaintext is shown once at creation.

402 – Budget

assertCompanyBudget (server/utils/apiUsage.ts) checks before forwarding to the upstream:

  • companies.monthly_budget_eur is null or ≤ 0unlimited, no cap.

  • Otherwise the monthly spend is fetched via company_month_spend_eur. If used ≥ budget, a 402 is returned, e.g. Monthly budget of €100.00 reached (used €100.42).

ℹ️

Fail-open on infrastructure errors: if the budget lookup itself fails (DB glitch), the request is let through rather than blocked — only a genuine 402 (budget exceeded) is propagated. A flaky lookup therefore never locks a customer out.

403 – Permission

Two independent causes:

  • Missing scopeauthenticateApiKey(event, ['chat:write']) throws Missing scope: chat:write if the key lacks the required scope. GET /v1/models requires no special scope; chat/completions and embeddings require chat:write.

  • Non-EU gateresolveApiModel throws 403 when the model has eu_hosted = false and companies.allow_non_eu_models is not true: Model "…" is not enabled for your organization (non-EU model).

404 – Not found

resolveApiModel (chat/embeddings) and the assistant handler throw 404 when:

  • the slug does not exist in models, is inactive, or has a different purpose (chat vs embedding),

  • the slug is listed in companies.disabled_model_slugs (deliberately masked as "not found"),

  • an assistant (/v1/assistants/{id}/run) does not exist or belongs to a foreign company (tenant isolation),

  • the assistant's model does not exist/is inactive — Model "…" not found or inactive.

502 / passed-through 5xx – Upstream

If the LiteLLM layer does not respond with 2xx (or the body is missing for streaming), Kasimir throws res.status || 502 with Upstream error: <first 200 chars of the upstream response>. A 502 typically means the upstream returned no usable status at all.

Request-Lebenszyklus POST /api/v1/chat/completions Anfrage 1 Bearer-Auth Token prüfen 401 2 Scope-Check Berechtigung 403 3 Modell + EU Auflösen / Gate 404/403 4 Budget Firmen-Limit 402 5 LiteLLM Upstream-Call 502/5xx 200 OK · OpenAI-Body { "object":"chat.completion", "model":"chat1-gemma", "choices":[ … ], "usage":{ … } } 401 { "error": { message: "Ungültiger API-Schlüssel", type: "invalid_ request_error", code: null } } 403 { "error": { message: "Scope fehlt", type: "permission _error", code: null } } 404/403 { "error": { message: "Modell nicht verfügbar", type: "model_not _found", code: null } } 402 { "error": { message: "Budget erschöpft", type: "insufficient _quota", code: null } } 502/5xx { "error": { message: "Upstream nicht erreichbar", type: "api_error", code: null } } Legende Erfolgspfad (Gate bestanden) Fehler-Abzweig (Short-Circuit) Gate-Nummer · sequenzielle Prüfung
Request lifecycle: which gate produces which status code.

Limits

In Phase 1 there are no rate limits and no 429 responses — per-key spend caps and per-key rate limits are explicitly deferred to Phase 2. The only enforced "quantity" limit is the company monthly budget (see 402). Beyond that, payload schema bounds apply:

Limit

Value

Endpoint

Source

Monthly budget (company)

companies.monthly_budget_eur; null/≤0 = unlimited

chat, embeddings

assertCompanyBudget

messages (min)

at least 1 message

/v1/chat/completions

Zod .min(1)

Assistant messages

1 to 50 messages

/v1/assistants/{id}/run

Zod .min(1).max(50)

Assistant temperature

0 to 2

/v1/assistants/{id}/run

Zod .min(0).max(2)

Assistant max_tokens

1 to 32000 (integer)

/v1/assistants/{id}/run

Zod .min(1).max(32000)

Rate limit / 429

none (Phase 2)

Spec §Non-Goals

⚠️

For chat/completions and embeddings, temperature, max_tokens, tools, response_format etc. are not validated by Kasimir but passed verbatim to the upstream (.passthrough()). Invalid values therefore surface as a passed-through upstream error (e.g. 400/5xx), not a Kasimir 400.

Handling errors (recommendations)

Status

Action

400

Fix the request body; do not retry (deterministic).

401

Check/rotate the key; recreate revoked keys in the app.

402

Raise the budget in Administration → … or wait for the month rollover; retrying is pointless.

403

Check the key's scope or clarify the EU gate (allow_non_eu_models) — both changeable only by owner/admin.

404

Reconcile the model slug against GET /v1/models; verify the assistant ID/company.

429

Does not occur currently — no handling needed (arrives only in Phase 2).

502 / 5xx

Transient upstream error — retry with backoff.

Because the error shape is OpenAI-conformant, every OpenAI SDK catches these cases automatically — e.g. the Python SDK raises AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404) and APIStatusError (5xx). Read error.message for the specific reason.