koala73/worldmonitor · error
Exa search failed HTTP ${resp.status}: ${text.slice(0, 120)}
Error message
Exa search failed HTTP ${resp.status}: ${text.slice(0, 120)} What it means
The exa-search adapter's raw Exa call (POST with x-api-key and a 15s AbortSignal timeout) received a non-2xx status; the message embeds both the code and the first 120 characters of the response body. Exa reports bad keys (401), rate limits (429), and validator rejections (400 INVALID_REQUEST_BODY) this way, and the body slice is the only place the actual reason appears.
Source
Thrown at consumer-prices-core/src/adapters/exa-search.ts:177
query: `What is the retail price of this product? State amount and ISO currency code (e.g. ${currency} 12.50).`,
},
},
};
const resp = await fetch('https://api.exa.ai/search', {
method: 'POST',
headers: {
'x-api-key': this.apiKey,
'Content-Type': 'application/json',
'User-Agent': CHROME_UA,
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(15_000),
});
if (!resp.ok) {
const text = await resp.text().catch(() => '');
throw new Error(`Exa search failed HTTP ${resp.status}: ${text.slice(0, 120)}`);
}
const data = (await resp.json()) as { results?: ExaResult[] };
const exaResults = data.results ?? [];
const payload: SearchPayload = {
exaResults,
basketSlug,
itemCategory: target.category,
canonicalName,
};
// Firecrawl fallback: when all Exa summaries fail price extraction,
// scrape the first result URL directly (JS-rendered pages expose prices in markdown).
const anyExaPrice = exaResults.some(
(r) => matchPrice(r.summary ?? '', currency) !== null || matchPrice(r.title ?? '', currency) !== null,
);
View on GitHub (pinned to eeab0a219f)
Solutions
- Read the embedded body slice first — it names the exact auth, quota, or validator complaint
- 401/403: fix EXA_API_KEY; 429: back off and reduce request concurrency
- 400: fix the request/schema shape — for Exa extraction schemas use anyOf for nullability, never type arrays
- 5xx: retry with backoff; Exa incidents usually clear within minutes
Example fix
// before — schema encoding Firecrawl accepts but Exa rejects (HTTP 400)
const schema = { price: { type: ['number', 'null'] } };
// after — Exa-compatible nullable encoding
const schema = { price: { anyOf: [{ type: 'number' }, { type: 'null' }] } }; Defensive patterns
Strategy: retry
Validate before calling
// cheap preflight: authenticated liveness call before the run
const resp = await fetch('https://api.exa.ai/search', {
method: 'POST',
headers: { 'x-api-key': key, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'ping', numResults: 0 }),
});
if (resp.status === 401 || resp.status === 403) {
throw new Error('EXA_API_KEY rejected — fix credentials before the run');
} Type guard
function exaStatus(err: unknown): number | null {
const m = err instanceof Error ? err.message.match(/^Exa search failed HTTP (\d+):/) : null;
return m ? Number(m[1]) : null;
} Try / catch
try {
await exaCall();
} catch (err) {
const status = exaStatus(err);
if (status === 429 || (status !== null && status >= 500)) {
await sleep(backoffMs);
return await exaCall(); // transient
}
throw err; // 4xx (except 429) is a request bug — retrying cannot help
} Prevention
- Always surface the 120-char body slice in logs — it distinguishes auth, quota, and schema errors
- Encode nullable schema fields as anyOf for Exa; never share schema encodings between providers untested
- Keep Exa concurrency low enough to stay under the plan's rate limit
When it happens
Trigger: 401 with a wrong EXA_API_KEY; 429 when the Exa rate limit is hit during basket discovery; 400 INVALID_REQUEST_BODY when the request JSON violates Exa's validator — notably a JSON Schema using array type unions, which Exa rejects but Firecrawl accepts (#6182); 5xx during an Exa incident.
Common situations: One Exa key shared across too many workers so 429s cluster at run start; porting a Firecrawl-validated schema to Exa verbatim so every call 400s; a key that expired after rotation.
Related errors
- P0 search failed: HTTP ${resp.status}
- Exa search failed for "${canonicalName}": ${detail}
- DNS ${recordType} lookup failed: status ${data.Status}
- Exa returned no content for ${url}
- Exa returned malformed structured summary for ${url}
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/da358a6bbec36eaa.
Report an issue: GitHub.