Embedding API errors are more insidious than chat API errors. When a chat request fails, the user sees it immediately; embeddings often run in an offline ingestion pipeline, where a single 400 or 429 may cause a batch of documents to be missing a few rows, and the problem is discovered only days later when retrieval quality drops.
Do not start troubleshooting by "trying another model." The order is: first check the status code and error type, then the request body size and batch, and finally the vectors themselves. Below, we break the problem down by 401, 413, 429, dimension mismatch, and empty vectors, with reusable handling code.
Note: OpenAI, DeepSeek, and other names are trademarks of their respective owners. API limits in this article use the public OpenAI embeddings documentation as an example: each input may not exceed 8,192 tokens, a request may contain at most 2,048 inputs, and a single request may not exceed 300,000 tokens in total; limits vary by vendor and gateway, so refer to the actual documentation.
1. Group Errors by Type First
Embedding errors can be roughly divided into seven categories. Classify first, then decide whether to change the code, the batch, or the data.
| Symptom | Common status code | Typical cause | Check first |
|---|---|---|---|
| Authentication failure | 401 or 403 | Wrong key, missing header, disabled key, wrong permission or region | Authorization header and key source |
| Input too long | 400 or 413 | Single item exceeds model limit, request body exceeds gateway limit | Token count per item and request body bytes |
| Batch too large | 400 or 413 | Item count exceeds limit, total tokens exceed limit | Item count and total tokens |
| Rate limited | 429 | Sudden concurrency spike, shared key, retry storm | Rate, concurrency, and Retry-After |
| Upstream failure | 5xx | Server overload or transient fault | Request ID and timestamp |
| Dimension mismatch | 400 or client exception | Model changed, dimensions changed, index has fixed dimensions | Model name, dimensions, and index configuration |
| Empty vector or NaN | 200 but invalid data | Empty text, parsing error, division by zero in normalization | Vector length, finite values, and norm |
Log at least the timestamp, status code, error type, model name, batch size, duration, and request ID. Do not log complete keys, and do not print entire source texts.
2. 401 and 403: Check the Request Header First
401 means authentication failed; 403 means authentication succeeded but access is forbidden. In neither case should you suspect model capability first.
Common causes: the header does not use Authorization: Bearer <key>; the key has leading or trailing spaces or quotes; environment variables were not loaded; the key was deleted or disabled; the base URL and key belong to different platforms; a reverse proxy dropped the authentication header. For 403, also check model access permissions, project permissions, region, and IP allowlists.
Send a minimal request with only a short input via curl, confirm it returns 200, then gradually add back batching, concurrency, and the proxy. If it still returns 401, try a new key to distinguish the key itself from gateway configuration.
3. 413 and Overlong Input: Identify Whose Limit Was Hit
413 usually means the request body is too large, possibly a gateway or reverse-proxy limit rather than a model rejection. The model's input limit may return 400. Treat the two separately.
Using OpenAI embeddings as an example: each input may not exceed 8,192 tokens, an array may contain at most 2,048 items, and one request may not exceed 300,000 tokens in total. Exceeding the total usually returns 400, even if no individual item is over the limit. A gateway may also impose a request body size limit and return 413 before the request reaches the model.
For overlong input, follow this order:
- Count tokens with the matching model's tokenizer; do not estimate by character count.
- Chunk by headings, paragraphs, and semantic boundaries, keeping a small overlap between chunks.
- Use truncation as a fallback for overlong chunks, recording the original length and truncation position.
- When batching, limit both item count and total tokens, leaving 10%–20% headroom.
- For 413, split the request body first; for 400, check single-item content and parameters first.
Also watch for empty strings. The OpenAI embeddings interface does not accept empty input, so paragraphs that become blank after cleaning must be filtered out before sending.
4. 429: Rate Limits, Concurrency, and Retry Storms
429 means a rate, concurrency, or quota limit was exceeded; it does not mean insufficient balance. Common triggers: multiple services share a key, batch jobs start simultaneously on the hour, retries have no backoff, and long requests consume more quota.
Check the response headers for Retry-After and wait as instructed if present; otherwise use exponential backoff with random jitter and set a retry cap. Retries only help with transient errors: 401, 403, 400, and most 413 responses will not succeed no matter how many times they are retried.
Engineering measures include: splitting keys by project; adding queues and concurrency caps to batch jobs; staggering large jobs; and recording failures in a dead-letter queue. Keep retries to three to five attempts, alert beyond that, and never loop indefinitely.
5. Dimension Mismatch: The Most Easily Overlooked Error
Dimension mismatch may surface as a database error such as "expected dimension 1536, got 1024"; as a NumPy shape error; as a broadcasting error when computing cosine similarity; or simply as retrieval results that suddenly become irrelevant.
Common causes: the model was changed but old vectors remain in the index; the dimensions parameter was adjusted; the same model name has different dimensions under different vendors or versions; the index was created with fixed dimensions; someone manually truncated or zero-padded vectors. The same dimensions do not mean vectors can be mixed; different models have incompatible vector spaces.
The fix is not zero-padding but rebuilding. Record the model name, dimensions, normalization method, distance metric, and chunking version for every vector; keep only one model and version per index; when changing models, create a new collection, run a full recompute, and validate before shifting traffic. Validate dimensions on both the write and query sides.
6. Empty Vectors, Zero Vectors, and NaN
A 200 response does not mean the data is usable. Empty vectors, all-zero vectors, and NaN values all break similarity calculations, and they often raise no error at all.
Common sources: text that is blank after cleaning; an empty input array; a response parsing error that leaves embedding as an empty list; a local model returning a zero vector for empty input; normalization dividing by zero and producing NaN; base64 that was not decoded correctly.
Before writing to the index, run at least three checks: whether the length equals the expected dimensions; whether every element is finite; and whether the vector norm exceeds a very small threshold. Put records that fail validation into a quarantine table with the source ID and reason, fix them, and rerun; do not silently discard them.
7. Reusable Error-Handling Code
The example below targets an OpenAI-compatible interface and covers rate-limit backoff, batch splitting, and vector validation. In real use, replace the model name, dimensions, and base URL with your own configuration.
import logging
import math
import random
import time
from openai import OpenAI, APIStatusError, APITimeoutError, RateLimitError
logger = logging.getLogger("embedding")
client = OpenAI(
api_key="sk-your-key",
base_url="https://api.zhongshangqihang.com/v1",
max_retries=0, # Disable SDK retries to avoid stacking with custom retries
)
MODEL = "text-embedding-3-small"
EXPECTED_DIM = 1536
def vector_is_valid(vec: list[float]) -> bool:
if len(vec) != EXPECTED_DIM:
return False
if not all(math.isfinite(x) for x in vec):
return False
norm = math.sqrt(sum(x * x for x in vec))
return norm > 1e-6
def embed_batch(texts: list[str], attempt: int = 0) -> list[list[float] | None]:
# Do not send empty texts; use None as a placeholder to keep indexes aligned
pairs = [(i, t) for i, t in enumerate(texts) if t and t.strip()]
if not pairs:
return [None] * len(texts)
indices = [i for i, _ in pairs]
payload = [t for _, t in pairs]
try:
resp = client.embeddings.create(model=MODEL, input=payload, timeout=60)
items = sorted(resp.data, key=lambda item: item.index)
vectors = [item.embedding for item in items]
if len(vectors) != len(payload):
raise ValueError(f"Returned count mismatch: {len(vectors)} != {len(payload)}")
for index, vec in enumerate(vectors):
if not vector_is_valid(vec):
raise ValueError(f"Invalid vector: index={index}, dim={len(vec)}")
result: list[list[float] | None] = [None] * len(texts)
for source_index, vec in zip(indices, vectors):
result[source_index] = vec
return result
except (RateLimitError, APITimeoutError) as exc:
if attempt >= 5:
raise
wait = min(2 ** attempt, 30) + random.random()
logger.warning("Rate limited or timed out, retrying in %.1f seconds: %s", wait, exc)
time.sleep(wait)
return embed_batch(texts, attempt + 1)
except APIStatusError as exc:
if exc.status_code in (400, 413) and len(texts) > 1:
mid = len(texts) // 2
logger.warning("Batch too large, splitting into %d + %d", mid, len(texts) - mid)
return embed_batch(texts[:mid], attempt) + embed_batch(texts[mid:], attempt)
if exc.status_code >= 500 and attempt < 5:
wait = min(2 ** attempt, 30) + random.random()
logger.warning("Upstream error, retrying in %.1f seconds: %s", wait, exc)
time.sleep(wait)
return embed_batch(texts, attempt + 1)
raise
The code makes two trade-offs. First, 400 responses also trigger batch splitting, because some gateways report "total token limit exceeded" as 400; splitting down to single items will still raise if the item itself fails. Second, vector validation happens before writing, so bad data never enters the index. Production environments should also record request IDs, retry counts, and failed batches.
8. Troubleshooting Order and Monitoring Checklist
When an embedding error occurs, follow this order; it is usually faster than repeatedly switching models:
- Record the status code, error type, request ID, model name, batch size, and duration.
- For 401 or 403, check the request header, key, environment variables, and platform first.
- For 400, check per-item tokens, total tokens, empty strings, and parameter format first.
- For 413, check request body bytes and gateway limits, then split requests and retry.
- For 429, read Retry-After, back off, reduce concurrency, and check whether keys are shared.
- For 5xx, retry with bounded backoff and include the request ID when contacting the provider if the problem persists.
- When a 200 response contains invalid results, check dimensions, norms, NaN, and normalization.
- Feed these metrics into monitoring and run periodic regressions with a fixed evaluation set.
Monitor hourly counts of 401, 400, 413, 429, and 5xx responses, plus counts of empty vectors and dimension anomalies. When the error rate spikes, check whether the errors concentrate in a project, key, model, or document batch, then decide whether to reduce concurrency, split batches, roll back, or fix the data.
Frequently Asked Questions
Both 413 and 400 can mean the input is too long. What is the difference?
413 usually comes from a gateway or reverse-proxy request body size limit, and the request may be rejected before reaching the model; 400 more often comes from API parameters or token limits. Check the response body and request ID first, then decide whether to split the request body or split the text.
A single item is under 8,192 tokens, so why is the response still 400?
It may be that the total tokens for the request exceed the limit, the input array exceeds the item limit, an empty string is present, parameter formatting is wrong, or the model name is incorrect. Split the batch, filter empty text, and check the request body item by item.
How many times should a 429 be retried?
A common practice is three to five attempts with exponential backoff and random jitter, honoring Retry-After first. Beyond that, enqueue the work or raise an alert rather than retrying indefinitely. Under severe rate limiting, reducing concurrency is more effective than adding retries.
Can dimension mismatch be fixed with zero-padding?
No. Zero-padding changes vector direction and similarity, making retrieval results untrustworthy. The correct approach is to standardize the model and dimensions, create a new index, run a full recompute, and validate before shifting traffic.
Why does the API return 200 with an empty vector?
The input may be empty or contain only whitespace after cleaning, or the problem may be in response parsing, base64 decoding, or local inference. Validate length, finite values, and norm before writing, and quarantine anomalous records for investigation.
Next Steps
Create a test key at ZSQH AI Gateway, run a small batch of text through authentication, batching, rate limiting, and vector validation, and then connect the production ingestion pipeline. Model availability and pricing are listed on the model pricing page.
Related reading:
