Financial Evidence Expansion — FRED, Finnhub, FMP
Motivation
The Alpha Vantage plan covers company fundamentals and basic economic indicators, but has two constraints: 25 requests/day free tier, and Alpha Vantage is a secondary source for economic data — it aggregates from primary sources like the Federal Reserve. This plan adds three complementary APIs that fill specific gaps:
- FRED — the primary source for US economic data (816K+ series). When a fact claims "GDP grew 5.7%," FRED is where that number originates.
- Finnhub — unique data Alpha Vantage doesn't have: congressional trading, ESG scores, patents, supply chains, lobbying.
- FMP — capacity relief for company fundamentals (250 req/day vs Alpha Vantage's 25).
Service Overview
FRED (Federal Reserve Economic Data)
| Attribute | Value |
|---|---|
| Base URL | https://api.stlouisfed.org/fred |
| Auth | ?api_key=KEY (FRED_API_KEY env var, already in .env.local) |
| Free tier | 2 requests/second, no daily cap |
| Data | 816,000+ economic data series |
| History | Decades of data, some series back to 1940s |
| Source authority | Federal Reserve Bank of St. Louis — the primary source for US macro data |
Key endpoints:
| Endpoint | Returns |
|---|---|
/fred/series/search?search_text=GDP | Find series by keyword |
/fred/series/observations?series_id=GDP | Actual data values over time |
/fred/series?series_id=CPIAUCSL | Series metadata (title, units, frequency) |
/fred/category/children?category_id=0 | Browse data categories |
Key series IDs for fact verification:
| Series | ID | Description |
|---|---|---|
| Real GDP | GDP | Quarterly, billions of dollars |
| GDP Growth Rate | A191RL1Q225SBEA | Quarterly % change |
| CPI (All Urban) | CPIAUCSL | Monthly, index 1982-84=100 |
| Unemployment Rate | UNRATE | Monthly, % |
| Federal Funds Rate | FEDFUNDS | Monthly, % |
| 10-Year Treasury | DGS10 | Daily, % |
| M2 Money Supply | M2SL | Monthly, billions |
| Housing Starts | HOUST | Monthly, thousands |
| Inflation (YoY) | FPCPITOTLZGUSA | Annual, % |
| Trade Balance | BOPGSTB | Monthly, millions |
| Consumer Confidence | UMCSENT | Monthly, index |
| S&P 500 | SP500 | Daily, index |
Finnhub
| Attribute | Value |
|---|---|
| Base URL | https://finnhub.io/api/v1 |
| Auth | ?token=KEY or header X-Finnhub-Token (FINNHUB_API_KEY env var, already in .env.local) |
| Free tier | 60 requests/minute |
| Unique data | Congressional trading, ESG, patents, supply chains, lobbying, insider transactions |
Key endpoints:
| Endpoint | Returns |
|---|---|
/stock/profile2?symbol=AAPL | Company profile (IPO date, market cap, industry) |
/stock/insider-transactions?symbol=AAPL | Insider trading activity |
/stock/congressional-trading?symbol=AAPL | Congressional stock trades |
/stock/esg?symbol=AAPL | ESG scores and ratings |
/stock/uspto-patent?symbol=AAPL | Patent filings |
/stock/supply-chain?symbol=AAPL | Supply chain relationships |
/stock/lobbying?symbol=AAPL | Lobbying expenditures |
/calendar/earnings | Earnings calendar |
/calendar/ipo | IPO calendar |
FMP (Financial Modeling Prep)
| Attribute | Value |
|---|---|
| Base URL | https://financialmodelingprep.com/api/v3 |
| Auth | ?apikey=KEY (FMP_API_KEY env var, already in .env.local) |
| Free tier | 250 requests/day (US exchanges) |
| Role | Capacity backup for Alpha Vantage company fundamentals |
Key endpoints:
| Endpoint | Returns |
|---|---|
/profile/{symbol} | Company profile (market cap, IPO date, CEO, sector) |
/income-statement/{symbol} | Revenue, net income, EBITDA |
/balance-sheet-statement/{symbol} | Assets, liabilities, equity |
/key-metrics/{symbol} | PE ratio, debt/equity, ROE, book value |
/historical-price-full/{symbol} | Daily OHLCV history |
/search?query=Tesla | Symbol search by company name |
Implementation
Challenge 1: FRED Client
File: packages/ai/src/fred-client.ts (new)
- Base URL:
https://api.stlouisfed.org/fred - Auth:
api_keyquery param fromFRED_API_KEY - In-memory cache: 24h TTL for series metadata, 1h for observations, 5K max entries
- Metrics:
fred.api_calls,fred.cache_hit,fred.series_found
Key methods:
searchSeries(query: string): Promise<FredSeries | null>
getObservations(seriesId: string, startDate?: string, endDate?: string): Promise<FredObservation[]>
getSeriesMetadata(seriesId: string): Promise<FredSeriesInfo | null>
formatFredContext(series: FredSeriesInfo, observations: FredObservation[]): string
// Convenience lookups for common claims
getGDP(year: number): Promise<FredObservation | null>
getCPI(year: number, month?: number): Promise<FredObservation | null>
getUnemployment(year: number, month?: number): Promise<FredObservation | null>
getInflation(year: number): Promise<FredObservation | null>
Acceptance: Can query "What was US GDP in 2021?" → GDP series → observation for 2021-Q4.
Challenge 2: FRED Evidence Integration
File: packages/ai/src/validation/evidence.ts
FRED is the primary source for economic claims — check it before Alpha Vantage for macro data:
// Economic indicator claims — FRED first (authoritative primary source)
if (hasEconomicClaim(factContext) && fredApiKey) {
const indicator = detectEconomicIndicator(factContext)
// Map claim to FRED series ID
const seriesId = CLAIM_TO_FRED_SERIES[indicator] // e.g., 'gdp_growth' → 'A191RL1Q225SBEA'
if (seriesId) {
const year = extractYear(factContext)
const observations = await getObservations(seriesId, `${year}-01-01`, `${year}-12-31`)
if (observations.length > 0) {
findings.push(`FRED (Federal Reserve): ${indicator} = ${observations[0].value} (${observations[0].date})`)
sources.push({ name: 'FRED', type: 'authoritative' }) // higher weight than secondary sources
}
}
}
Confidence: FRED data is authoritative (primary source), not just corroborating:
- FRED value matches claim →
apiConfidence= 0.9 (near-definitive) - FRED value contradicts claim → flag as critical with high confidence
Acceptance: "US inflation hit 9.1% in June 2022" → FRED CPIAUCSL → actual value → verified or refuted.
Challenge 3: Finnhub Client
File: packages/ai/src/finnhub-client.ts (new)
- Base URL:
https://finnhub.io/api/v1 - Auth:
X-Finnhub-Tokenheader fromFINNHUB_API_KEY - In-memory cache: 1h TTL for profiles, 24h for ESG/patents, 3K max entries
- Metrics:
finnhub.api_calls,finnhub.cache_hit
Key methods:
getCompanyProfile(symbol: string): Promise<FinnhubProfile | null>
getInsiderTransactions(symbol: string): Promise<FinnhubInsiderTx[] | null>
getCongressionalTrading(symbol: string): Promise<FinnhubCongressTx[] | null>
getESGScores(symbol: string): Promise<FinnhubESG | null>
getPatents(symbol: string): Promise<FinnhubPatent[] | null>
getSupplyChain(symbol: string): Promise<FinnhubSupplyChain | null>
formatFinnhubContext(profile: FinnhubProfile): string
Acceptance: Can look up "AAPL" → company profile + ESG scores + patent count.
Challenge 4: Finnhub Evidence Integration
File: packages/ai/src/validation/evidence.ts
Finnhub supplements Alpha Vantage company lookups with unique data:
if (finnhubKey && hasCompanyClaim(factContext)) {
const symbol = resolvedSymbol // from Alpha Vantage SYMBOL_SEARCH or FMP /search
if (symbol) {
// ESG claims
if (hasESGClaim(factContext)) {
const esg = await getESGScores(symbol)
if (esg) findings.push(`Finnhub ESG: total ${esg.totalScore}, env ${esg.environmentScore}`)
}
// Congressional/insider trading claims
if (hasTradingClaim(factContext)) {
const trades = await getCongressionalTrading(symbol)
if (trades?.length) findings.push(`Finnhub: ${trades.length} congressional trades found`)
}
// Patent claims
if (hasPatentClaim(factContext)) {
const patents = await getPatents(symbol)
if (patents?.length) findings.push(`Finnhub: ${patents.length} patent filings`)
}
}
}
Acceptance: ESG, congressional trading, and patent claims get corroborated from Finnhub data.
Challenge 5: FMP Client (Capacity Backup)
File: packages/ai/src/fmp-client.ts (new)
- Base URL:
https://financialmodelingprep.com/api/v3 - Auth:
apikeyquery param fromFMP_API_KEY - In-memory cache: 1h TTL, 3K max entries
- Metrics:
fmp.api_calls,fmp.cache_hit
Key methods:
searchSymbol(query: string): Promise<FmpSymbolMatch | null>
getCompanyProfile(symbol: string): Promise<FmpProfile | null>
getIncomeStatement(symbol: string): Promise<FmpIncomeStatement | null>
getKeyMetrics(symbol: string): Promise<FmpKeyMetrics | null>
formatFmpContext(profile: FmpProfile): string
Acceptance: Can look up "Tesla" → TSLA → profile with market cap, IPO date, revenue.
Challenge 6: FMP Evidence Fallback
File: packages/ai/src/validation/evidence.ts
FMP fires when Alpha Vantage returns null (rate limited or key missing):
// Company fundamentals — Alpha Vantage first, FMP fallback
let companyData = await alphaVantageOverview(symbol)
if (!companyData && fmpKey) {
companyData = await fmpCompanyProfile(symbol)
if (companyData) {
findings.push(`FMP: ${companyData.companyName}, IPO ${companyData.ipoDate}, MCap $${companyData.mktCap}`)
}
}
Acceptance: When Alpha Vantage hits 25/day limit, FMP seamlessly provides company data.
Challenge 7: World Bank Indicators Client
File: packages/ai/src/worldbank-client.ts (new)
The World Bank Indicators API provides ~16,000 development indicators across 200+ countries with 50+ years of history. Covers global GDP, population, poverty, life expectancy, emissions, literacy, and other development metrics that FRED (US-focused) doesn't cover.
Base URL: https://api.worldbank.org/v2
Auth: None required (fully open)
Rate limits: None documented
Cost: Free, forever
Query pattern: /country/{iso2}/indicator/{id}?date={year}&format=json
Key indicators for fact verification:
| Indicator | ID | Description |
|---|---|---|
| GDP (current US$) | NY.GDP.MKTP.CD | Country GDP in dollars |
| GDP growth | NY.GDP.MKTP.KD.ZG | Annual % growth |
| Population | SP.POP.TOTL | Total population |
| Life expectancy | SP.DYN.LE00.IN | At birth, years |
| Poverty headcount | SI.POV.DDAY | % at $2.15/day |
| CO2 emissions | EN.ATM.CO2E.KT | Kilotons |
| Literacy rate | SE.ADT.LITR.ZS | Adult %, 15+ |
| Internet users | IT.NET.USER.ZS | % of population |
| Infant mortality | SP.DYN.IMRT.IN | Per 1,000 live births |
| Renewable energy | EG.FEC.RNEW.ZS | % of total consumption |
| Military spending | MS.MIL.XPND.GD.ZS | % of GDP |
| Unemployment | SL.UEM.TOTL.ZS | % of labor force |
Key methods:
getIndicator(countryCode: string, indicatorId: string, year?: number): Promise<WorldBankObservation | null>
searchIndicator(query: string): Promise<WorldBankIndicatorInfo[]>
getCountryData(countryCode: string, indicatorId: string, startYear: number, endYear: number): Promise<WorldBankObservation[]>
formatWorldBankContext(obs: WorldBankObservation): string
// Convenience lookups
getCountryGDP(countryCode: string, year: number): Promise<number | null>
getCountryPopulation(countryCode: string, year: number): Promise<number | null>
getCountryLifeExpectancy(countryCode: string, year: number): Promise<number | null>
- In-memory cache: 24h TTL, 5K max entries
- Metrics:
worldbank.api_calls,worldbank.cache_hit,worldbank.indicator_found - Country name → ISO2 code resolver (e.g., "India" → "IN", "United States" → "US")
Acceptance: Can query "India population 2023" → SP.POP.TOTL for IN → 1,438,069,596.
Challenge 8: World Bank Evidence Integration
File: packages/ai/src/validation/evidence.ts
World Bank supplements FRED for global/international claims:
// Global development claims — World Bank (complements FRED for non-US data)
const globalTopics = ['economics', 'politics', 'geography', 'history', 'health', 'environment']
if (globalTopics.some(t => topicPath.includes(t))) {
// Country-specific claims with numeric values
const country = detectCountry(factContext)
if (country && hasNumericClaim(factContext)) {
const indicator = detectWorldBankIndicator(factContext)
// Map claim to indicator ID: "population" → SP.POP.TOTL, "GDP" → NY.GDP.MKTP.CD
if (indicator) {
const year = extractYear(factContext)
const obs = await getIndicator(country.iso2, indicator.id, year)
if (obs) {
findings.push(`World Bank: ${country.name} ${indicator.name} = ${obs.value} (${obs.date})`)
sources.push({ name: 'World Bank', type: 'authoritative' })
}
}
}
}
FRED vs World Bank routing:
- Claim mentions US-specific data (Federal funds rate, S&P 500, US CPI) → FRED first
- Claim mentions a non-US country or global comparison → World Bank first
- Both can fall through to each other as backup
Confidence:
- World Bank value matches claim within margin →
apiConfidence= 0.85 (authoritative) - World Bank value contradicts claim → flag as critical
- No results → fall through
Acceptance: "India surpassed China in population in 2023" → World Bank SP.POP.TOTL for IN and CN → actual values → verified.
Challenge 9: Tests
Files:
packages/ai/src/__tests__/fred-client.test.ts(new)packages/ai/src/__tests__/finnhub-client.test.ts(new)packages/ai/src/__tests__/fmp-client.test.ts(new)packages/ai/src/__tests__/worldbank-client.test.ts(new)
Per client:
- Response parsing
- Cache behavior
- Rate limit tracking (FRED, Finnhub, FMP) / no-limit behavior (World Bank)
- Graceful failure
- Evidence claim detection helpers
World Bank specific:
- Country name → ISO2 code resolution
- Indicator ID detection from fact context
- Multi-year range queries
- JSON response parsing (
.[1][0].valuestructure)
Acceptance: bun run test passes.
Evidence Source Priority
For financial/economic/development facts, the evidence pipeline queries sources in priority order:
| Priority | Source | Role | Daily Budget | Scope |
|---|---|---|---|---|
| 1 | FRED | Authoritative US economic data | Unlimited (2 req/sec) | US macro |
| 2 | World Bank | Authoritative global development data | Unlimited (no limits) | 200+ countries |
| 3 | Alpha Vantage | Company fundamentals + US economic backup | 25 req/day | Companies |
| 4 | Finnhub | Unique data (ESG, congressional, patents) | 60 req/min | Companies |
| 5 | FMP | Company fundamentals overflow | 250 req/day | Companies |
Total daily capacity: unlimited economic/development lookups (FRED + World Bank), ~275+ company lookups.
Cost
All free tier. No paid plans required for validation pipeline volumes.
| API | Free Tier | Paid (if needed) |
|---|---|---|
| FRED | 2 req/sec, no daily cap | N/A (always free) |
| World Bank | No limits, no key required | N/A (always free) |
| Finnhub | 60 req/min | $0 (free plan sufficient) |
| FMP | 250 req/day | $14/month for 300 req/day |
Dependencies
Keys already in .env.local:
FRED_API_KEYFINNHUB_API_KEYFMP_API_KEY- World Bank: no key needed
Add FRED, Finnhub, FMP to packages/config/src/index.ts env schema and .env.example.
Relationship to Other Evidence Plans
| Plan | Domain | Data |
|---|---|---|
| API-Sports | Sports | Match results, player stats, game data |
| OpenAlex + Nobel Prize + NASA | Science, academia, space | Authors, papers, institutions, prize attribution, exoplanets |
| Alpha Vantage | Finance (primary) | Company fundamentals, stock prices, basic economic data |
| FRED + Finnhub + FMP + World Bank | Finance/economics (expansion) | US macro, global development, ESG, congressional trading, capacity backup |
| DBpedia | General-purpose fallback | Structured Wikipedia infobox properties |