zeroclaw-labs/zeroclaw · error

Embedding API error {status}: {text}

Error message

Embedding API error {status}: {text}

What it means

OpenAiEmbedding::embed POSTs {"model", "input": texts} with a Bearer token to {base_url}/v1/embeddings (or {base_url}/embeddings when the base URL already carries an explicit path). Any non-2xx response is turned into this error, embedding the HTTP status code and the provider's response body. It is the provider rejecting the request — auth, model, payload, rate limits, or proxy — and the response text usually names the exact reason.

Source

Thrown at crates/zeroclaw-memory/src/embeddings.rs:151

        let body = serde_json::json!({
            "model": self.model,
            "input": texts,
        });

        let resp = self
            .http_client()
            .post(self.embeddings_url())
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();
            anyhow::bail!("Embedding API error {status}: {text}");
        }

        let json: serde_json::Value = resp.json().await?;
        let data = json.get("data").and_then(|d| d.as_array()).ok_or_else(|| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                "embedding response missing 'data' field"
            );
            anyhow::Error::msg("Invalid embedding response: missing 'data'")
        })?;

        let mut embeddings = Vec::with_capacity(data.len());
        for item in data {
            let embedding = item
                .get("embedding")
                .and_then(|e| e.as_array())

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the status and body in the message first: 401/403 → fix the API key; 400/404 → fix model name, base_url path, or dimensions to match the endpoint; 429 → slow down/backoff or raise quota; 5xx → retry later.
  2. Verify the endpoint by curl-ing {base_url}/v1/embeddings with the same model and one short input; the response body must contain a data[].embedding array.
  3. Check the runtime proxy configuration for memory.embeddings if a proxy sits in the path (407/502/503 usually come from it, not the provider).
  4. Batch smaller: split the texts slice and retry so per-request input limits are not hit.
  5. If embeddings are optional for your flow, fall back to NoopEmbedding (keyword-only) explicitly rather than letting recall fail.

Example fix

// before
let emb = OpenAiEmbedding::new("http://localhost:9999", &key, "text-embedding-3-large", 1536);
let v = emb.embed_one(text).await?; // Embedding API error 404 Not Found: ...

// after: model and base path that the endpoint actually serves
let emb = OpenAiEmbedding::new("https://api.openai.com", &key, "text-embedding-3-small", 1536);
let v = emb.embed_one(text).await?;
Defensive patterns

Strategy: retry

Validate before calling

null

Try / catch

// Distinguish transient (retry) from permanent (fix config) statuses
const MAX_TRIES: u32 = 3;
for attempt in 1..=MAX_TRIES {
    match provider.embed(&texts).await {
        Ok(v) => return Ok(v),
        Err(e) if e.to_string().contains("Embedding API error 429") => {
            tokio::time::sleep(std::time::Duration::from_secs(2u64.pow(attempt))).await;
        }
        Err(e) if e.to_string().contains("Embedding API error 5") => {
            tokio::time::sleep(std::time::Duration::from_secs(attempt as u64)).await;
        }
        Err(e) => return Err(e), // 4xx: fix api key / model / base_url, do not retry
    }
}
bail!("embedding provider unavailable after {MAX_TRIES} retries")

Prevention

When it happens

Trigger: Calling embed/embed_one on OpenAiEmbedding (recall/store paths in memory that need vectors) with: a wrong/missing API key (401), a model name the endpoint does not serve or a model/dims mismatch (400/404), a base_url pointing at the wrong path or a non-OpenAI-compatible server (404), too many/too long texts in one batch (400 payload limits), provider rate limiting or exhausted quota (429), or transient 5xx/proxy failures — note the client is built via build_runtime_proxy_client("memory.embeddings"), so a misconfigured runtime proxy surfaces here too.

Common situations: Swapping embedding providers (e.g. a local OpenAI-compatible server) without updating base_url/model/dims together; expired or rotated API keys; CI environments without the proxy env vars the runtime expects; nightly batch jobs that exceed rate limits after the corpus grows; model renamed by the vendor.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/30d35625d784cbd6. Report an issue: GitHub.