aisplice

When something goes wrong

Look up an error

#Paste the error

Read errors in one order: HTTP status, then error.type, then error.code. The status is always meaningful; type tells you who rejected the call; code is stable only for errors the gateway raised itself.

#How to branch

  1. 1

    Switch on the status

    402 means top up, 403 means key scope, 429 means wait, 5xx means retry. This layer never lies and never changes.

  2. 2

    Check error.type

    xiaoxiang_error is ours. Anything else — openai_error, claude_error, gemini_error — is the vendor’s own body, forwarded unchanged.

  3. 3

    Then read error.code

    Only branch on code for xiaoxiang_error. Upstream codes belong to the vendor and can change without notice.

Not every failure has a JSON body

Rate-limit rejections and oversized bodies abort with a bare status line. A parser that assumes response.json() always succeeds will throw on the error path instead of handling it.
Error envelopes

Five shapes reach your client. Handle all five — the last three are the ones integrations usually miss.

Gateway error

The gateway itself rejected the call — auth, quota, routing, request shape.

http
{
  "error": {
    "message": "insufficient account balance, please top up to continue (request id: 20260910...)",
    "type": "xiaoxiang_error",
    "code": "insufficient_user_quota"
  }
}

Upstream error, passed through

The vendor answered with its own error. The body is forwarded unchanged, so type and code are the vendor’s.

http
{
  "error": {
    "message": "This model's maximum context length is 128000 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded"
  }
}

Guard rejection

Idempotency and a few other pre-relay guards answer before the relay envelope exists. Flat shape, uppercase code.

http
{
  "code": "IDEMPOTENCY_IN_PROGRESS",
  "error": "a request with this Idempotency-Key is still processing"
}

Async task rejection

Every task endpoint — video, image task, music, 3D — reports failures in its own flat shape. There is no nested error object here, so a parser written against /v1/chat/completions reads null.

http
{
  "code": "task_not_exist",
  "message": "task does not exist",
  "data": null
}

Status only, empty body

Rate-limit rejections and oversized request bodies abort with a bare status.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0
Response headers

Log the request id on every response, not just failures. It is the identifier support traces a call by, and it is the difference between a five-minute answer and a day of guessing.

HeaderSent whenWhat to do with it
X-Oneapi-Request-IdEvery response, success or error.Log it beside your own trace id. Support answers tickets from this value, and the same id is appended to gateway error messages.
Retry-AfterOn 429, and on 409 while an Idempotency-Key is still processing.Wait at least this many seconds. It is the only backoff hint the gateway sends.
X-Idempotent-ReplayedA stored response is replayed for a repeated Idempotency-Key.true means no new work ran and nothing was charged again — treat the body as the original result.
X-Idempotency-SealedThe original response cannot be replayed safely (for example a stream or an oversized body).Stop retrying that key. Reconcile from the task endpoint or your own records instead.
log-the-request-id.ts
const response = await fetch(url, init);
const requestId = response.headers.get("X-Oneapi-Request-Id");

if (!response.ok) {
  // Bodies are absent on 429 and 413 — never assume JSON here.
  const detail = await response.text();
  logger.error({ status: response.status, requestId, detail });
  throw new GatewayError(response.status, requestId, detail);
}

#Error-code table

Errors the gateway raises itself, always with type "xiaoxiang_error". Two rows share status 429 and mean opposite things — read the code before you back off.

HTTPCodeMeaningWhat to doRetry?
401noneMissing, malformed, deleted or disabled bearer token. This layer sets no code — match on the status.Check the Authorization header and the key state in Console → Keys.no
401signature_requiredThe key has request signing on and the request lacks X-Timestamp, X-Nonce or X-Signature.Sign every request made with this key (Authentication & keys → Request signing), or turn signing off on the key.no
401signature_expiredX-Timestamp is not 10-digit unix seconds or is more than 5 minutes from server time. The response carries X-Server-Timestamp.Correct the client clock by the X-Server-Timestamp difference and sign again with a new nonce.yes
401signature_invalidX-Signature does not match the key’s signing secret, or X-Nonce is malformed.Sign timestamp, nonce, method and path (no query string) joined by newlines with the current secret from Console → Keys.no
401signature_replayedThis X-Nonce was already used with this key in the last 10 minutes.Generate a new random nonce for every request, including retries.yes
403access_deniedThe caller IP is outside the key’s IP allowlist.Add the egress address in Console → Keys, or drop the allowlist for keys with no fixed IP.no
403noneAccount suspended, or the key is not allowed to use this model or channel.Check the key’s model whitelist first; if it is unrestricted, open a ticket — the account itself is blocked.no
400unsupported_endpointThe model exists but does not serve this path. The message lists the endpoints it does serve.Move the call to a listed endpoint — image task models do not answer on /v1/chat/completions.no
400read_request_body_failedThe request body could not be read — truncated upload, wrong Content-Type, or a stream that ended early.Send a complete body with a matching Content-Type. Resending the same broken request fails the same way.no
402insufficient_user_quotaWallet balance cannot cover the amount pre-reserved for this call.Top up in Console → Wallet, then retry.no
429insufficient_user_quotaThe account’s daily spend cap is reached. Same status as throttling, opposite meaning — backoff will not clear it.Raise or clear the cap in account settings, or resume tomorrow. Branch on the code, not on 429 alone.no
429nonePer-key RPM/RPD throttle or a concurrency guard. The body is empty; Retry-After carries the wait.Sleep for Retry-After seconds, then retry with exponential backoff and jitter.yes
413read_request_body_failedRequest body exceeds the accepted size. The transport-level limit aborts with an empty body.Shrink the payload — upload files through /v1/media/upload-policy and send the URL instead of Base64.no
422invalid_requestNo channel supports this media intent for the model — for example image-to-video on a text-to-video-only route.Check the model card for supported intents and switch model or intent.no
503model_not_foundUnknown model name, or no healthy channel currently serves it. Note the status: an unknown model is 503, not 404.Verify the name against /v1/models, then retry — failover usually finds a healthy channel on the next attempt.yes
500sensitive_words_detectedThe prompt hit the content filter before it left the gateway.Change the prompt. Retrying the same text always fails.no
500count_token_failedThe gateway could not estimate tokens for pricing, usually from a malformed message array.Validate the messages array; if it is well-formed, retry and open a ticket with the request id.yes
500invalid_requestThe request could not be parsed into a shape this endpoint accepts. Note the status — a body the gateway cannot read is reported as 500, not 400.Validate the JSON against the endpoint reference before retrying.no
502do_request_failedThe vendor was unreachable, or returned a failure the gateway could not recover through failover.Retry with backoff — the next attempt usually lands on a different channel.yes
504do_request_failedThe vendor did not answer within the gateway deadline.Retry with backoff; long prompts and busy vendors time out more often.yes
499do_request_failedYour client closed the connection before the answer arrived — usually your own HTTP timeout firing mid-stream.Raise the client read timeout. Long generations need minutes, not the default of most HTTP libraries.no
Idempotency guard errors

These come from the replay guard in front of the relay, so they use the flat {code, error} shape with an uppercase code instead of the OpenAI envelope.

HTTPCodeMeaningWhat to doRetry?
400IDEMPOTENCY_STREAM_UNSUPPORTEDIdempotency-Key was sent on a streaming request.Drop the header for streaming calls, or set stream:false when you need replay protection.no
400IDEMPOTENCY_BODY_UNREADABLEThe body could not be hashed for the idempotency ledger.Send a normal JSON or multipart body — chunked bodies that cannot be buffered are rejected.no
409IDEMPOTENCY_PAYLOAD_CONFLICTThis key was already used with a different body, path or query.Generate a new key. A key identifies one exact request, not one retry loop.no
409IDEMPOTENCY_IN_PROGRESSThe first request with this key is still running.Wait for Retry-After (2s) and retry the same key — you will get the original response.yes
409IDEMPOTENCY_RESULT_UNREPLAYABLEThe original request finished but its response was sealed and cannot be replayed.Do not resubmit blindly. Reconcile through the task endpoint or your own records first.no
503IDEMPOTENCY_UNAVAILABLEThe idempotency ledger is unavailable, so the guard fails closed rather than risk a double charge.Retry with backoff — the request was not executed.yes

How idempotency keys work →

Async task errors

Video, image-task, music and 3D endpoints answer with their own flat shape — {code, message, data} — for submit, poll and cancel alike. One quirk to plan for: polling an unknown task id is 400, cancelling one is 404.

HTTPCodeMeaningWhat to doRetry?
400task_not_existPolling or remixing a task id that does not exist, or belongs to another account. Note the status — polling an unknown task is 400, while cancelling one is 404.Persist task_id at submit time and poll with exactly that value.no
400invalid_requestA required field is missing — prompt on a remix, task_id on a fetch.Fix the body or the path before retrying.no
400task_channel_disableA follow-up task (remix, extend) is pinned to the original task’s channel, and that channel is now disabled.Submit a fresh task instead of deriving from the old one.no
400invalid_source_taskThe source task is the wrong kind for this operation — for example a video id passed to a 3D continuation.Check that the source id came from the same capability family.no
404task_not_existCancelling a task id that does not exist or is not yours.Nothing to cancel — treat it as already gone.no
409task_not_cancellableThe task already reached a terminal state, so there is nothing to cancel.Poll once more and use the result you already paid for.no
501task_cancel_not_supportedThe provider behind this task exposes no cancellation API. Most do not — cancellation is the exception, not the rule.Let the task finish. Design the UI so cancelling is best-effort, not a guarantee.no
502cancel_task_failedThe provider was unreachable while cancelling. The task is unchanged.Retry once, then fall back to letting it finish.yes

Async task lifecycle →

Still stuck?

Send us the request ID or the error message and we will look into it.