All documentation

Errors

Every failure carries a stable machine code. Branch on the code, not on the message.

The control-plane error envelope

Control-plane failures return a JSON body with a human message and a machine code:

402 Payment Required
{
  "message": "This entitlement has no remaining token quota.",
  "code": "insufficient_tokens"
}

Validation failures add an errors map keyed by field name:

422 Unprocessable Content
{
  "message": "Please check the highlighted fields and try again.",
  "code": "validation_failed",
  "errors": {
    "email": ["This email is already registered."]
  }
}

code is stable and safe to compare against. message is copy: it is written for a human reading a screen, it can be reworded at any time, and it may be localised. Never branch on it, and never regex it.

Control-plane codes

Code HTTP Meaning What to do
validation_failed422The request body failed validation. Per-field messages are in errors.Fix the input. Show the field messages rather than the summary.
unauthenticated401No credential was presented, or it is not valid.Sign in again, or check the API key you are sending.
session_expired401 / 419The browser session is no longer valid.Re-authenticate. This dashboard does this for you and returns you to the page you were on.
forbidden403Authenticated, but not permitted — often a key scoped to different models.Check the key scope. Do not change the base URL; that is a different failure.
account_suspended403The account is suspended.Contact SP Cambo. Retrying will not clear it.
not_found404The addressed record does not exist, or is not yours.Check the identifier.
rate_limit_exceeded Retryable 429A per-key or per-package rate limit was hit.Back off and retry. Honour Retry-After when it is present.
insufficient_tokens402No remaining token quota on any usable entitlement.Buy another package. Requests stop rather than becoming an overage bill.
insufficient_credits402No remaining credit balance.Top up. Retrying without a top-up returns the same error.
payment_pending Retryable 402The payment for this order has not been confirmed yet.Wait for verification, or ask SP Cambo to re-check. Re-checking is always safe.
payment_verification_failed402 / 422The payment could not be verified against the payment network.Do not re-pay on the strength of this alone. Check the order, then contact support.
server_error Retryable 5xxA fault on the SP Cambo side.Retry with backoff. If a request was metered and then failed, the reservation is released.

Classified by the client

These three are produced by SP Cambo's own clients — this dashboard included — when a failure arrives without a usable code. They are not sent by the server, but you will see them in the UI and it is useful to know what they mean.

Code Seen as Meaning What to do
network_unreachable Retryable The request never reached SP Cambo: offline, DNS, TLS or a blocked host.Check connectivity and the base URL. Nothing was metered.
endpoint_unavailable404 / 501The endpoint has not been published yet.Nothing to fix on your side. Pages that depend on one say so plainly.
unknown_erroranyA failure that did not carry a recognised code.Treat as unexpected. Report it if it persists.

Handling them

client.ts
const res = await fetch(url, { headers })

if (!res.ok) {
  const body = await res.json().catch(() => ({}))

  switch (body.code) {
    case 'insufficient_tokens':
    case 'insufficient_credits':
      return stopAndPromptForTopUp()      // never retry: the answer will not change
    case 'rate_limit_exceeded':
    case 'concurrency_limit_exceeded':
      return retryAfterBackoff(res.headers.get('retry-after'))
    case 'session_expired':
    case 'unauthenticated':
      return reauthenticate()
    default:
      throw new Error(body.message ?? 'Request failed')
  }
}

The important distinction is between retry and stop. A quota error will return the same answer however many times you send it, so retrying only burns your own rate limit. A 429 or a 5xx is worth retrying with backoff.

That example reads body.code, which is the control-plane shape. Inference failures put the code somewhere else — see below before reusing this against the gateway.

Errors from the inference gateway

Inference failures do not use the envelope above. They are shaped like the API you are calling, so your existing SDK error handling keeps working — which also means the SP Cambo code is in a different field depending on the surface. On /v1/messages and /v1/messages/count_tokens:

400 Bad Request
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "The model does not support this inference protocol.",
    "sp_cambo_code": "model_unavailable"
  }
}

On /v1/responses, /v1/chat/completions and the key and model read endpoints:

400 Bad Request
{
  "error": {
    "message": "The model does not support this inference protocol.",
    "type": "invalid_request_error",
    "code": "model_unavailable"
  }
}

Read the code from error.sp_cambo_code on the first and error.code on the second. The type field is the upstream classification your SDK expects and is coarser than the code — several distinct SP Cambo failures share one type, so branch on the code.

Gateway codes

These are additional to the codes above; rate_limit_exceeded, insufficient_tokens, insufficient_credits, account_suspended and server_error also arrive here, meaning the same thing.

Code HTTP Meaning What to do
invalid_api_key401No key was sent, or it is malformed, or SP Cambo does not recognise it.Check the key. An SP Cambo key starts with sk-.
conflicting_api_keys401Authorization and x-api-key were both sent, with different values.Send one credential. A leftover environment variable from another provider is the usual cause.
api_key_disabled403The key exists but is disabled. Also api_key_revoked and api_key_expired.Re-enable it, or use another. A revoked key never comes back.
model_not_allowed403The alias is outside this key's scope, or is no longer published.Call GET /v1/models to see what this key may use.
model_unavailable400The alias does not support the protocol you called it through.Use a protocol the alias supports. The catalogue lists them per alias.
invalid_model400The model field is missing or is not a valid alias.Send a public alias, not a provider model id.
unsupported_parameter400A parameter outside the set this surface accepts. The message names it.Remove it. It is rejected rather than dropped so it cannot silently not apply.
invalid_max_output_tokens400The maximum output field is not a positive integer.Send a positive integer, or omit it and take the default.
max_output_tokens_exceeded400More output was requested than the key permits.Lower the request. Your key's ceiling is on its card in the dashboard.
request_too_large413The body exceeds the service limit or your key's max_request_bytes.Split the request. It is refused before any upstream call, so it costs nothing.
concurrency_limit_exceeded Retryable 429Too many of your requests are in flight at once.Wait for one to finish — Retry-After is short. Bound your own parallelism.
rate_limiter_unavailable Retryable 503The limiter could not be reached, so the request was refused rather than admitted unchecked.Retry with backoff. Nothing was metered.
billing_unavailable Retryable 503The control plane could not be reached to reserve or settle the request.Retry with backoff. No quota is spent on a request that was never reserved.
upstream_rejected4xxThe provider refused the request itself. The status is passed through.Fix the request. The reservation is released, so it is not charged.
upstream_unavailable Retryable 503The provider was unavailable, over capacity or too slow to answer.Retry with backoff.
upstream_invalid_response Retryable 502The provider returned something that is not a valid response body.Retry. The reservation is reconciled rather than charged as used.
billing_settlement_pending Retryable 502The response carried no usage figures, so it could not be settled.Retry. The reservation is held for reconciliation, not billed as an estimate.
client_disconnected499Your client closed the connection before the response finished.Usually your own timeout or a cancelled request. The reservation is released.

Every 4xx in the table is refused before any upstream call and is therefore not metered — a misconfigured client cannot quietly drain a package. The exception is upstream_rejected, which is the provider refusing the request itself; there the reservation is released, so it is not charged either. Where a request reached the provider and then failed part-way, the reservation is released or held for reconciliation rather than settled, so you are never charged an estimate for a response you did not receive.

For errors that appear mid-stream rather than in the status line, see streaming .

What errors never contain

Error bodies never include a stack trace, an internal hostname, an upstream provider identifier or a credential. If you are seeing a framework error page instead of one of these envelopes, you are not talking to SP Cambo — check the base URL and any proxy in between.