ModelBeatbeta

Errors

The two ModelBeat error envelopes, every status the gateway returns, and how to use X-Request-Id.

ModelBeat returns two different error envelopes. Which one you get tells you where the failure happened, so handle both.

Every message below is the literal string the gateway emits, taken from the source rather than paraphrased.

Envelope 1: ModelBeat refused (error is a string)

Returned for failures ModelBeat raises itself, before any provider is called:

{ "error": "modelbeat: invalid or revoked api key" }

No provider was contacted and nothing was charged.

Envelope 2: the provider failed (error is an object)

OpenAI-compatible, so existing error handling mostly works unchanged:

{
  "error": {
    "message": "the model does not exist",
    "type": "invalid_request_error",
    "code": "model_not_found"
  },
  "status_code": 404
}

Other fields may appear alongside these; ignore any you do not recognise.

Telling them apart

Check the type of error: a string means ModelBeat refused, an object means a provider responded with an error. The two shapes never overlap, and a test in the gateway pins that property, so you can rely on it.

Every status ModelBeat itself returns

StatusMessageWhat happenedWhat to do
400modelbeat: <reason>Routing refused on your configuration. The reason comes from the router: no_eligible_candidate (nothing in the catalogue satisfies the request), pin_not_permitted (you named a model your routing policy forbids), policy_excludes_all (the policy leaves nothing eligible).Fix the request or the policy. Retrying unchanged will not help.
401modelbeat: invalid or revoked api keyKey missing, malformed, invalid, or revoked.See Authentication.
402modelbeat: insufficient balanceThe worst-case hold exceeds your prepaid balance, which is not necessarily the final cost.Top up, or lower max_tokens to shrink the hold. See Costs.
501modelbeat: this request shape is not supported; use /v1/chat/completionsYou called /v1/messages or /v1/responses.Use the OpenAI chat shape. See Limits.
503modelbeat: auth unavailableThe control plane could not verify your key.Retry with backoff. Not your bug.
503modelbeat: billing unavailableThe balance could not be reserved against.Retry with backoff.
503modelbeat: routing is temporarily unavailable; please retryThe router could not verify your routing constraints, including when the model catalogue is empty.Retry with backoff.
503modelbeat: routing selected an unconfigured providerRouting chose a provider the gateway holds no credentials for. Ours to fix.Report it with the request id.

Anything else carrying an object error came from the provider. Handle it as you would calling that provider directly.

400 and 503 are a deliberate split

Both can come out of routing, and the difference matters:

  • 400 means the router ran and decided your request cannot be served as configured. That is a statement about your request, so retrying it unchanged will fail again.
  • 503 means the router could not determine whether your constraints are satisfiable which is an outage on our side. ModelBeat fails closed rather than guessing a model, because substituting one that ignores your routing policy would be a policy bypass. Retry.

Why 503 rather than a best-effort attempt

If ModelBeat cannot verify your key, reserve against your balance, or evaluate your routing policy, it refuses. It will not serve traffic it cannot authenticate, account for, or route within your constraints. A 503 therefore means "try again". It never means "this succeeded but went unbilled".

X-Request-Id

Every response, success or failure, carries an X-Request-Id header:

X-Request-Id: req_01HXYZ...

Log it, and quote it when you contact us. It is the fastest path to the exact call in our records. Read it off the response headers, and off the error's response on a failure:

from openai import OpenAI, APIStatusError

client = OpenAI(api_key=..., base_url="https://api.modelbeat.ai/v1")

try:
    r = client.chat.completions.with_raw_response.create(model="auto", messages=[...])
    log.info("ok (request_id=%s)", r.headers.get("x-request-id"))
except APIStatusError as e:
    log.error(
        "modelbeat call failed: %s (request_id=%s)",
        e.message,
        e.response.headers.get("x-request-id"),
    )

You may also send your own X-Request-Id; it is echoed back rather than replaced, so you can correlate against your own tracing.

On this page