koala73/worldmonitor · error

Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${d

Error message

Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${detail.slice(0, 120)}

What it means

All Exa REST calls in this provider (search, contents) go through request(), which throws on any non-2xx with the endpoint name, HTTP status, and the first 120 characters of the response body — enough to distinguish 401 (bad API key), 400 INVALID_REQUEST_BODY, 429 rate/quota, and 5xx incidents.

Source

Thrown at consumer-prices-core/src/acquisition/exa.ts:141

      return false;
    }
  }

  private async request<T>(endpoint: string, body: Record<string, unknown>, timeout = 30_000): Promise<T> {
    const response = await globalThis.fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'x-api-key': this.apiKey,
        'Content-Type': 'application/json',
        'User-Agent': 'worldmonitor-consumer-prices/1.0',
      },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(timeout),
    });

    if (!response.ok) {
      const detail = await response.text().catch(() => '');
      throw new Error(`Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${detail.slice(0, 120)}`);
    }

    return (await response.json()) as T;
  }
}

function parseStructuredSummary<T>(summary: unknown): T | null {
  if (summary && typeof summary === 'object') return summary as T;
  if (typeof summary !== 'string') return null;

  const trimmed = summary.trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
  if (!trimmed) return null;
  try {
    const parsed: unknown = JSON.parse(trimmed);
    return parsed && typeof parsed === 'object' ? (parsed as T) : null;
  } catch {
    return null;
  }

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Read the status and body slice in the message: 401 → fix the EXA API key env; 400 → fix the request/schema shape
  2. For nullable fields use anyOf: [{ type: 'number' }, { type: 'null' }] — never type arrays
  3. On 429, back off exponentially and check the plan quota before resuming the batch
  4. On 5xx, retry with backoff and check the Exa status page

Example fix

// before — nullable field as a JSON-Schema type array
const outputSchema = {
  type: 'object',
  properties: { price: { type: ['number', 'null'] } },  // Exa 400: INVALID_REQUEST_BODY
};

// after — express nullability with anyOf, which Exa's validator accepts
const outputSchema = {
  type: 'object',
  properties: { price: { anyOf: [{ type: 'number' }, { type: 'null' }] } },
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap preflight: key present and non-empty before any Exa call
if (!process.env.EXA_API_KEY) throw new Error('EXA_API_KEY not set');

Try / catch

try {
  await exa.search(q);
} catch (err) {
  const m = /failed HTTP (\d+): (.*)/.exec(err.message);
  if (!m) throw err;
  const [, status, detail] = m;
  if (status === '401') throw new ConfigError('Exa API key invalid');
  if (status === '429') return backoffRetry(fn);            // rate/quota — retryable
  if (Number(status) >= 500) return backoffRetry(fn);       // provider incident
  throw new Error(`Exa request rejected: ${detail}`);        // 400 — fix the payload
}

Prevention

When it happens

Trigger: 401 from a missing or typo'd Exa API key; 400 when the summary schema uses a form Exa's validator rejects — notably JSON-Schema type arrays like ['number','null'] instead of anyOf; 429 rate limit or exhausted credits during burst scraping; 5xx provider incidents.

Common situations: API key rotated but the env var not updated; schema evolution introducing nullable union types; a large scrape run draining the plan quota mid-batch.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/09e04f2adfdc9fce. Report an issue: GitHub.