All external services are swappable seams. Pick per deployment in seo.config.json (providers.*) and set the matching keys in Vault. Every provider is fail-soft — on any error it returns an empty result rather than throwing, so one flaky dependency never crashes a run.
SERP (rankings) — providers.serp
Reads the public search ranking for a keyword. search(query, opts?) → { results: {url,title}[] }.
| Value | Impl | Keys | Notes |
|---|---|---|---|
google (default) | googleProvider({ apiKey, cx }) | GOOGLE_SEARCH_API_KEY, GOOGLE_SEARCH_CX | Needs a GCP project with Custom Search API enabled. |
serper | serperProvider({ apiKey }) | SERPER_API_KEY | Single key, no GCP setup. Serves SERP + web search. |
fake | fakeProvider(fixture) | — | Tests only. |
Web search (competitor discovery) — providers.webSearch
Used by the competitor researcher to find a competitor's pages. Same search() shape. google uses googleWebSearch({ apiKey, cx }); serper reuses serperProvider.
Search Console (optional) — providers.searchConsole
Owner-measured positions/impressions, higher fidelity than public SERP. Off by default (null). gscProvider({ token, siteUrl, days?, rowLimit? }) → positions() → {keyword,position,impressions}[]. Requires an OAuth token for a property you own (GSC_OAUTH_TOKEN).
LLM (analysis + proposals)
Anthropic via callClaude({ system, user }). The reference uses claude-haiku-4-5 for cost. Key: ANTHROPIC_API_KEY. Untrusted fetched content is always fenced in <data> tags and the fences are neutralized in the input (see docs/SECURITY.md).
Adding a provider
Implement the interface (adapter/serpProvider.d.ts or searchConsole.d.ts), keep it fail-soft, inject fetchImpl for tests, add a fake + test, then reference it from the edge shell. No core changes required.