{"record":{"id":"e2757c4de9cbee1d","repo":"koala73/worldmonitor","slug":"p0-search-failed-http-resp-status","errorCode":null,"errorMessage":"P0 search failed: HTTP ${resp.status}","messagePattern":"P0 search failed: HTTP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"consumer-prices-core/src/acquisition/p0.ts","lineNumber":82,"sourceCode":"      markdown: data.markdown,\n      statusCode: data.statusCode ?? 200,\n      provider: this.name,\n      fetchedAt: new Date(),\n    };\n  }\n\n  async search(query: string, opts: SearchOptions = {}): Promise<SearchResult[]> {\n    const resp = await fetch(`${this.baseUrl}/search`, {\n      method: 'POST',\n      headers: this.headers(),\n      body: JSON.stringify({\n        query,\n        num_results: opts.numResults ?? 10,\n        include_domains: opts.includeDomains,\n      }),\n    });\n\n    if (!resp.ok) throw new Error(`P0 search failed: HTTP ${resp.status}`);\n\n    const data = (await resp.json()) as P0SearchResponse;\n    return (data.results ?? []).map((r) => ({\n      url: r.url,\n      title: r.title,\n      text: r.snippet,\n    }));\n  }\n\n  async validate(): Promise<boolean> {\n    try {\n      const resp = await fetch(`${this.baseUrl}/health`, {\n        headers: this.headers(),\n        signal: AbortSignal.timeout(5_000),\n      });\n      return resp.ok;\n    } catch {\n      return false;","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/consumer-prices-core/src/acquisition/p0.ts#L64-L100","documentation":"P0Provider.search() throws when POST {P0_BASE_URL}/search returns a non-2xx status. The search endpoint takes query, num_results (default 10) and include_domains with x-api-key auth. Unlike scrape, no fallback path is wired for search — Exa is the pipeline's primary search provider — so an HTTP failure here propagates straight to the caller.","triggerScenarios":"401/403 invalid P0_API_KEY on /search; 429 when search quota is exhausted; 5xx during a P0 outage; P0_BASE_URL misconfigured so /search resolves to a wrong path or gateway.","commonSituations":"Using P0 for search without provisioning search on the plan; P0_API_KEY rotated but only some workers restarted; a gateway returning 502 between the worker and P0.","solutions":["Map the status code to auth (401/403), quota/rate (402/429), or outage (5xx) and fix accordingly","Verify P0_BASE_URL ends in the correct API version path","Route search through Exa — the provider the pipeline is designed around — and keep P0 for scraping only","Retry with backoff on 429/5xx before giving up"],"exampleFix":"// before\nconst results = await p0.search(query);\n\n// after — retry transient statuses, fail fast on auth\nconst results = await retryOn(() => p0.search(query), {\n  retries: 2,\n  if: (e) => /HTTP (429|5\\d\\d)/.test(String(e?.message)),\n});","handlingStrategy":"retry","validationCode":"// probe the provider before relying on search\nif (!(await p0.validate())) {\n  throw new Error('P0 /health failed — do not route search through it');\n}","typeGuard":"function isP0SearchHttpError(err: unknown): boolean {\n  return err instanceof Error && /^P0 search failed: HTTP \\d+$/.test(err.message);\n}","tryCatchPattern":"try {\n  return await p0.search(q, opts);\n} catch (err) {\n  if (isP0SearchHttpError(err) && /HTTP (429|5\\d\\d)/.test((err as Error).message)) {\n    await sleep(2_000);\n    return await p0.search(q, opts); // transient only\n  }\n  throw err; // 401/403/4xx is permanent — surface it\n}","preventionTips":["Reserve P0 for scrape jobs; wire search to Exa, the provider the pipeline is designed around","Health-check P0 via validate() before any run that calls search","Alert on 429 clusters — they precede quota exhaustion"],"tags":["p0","search-api","http-status","rate-limit"],"backgroundTag":"http-api-error","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}