Skip to main content

Errors, limits, retries

Error format​

Errors use the OpenAI format:

{
"error": {
"message": "No API key provided",
"type": "invalid_api_key",
"param": "api_key",
"code": "401"
}
}

Branch on the type field, not on the message text; the texts may change.

Status codes​

StatustypeMeaningWhat to do
400invalid_request_errorInvalid request, e.g. unknown model name or no model given.Fix the request; check the model name in the model list.
401invalid_api_keyKey missing, invalid or revoked.Check the Authorization: Bearer <key> header.
402payment_requiredBalance empty. Private account: with a link to top up in the console. Team member: "ask your admin".Top up or ask your admin.
403account_suspendedAccount suspended.Contact support.
403company_plan_pausedThe company's team plan is paused (applies to members).Ask the team owner.
403permission_deniedModel not enabled for your company, or a partner model without any top-up so far.Ask your admin to enable it, or top up.
403model_not_enabledModel not enabled for your account.Contact Open Hippo.
404not_found_errorThe path does not exist.Compare the path with the endpoint list.
429quota_exceededA budget is reached: key, account, member or company. Header Retry-After: 60.See Budgets & limits.
429rate_limit_exceededOnly on GET /v1/usage: more than 30 queries per minute and key.Query again after Retry-After.
503service_unavailableAuthentication service temporarily unavailable. Header Retry-After: 60.Retry after Retry-After.

A path that does not exist at all may also get a plain 404 response without JSON.

Examples​

Format examples with the gateway's message texts. Branch on the type field; the texts in message may change.

429, key budget with hard stop reached:

HTTP/2 429
Retry-After: 60
Content-Type: application/json

{
"error": {
"message": "API key spend limit reached",
"type": "quota_exceeded",
"param": "api_key",
"code": "429"
}
}

Other budgets return the same type with a different text, e.g. Monthly account budget reached (account), You have reached your monthly limit — ask your admin to raise it. (member) or Your company has reached its monthly limit. (company).

402, balance empty, private account:

{
"error": {
"message": "Insufficient credit balance. Top up at https://console.openhippo.io/billing",
"type": "payment_required",
"param": "api_key",
"code": "402"
}
}

402, balance empty, team member:

{
"error": {
"message": "Your company's balance is empty — ask your admin to top up.",
"type": "payment_required",
"param": "api_key",
"code": "402"
}
}

Check order​

Before every request the API checks in this order; the first hit determines the error:

  1. Account suspended → 403 account_suspended
  2. Team plan paused → 403 company_plan_paused
  3. Balance at €0 → 402 payment_required
  4. Key budget reached → 429 quota_exceeded
  5. Company budget reached → 429 quota_exceeded
  6. Member or account budget reached → 429 quota_exceeded

The result of the check is cached for up to 60 seconds. A budget can therefore be overshot slightly before the stop takes effect.

Rate limits​

Fixed RPM/TPM limits (requests or tokens per minute) are currently not documented. For load planning: support@openhippo.io. Exception: GET /v1/usage allows 30 queries per minute and key. The 429 quota_exceeded above are budgets, not rate limits.

Retries​

  • Retrying makes sense on 503: wait as long as Retry-After says.
  • 429 quota_exceeded is a budget, not a short-term rate limit. A retry only helps once the budget is raised or the new month begins.
  • Do not retry on 400, 401, 402, 403 and 404: the request will fail the same way.
import os, time
import openai
from openai import OpenAI

client = OpenAI(
base_url="https://api.openhippo.io/v1",
api_key=os.environ["OPENHIPPO_API_KEY"],
max_retries=0, # control retries yourself
)

def chat(messages, attempts=3):
for attempt in range(attempts):
try:
return client.chat.completions.create(model="openhippo/gpt-oss-120b", messages=messages)
except openai.APIStatusError as e:
if e.status_code != 503 or attempt == attempts - 1:
raise
time.sleep(int(e.response.headers.get("retry-after", "60")))

print(chat([{"role": "user", "content": "Hello!"}]).choices[0].message.content)