{"record":{"id":"30d35625d784cbd6","repo":"zeroclaw-labs/zeroclaw","slug":"embedding-api-error-status-text","errorCode":null,"errorMessage":"Embedding API error {status}: {text}","messagePattern":"Embedding API error (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/embeddings.rs","lineNumber":151,"sourceCode":"\n        let body = serde_json::json!({\n            \"model\": self.model,\n            \"input\": texts,\n        });\n\n        let resp = self\n            .http_client()\n            .post(self.embeddings_url())\n            .header(\"Authorization\", format!(\"Bearer {}\", self.api_key))\n            .header(\"Content-Type\", \"application/json\")\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let text = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"Embedding API error {status}: {text}\");\n        }\n\n        let json: serde_json::Value = resp.json().await?;\n        let data = json.get(\"data\").and_then(|d| d.as_array()).ok_or_else(|| {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                \"embedding response missing 'data' field\"\n            );\n            anyhow::Error::msg(\"Invalid embedding response: missing 'data'\")\n        })?;\n\n        let mut embeddings = Vec::with_capacity(data.len());\n        for item in data {\n            let embedding = item\n                .get(\"embedding\")\n                .and_then(|e| e.as_array())","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/embeddings.rs#L133-L169","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","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).","Batch smaller: split the texts slice and retry so per-request input limits are not hit.","If embeddings are optional for your flow, fall back to NoopEmbedding (keyword-only) explicitly rather than letting recall fail."],"exampleFix":"// before\nlet emb = OpenAiEmbedding::new(\"http://localhost:9999\", &key, \"text-embedding-3-large\", 1536);\nlet v = emb.embed_one(text).await?; // Embedding API error 404 Not Found: ...\n\n// after: model and base path that the endpoint actually serves\nlet emb = OpenAiEmbedding::new(\"https://api.openai.com\", &key, \"text-embedding-3-small\", 1536);\nlet v = emb.embed_one(text).await?;","handlingStrategy":"retry","validationCode":"null","typeGuard":null,"tryCatchPattern":"// Distinguish transient (retry) from permanent (fix config) statuses\nconst MAX_TRIES: u32 = 3;\nfor attempt in 1..=MAX_TRIES {\n    match provider.embed(&texts).await {\n        Ok(v) => return Ok(v),\n        Err(e) if e.to_string().contains(\"Embedding API error 429\") => {\n            tokio::time::sleep(std::time::Duration::from_secs(2u64.pow(attempt))).await;\n        }\n        Err(e) if e.to_string().contains(\"Embedding API error 5\") => {\n            tokio::time::sleep(std::time::Duration::from_secs(attempt as u64)).await;\n        }\n        Err(e) => return Err(e), // 4xx: fix api key / model / base_url, do not retry\n    }\n}\nbail!(\"embedding provider unavailable after {MAX_TRIES} retries\")","preventionTips":["Smoke-test the provider at startup: embed_one(\"ping\") and fail fast with a clear config error before the first real recall.","Keep base_url, model, and dims as one versioned unit in config — they must match the endpoint together.","Run recall/store paths against NoopEmbedding in environments without network, so missing embeddings degrade to keyword search by design.","Log status+body verbatim on failure (this error already includes both) and alert on 401/403 separately from 429/5xx.","Batch embedding requests below the provider's per-request input limit."],"tags":["rust","zeroclaw","memory","embeddings","openai","http","api-key","rate-limit"],"backgroundTag":"openai-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}