Reference
Errors
Failures use the OpenAI error envelope and a small set of stable codes. Read the code rather than the message, because messages are written for humans and may be reworded while codes are part of the contract.
The envelope
{
"error": {
"message": "Access denied for this request.",
"type": "authentication_error",
"param": null,
"code": "access_denied"
}
}All four fields are always present. param is null when no single field is at fault, and names the offending one when there is:
{
"error": {
"message": "The \"temperature\" parameter is recognised but not supported by this API.",
"type": "invalid_request_error",
"param": "temperature",
"code": "unsupported_parameter"
}
}| Field | Purpose |
|---|---|
| message | Human readable. Fine to log, not something to branch on |
| type | Error family: invalid_request_error, authentication_error, rate_limit_error or server_error |
| param | The request field at fault, when one field is to blame, for example model, stream, or a nested messages[1].name. null otherwise |
| code | Stable machine code. This is what your code should switch on |
Every status and code
| Status | Code | Cause | What to do |
|---|---|---|---|
400 | invalid_request_error | The body is not valid for this API: a malformed field, a size limit, or no user message | Fix the request. Retrying will not help |
400 | streaming_not_supported | A truthy stream was sent, or any stream_options. "true" and 1 count as truthy; false and omitting it are both fine | Remove the flag. Responses arrive complete |
400 | model_not_found | model was missing, empty, not a string, or not wicarax-zenith | Send model: "wicarax-zenith". See Models |
400 | unsupported_parameter | A recognized OpenAI field this API does not implement, such as temperature, max_tokens, tools, response_format and the rest. param names it | Remove the field. It was refused, not ignored |
400 | unknown_parameter | A field this API does not recognise at all, including a misspelling like mesages | Check the spelling against the request reference |
401 | invalid_api_key | No Authorization header at all | Send the header. This is the only case that reports a missing credential |
403 | access_denied | The credential or the origin was refused, for any of several reasons. A publishable key on /v1/chat/completions is one of them | See the section below |
404 | The path does not exist. Unknown paths answer with the server default shape rather than the envelope above | Check the endpoint list | |
413 | The request body exceeded about 100 KB | Send less history, or shorten the message | |
429 | tenant_plan_rate_limit_exceeded | Your plan's rpm or rpd, counted per account across every agent and channel | Wait for Retry-After seconds, then retry |
429 | auth_probe_rate_limit_exceeded | The failed-credential budget for your network block. A working integration never touches it | Fix the credential. Retrying the same bad key spends more of the budget |
5xx | server_error | A failure on our side | Retry with exponential backoff |
Running out of monthly requests is not an error. The call returns 200 with a short unavailability message and zero usage. See Chat Completions.
Why 403 will not tell you more
A malformed key, an unknown key, a key of the wrong class, an inactive agent and a disallowed origin all answer with the same 403 access_denied. This is deliberate. A publishable key is visible in page source, and if the response distinguished those cases, anyone could take a scraped key and confirm whether it was real, or discover which agents exist.
The response intentionally does not identify which case applied. Check the key state, agent state, key type, and configured allowed origin before retrying.
| If you see 403 while integrating | Check first |
|---|---|
| From a browser | The site origin is registered exactly, including the scheme, subdomain and port |
| From a server or curl | You are using the secret key. A publishable key requires a browser origin |
On /v1/chat/completions with a wcx_pk_ key | Chat requires a secret key. A publishable key is valid on widget bootstrap, agent config and models, and on nothing that reaches the model |
| Everywhere, suddenly | The key was rotated or revoked, or the agent was deactivated |
| On a new agent | It has no installation yet, or the installation allows no origin |
| A 400, not a 403, on your first call | Different problem: you are authenticated. Check param; it is almost always a missing model or a sampling field the API refuses |
Two different 429s
Both carry Retry-After in seconds and have different codes, because the right response to each is different.
tenant_plan_rate_limit_exceededmeans requests across your account exceed its current per-minute plan allowance. Pace valid traffic before retrying.auth_probe_rate_limit_exceededmeans too many refused credentials came from the same network in a short time. Fix the credential or the origin first, because retrying with the same broken setup will keep failing.
If you were seeing 403 repeatedly while debugging and it turned into 429 auth_probe_rate_limit_exceeded, you have exhausted the failure budget rather than hit a plan limit. Correct the credential, wait for the window to pass, and try once.
Current plans do not configure a separate daily throughput cap. Monthly request quota is the longer-window consumption limit and is separate from these 429 responses.
Handling failures well
- Branch on code, never on message text.
- Respect
Retry-Afterinstead of retrying on a fixed interval. - Retry 5xx with exponential backoff. Never retry a 400, and never retry a 403 without changing something.
- Show the visitor a short, human message. The raw error text is for your logs.
- Keep failures out of the conversation transcript, or the assistant will read them back as its own words.
The official SDK maps all of this onto a single WicaraxError with status, type, code, param and retryAfter. See Official SDK.
One SDK error never appears on this page, because it never reaches the network: calling chat.send on a client built with a publishable key throws locally with code: "secret_key_required" and no status. It is a configuration mistake rather than a refusal, so isAuthError is deliberately false for it.
Branch on the code, respect Retry-After, and log the message. That handles every case on this page without a single string comparison against human-readable text.
The uniform 403 is deliberate and will not be made more specific because a detailed response could be used to confirm whether a scraped key is real.