@pencil-agent/nano-pencil 2.0.0-beta.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +267 -267
  2. package/dist/build-meta.json +3 -3
  3. package/dist/core/export-html/AGENT.md +11 -11
  4. package/dist/core/export-html/template.css +971 -971
  5. package/dist/core/export-html/template.html +54 -54
  6. package/dist/core/extensions-host/index.d.ts +1 -1
  7. package/dist/core/extensions-host/loader.js +1 -1
  8. package/dist/core/extensions-host/runner.d.ts +1 -0
  9. package/dist/core/extensions-host/runner.js +2 -2
  10. package/dist/core/extensions-host/types.d.ts +17 -22
  11. package/dist/core/lib/ai/src/types.d.ts +12 -2
  12. package/dist/core/persona/persona-manager.js +5 -2
  13. package/dist/core/runtime/agent-session.js +3 -3
  14. package/dist/core/runtime/extension-core-bindings.d.ts +1 -0
  15. package/dist/core/runtime/extension-core-bindings.js +2 -2
  16. package/dist/extensions/builtin/AGENT.md +115 -115
  17. package/dist/extensions/builtin/browser/AGENT.md +17 -17
  18. package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
  19. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
  20. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
  21. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
  22. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
  23. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
  24. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
  25. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
  26. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
  27. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
  28. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
  29. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
  30. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
  31. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
  32. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
  33. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
  34. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
  35. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
  36. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
  37. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
  38. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
  39. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
  40. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
  41. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
  42. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
  43. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
  44. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
  45. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
  46. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
  47. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
  48. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
  49. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
  50. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
  51. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
  52. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
  53. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
  54. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
  55. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
  56. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
  57. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
  58. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
  59. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
  60. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
  61. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
  62. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
  63. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
  64. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
  65. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
  66. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
  67. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
  68. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
  69. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
  70. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
  71. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
  72. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
  73. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
  74. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
  75. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
  76. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
  77. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
  78. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
  79. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
  80. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
  81. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
  82. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
  83. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
  84. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
  85. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
  86. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
  87. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
  88. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
  89. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
  90. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
  91. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
  92. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
  93. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
  94. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
  95. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
  96. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
  97. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
  98. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
  99. package/dist/extensions/builtin/browser/browser.md +73 -73
  100. package/dist/extensions/builtin/browser/install.md +142 -142
  101. package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
  102. package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
  103. package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
  104. package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
  105. package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
  106. package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
  107. package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
  108. package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
  109. package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
  110. package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
  111. package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
  112. package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
  113. package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
  114. package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
  115. package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
  116. package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
  117. package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
  118. package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
  119. package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
  120. package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
  121. package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
  122. package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
  123. package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
  124. package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
  125. package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
  126. package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
  127. package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
  128. package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
  129. package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
  130. package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
  131. package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
  132. package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
  133. package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
  134. package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
  135. package/dist/extensions/builtin/goal/README.md +67 -67
  136. package/dist/extensions/builtin/goal/goal-controller.d.ts +39 -10
  137. package/dist/extensions/builtin/goal/goal-controller.js +1 -1
  138. package/dist/extensions/builtin/goal/goal-format.js +1 -1
  139. package/dist/extensions/builtin/goal/goal-prompts.d.ts +2 -0
  140. package/dist/extensions/builtin/goal/goal-prompts.js +5 -4
  141. package/dist/extensions/builtin/goal/goal-store.js +1 -1
  142. package/dist/extensions/builtin/goal/index.d.ts +1 -1
  143. package/dist/extensions/builtin/goal/index.js +10 -7
  144. package/dist/extensions/builtin/grub/README.md +112 -112
  145. package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
  146. package/dist/extensions/builtin/link-world/index.js +6 -6
  147. package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
  148. package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
  149. package/dist/extensions/builtin/link-world/linkworld.md +313 -313
  150. package/dist/extensions/builtin/link-world/{network-routing.md → network-routing/network-routing.md} +67 -67
  151. package/dist/extensions/builtin/loop/README.md +92 -92
  152. package/dist/extensions/builtin/mcp/figma-design.md +68 -68
  153. package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
  154. package/dist/extensions/builtin/plan/index.js +1 -1
  155. package/dist/extensions/builtin/recap/AGENT.md +15 -15
  156. package/dist/extensions/builtin/sal/README.md +72 -72
  157. package/dist/extensions/builtin/security-audit/README.md +289 -289
  158. package/dist/extensions/builtin/task/task-store.d.ts +4 -0
  159. package/dist/extensions/builtin/task/task-store.js +1 -1
  160. package/dist/extensions/builtin/team/AGENT.md +112 -112
  161. package/dist/extensions/builtin/team/TESTING.md +299 -299
  162. package/dist/extensions/builtin/token-save/README.md +56 -56
  163. package/dist/extensions/optional/AGENT.md +10 -10
  164. package/dist/index.d.ts +5 -30
  165. package/dist/index.js +1 -1
  166. package/dist/models.d.ts +7 -0
  167. package/dist/models.js +1 -0
  168. package/dist/modes/interactive/components/footer.js +1 -1
  169. package/dist/modes/interactive/components/task-status-panel.d.ts +36 -0
  170. package/dist/modes/interactive/components/task-status-panel.js +1 -0
  171. package/dist/modes/interactive/controllers/stream-render-controller.d.ts +7 -0
  172. package/dist/modes/interactive/controllers/stream-render-controller.js +2 -2
  173. package/dist/modes/interactive/interactive-mode.js +40 -40
  174. package/dist/modes/interactive/state/interactive-state.d.ts +2 -0
  175. package/dist/modes/interactive/state/interactive-state.js +1 -1
  176. package/dist/modes/interactive/theme/dark.json +85 -85
  177. package/dist/modes/interactive/theme/light.json +84 -84
  178. package/dist/modes/interactive/theme/theme-schema.json +335 -335
  179. package/dist/modes/interactive/theme/warm.json +81 -81
  180. package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
  181. package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +1 -1
  182. package/dist/node_modules/@pencil-agent/ai/dist/providers/anthropic.js +2 -2
  183. package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-completions.js +5 -5
  184. package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses.js +1 -1
  185. package/dist/node_modules/@pencil-agent/ai/dist/stream.js +1 -1
  186. package/dist/packages/protocol/src/commands.d.ts +33 -0
  187. package/dist/packages/protocol/src/flags.d.ts +20 -0
  188. package/dist/packages/protocol/src/hooks.d.ts +17 -0
  189. package/dist/packages/protocol/src/hooks.js +0 -0
  190. package/dist/packages/{extension-sdk → protocol}/src/index.d.ts +7 -4
  191. package/dist/packages/protocol/src/index.js +1 -0
  192. package/dist/packages/{extension-sdk → protocol}/src/lifecycle.d.ts +15 -27
  193. package/dist/packages/protocol/src/lifecycle.js +0 -0
  194. package/dist/packages/{extension-sdk → protocol}/src/tools.d.ts +1 -1
  195. package/dist/packages/protocol/src/tools.js +0 -0
  196. package/dist/public-config.d.ts +12 -0
  197. package/dist/public-config.js +1 -0
  198. package/dist/runtime.d.ts +9 -0
  199. package/dist/runtime.js +1 -0
  200. package/dist/session-compaction.d.ts +7 -0
  201. package/dist/session-compaction.js +1 -0
  202. package/dist/session.d.ts +7 -0
  203. package/dist/session.js +1 -0
  204. package/dist/skills.d.ts +7 -0
  205. package/dist/skills.js +1 -0
  206. package/dist/tools.d.ts +7 -0
  207. package/dist/tools.js +1 -0
  208. package/docs/ACP/345/215/217/350/256/256/351/233/206/346/210/220/345/274/200/345/217/221/346/226/207/346/241/243.md +851 -0
  209. package/docs/SDK-TESTING.md +364 -0
  210. package/docs/codex-goal-command-impl.md +1055 -1055
  211. package/docs/codex-goal-vs-grub.md +500 -500
  212. package/docs/custom-provider.md +27 -27
  213. package/docs/extensions.md +27 -27
  214. package/docs/keybindings.md +27 -27
  215. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
  216. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
  217. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
  218. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -158
  219. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -128
  220. package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
  221. package/docs/loop-usage-examples.md +214 -214
  222. package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
  223. package/docs/models.md +27 -27
  224. package/docs/packages.md +27 -27
  225. package/docs/pi-design-philosophy.md +457 -457
  226. package/docs/planmode.md +1987 -1987
  227. package/docs/prompt-templates.md +27 -27
  228. package/docs/providers.md +27 -27
  229. package/docs/sdk.md +27 -27
  230. package/docs/skills.md +27 -27
  231. package/docs/startup-performance-optimization.md +301 -0
  232. package/docs/themes.md +27 -27
  233. package/docs/tui.md +27 -27
  234. package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
  235. package/package.json +190 -162
  236. package/dist/packages/extension-sdk/src/index.js +0 -1
  237. package/docs/cc-agent-design.md +0 -1297
  238. package/docs/cc-tui-design.md +0 -1333
  239. package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
  240. /package/dist/packages/{extension-sdk/src/lifecycle.js → protocol/src/commands.js} +0 -0
  241. /package/dist/packages/{extension-sdk/src/tools.js → protocol/src/flags.js} +0 -0
@@ -1,493 +1,493 @@
1
- # FRED — Federal Reserve Economic Data
2
-
3
- `https://fred.stlouisfed.org` / `https://api.stlouisfed.org` — the canonical source for US macroeconomic time series (800,000+ series). The REST API at `api.stlouisfed.org` requires a free registered key. The web endpoints at `fred.stlouisfed.org` (CSV, JSON, HTML) are all blocked to headless HTTP — they consistently timeout with no response. For zero-key access use the BLS API (unemployment, CPI, payrolls) or World Bank API (GDP, growth rates, annual data).
4
-
5
- ## Do this first
6
-
7
- **Decision tree: pick one approach.**
8
-
9
- ```
10
- Need GDP, CPI, UNRATE, payrolls only? → use BLS + World Bank (no key, free forever)
11
- Need FEDFUNDS, DGS10, SP500, any FRED series? → get a free FRED API key (5 min)
12
- Need browser-visible chart data? → use CDP to intercept network requests
13
- ```
14
-
15
- **The web CSV/JSON/TXT URLs all timeout — do NOT attempt them:**
16
- ```python
17
- # ALL OF THESE TIMEOUT — confirmed dead from headless HTTP:
18
- # https://fred.stlouisfed.org/graph/fredgraph.csv?id=GDP ← timeout
19
- # https://fred.stlouisfed.org/graph/fredgraph.json?id=GDP ← timeout
20
- # https://fred.stlouisfed.org/data/GDP.txt ← timeout
21
- # https://fred.stlouisfed.org/series/GDP ← timeout
22
- ```
23
-
24
- ## Getting a free FRED API key
25
-
26
- 1. Go to `https://fred.stlouisfed.org/docs/api/api_key.html`
27
- 2. Click "Request or view your API Keys"
28
- 3. Sign in / register (free St. Louis Fed account)
29
- 4. Key appears immediately — it's a 32-character lowercase alphanumeric string
30
-
31
- The key is free, instant, and unlimited for reasonable use (120 req/min cap).
32
-
33
- ---
34
-
35
- ## Option A: FRED REST API (requires free key, 800K+ series)
36
-
37
- The only way to get FRED data programmatically. Set `FRED_KEY` in your `.env` file.
38
-
39
- ```python
40
- import json, os
41
- FRED_KEY = os.environ["FRED_KEY"] # 32-char lowercase alphanumeric
42
- BASE = "https://api.stlouisfed.org/fred"
43
- ```
44
-
45
- ### Series metadata
46
-
47
- ```python
48
- import json, os
49
- FRED_KEY = os.environ["FRED_KEY"]
50
- BASE = "https://api.stlouisfed.org/fred"
51
-
52
- meta = json.loads(http_get(f"{BASE}/series?series_id=GDP&api_key={FRED_KEY}&file_type=json"))
53
- s = meta['seriess'][0]
54
- print(s['title']) # "Gross Domestic Product"
55
- print(s['observation_start']) # "1947-01-01"
56
- print(s['observation_end']) # "2025-10-01"
57
- print(s['frequency']) # "Quarterly"
58
- print(s['frequency_short']) # "Q"
59
- print(s['units']) # "Billions of Dollars"
60
- print(s['units_short']) # "Bil. of $"
61
- print(s['seasonal_adjustment']) # "Seasonally Adjusted Annual Rate"
62
- print(s['popularity']) # 81 (0-100)
63
- print(s['last_updated']) # "2025-12-19 08:00:06-06"
64
- ```
65
-
66
- ### Observations (the actual data)
67
-
68
- ```python
69
- import json, os
70
- FRED_KEY = os.environ["FRED_KEY"]
71
- BASE = "https://api.stlouisfed.org/fred"
72
-
73
- # Latest 10 values, most recent first
74
- obs = json.loads(http_get(
75
- f"{BASE}/series/observations"
76
- f"?series_id=GDP"
77
- f"&api_key={FRED_KEY}"
78
- f"&file_type=json"
79
- f"&limit=10"
80
- f"&sort_order=desc" # "desc" = newest first, "asc" = oldest first (default)
81
- ))
82
- print(obs['count']) # 314 (total observations)
83
- print(obs['observation_start']) # "1947-01-01" (what's in the full series)
84
-
85
- for o in obs['observations']:
86
- date = o['date'] # "2025-10-01"
87
- value = o['value'] # "29726.4" — always a STRING, may be "." for missing
88
- if value != '.':
89
- print(f"{date}: ${float(value):,.1f}B")
90
- # 2025-10-01: $29,726.4B
91
- # 2025-07-01: $29,339.1B
92
- # 2025-04-01: $29,119.3B
93
- ```
94
-
95
- ### Date-range filtering
96
-
97
- ```python
98
- import json, os
99
- FRED_KEY = os.environ["FRED_KEY"]
100
- BASE = "https://api.stlouisfed.org/fred"
101
-
102
- obs = json.loads(http_get(
103
- f"{BASE}/series/observations"
104
- f"?series_id=UNRATE"
105
- f"&api_key={FRED_KEY}"
106
- f"&file_type=json"
107
- f"&observation_start=2020-01-01"
108
- f"&observation_end=2024-12-31"
109
- f"&sort_order=desc"
110
- ))
111
- for o in obs['observations'][:5]:
112
- print(f"{o['date']}: {o['value']}%")
113
- # 2024-12-01: 4.1%
114
- # 2024-11-01: 4.2%
115
- # 2024-10-01: 4.1%
116
- ```
117
-
118
- ### Key series IDs
119
-
120
- | FRED ID | Description | Frequency | Unit |
121
- |---------|-------------|-----------|------|
122
- | `GDP` | Gross Domestic Product | Quarterly | Billions of $, SAAR |
123
- | `GDPC1` | Real GDP (chained 2017 $) | Quarterly | Billions of chained 2017 $ |
124
- | `UNRATE` | Unemployment Rate | Monthly | Percent, SA |
125
- | `CPIAUCSL` | CPI: All Urban Consumers, SA | Monthly | Index 1982-84=100 |
126
- | `CPIAUCNS` | CPI: All Urban Consumers, not SA | Monthly | Index 1982-84=100 |
127
- | `FEDFUNDS` | Federal Funds Effective Rate | Monthly | Percent |
128
- | `DFF` | Federal Funds Rate (daily) | Daily | Percent |
129
- | `DGS10` | 10-Year Treasury Constant Maturity | Daily | Percent |
130
- | `DGS2` | 2-Year Treasury | Daily | Percent |
131
- | `SP500` | S&P 500 | Daily | Index |
132
- | `NASDAQCOM` | NASDAQ Composite | Daily | Index |
133
- | `PAYEMS` | Total Nonfarm Payrolls | Monthly | Thousands of persons, SA |
134
- | `PCEPI` | PCE Price Index | Monthly | Index 2017=100, SA |
135
- | `PCEPILFE` | Core PCE Price Index | Monthly | Index 2017=100, SA |
136
- | `DCOILBRENTEU` | Brent Crude Oil | Daily | $ per Barrel |
137
- | `DEXUSEU` | USD/EUR Exchange Rate | Daily | USD per EUR |
138
- | `M2SL` | M2 Money Stock | Monthly | Billions of $, SA |
139
- | `MORTGAGE30US` | 30-Year Fixed Mortgage Rate | Weekly | Percent |
140
-
141
- ### Series search
142
-
143
- ```python
144
- import json, os
145
- FRED_KEY = os.environ["FRED_KEY"]
146
- BASE = "https://api.stlouisfed.org/fred"
147
-
148
- results = json.loads(http_get(
149
- f"{BASE}/series/search"
150
- f"?search_text=unemployment+rate"
151
- f"&api_key={FRED_KEY}"
152
- f"&file_type=json"
153
- f"&limit=5"
154
- f"&order_by=popularity" # "popularity" | "search_rank" | "series_id" | "title" | "units" | "frequency" | "seasonal_adjustment" | "realtime_start" | "realtime_end" | "last_updated" | "observation_start" | "observation_end"
155
- f"&sort_order=desc" # most popular first
156
- ))
157
- for s in results['seriess']:
158
- print(f"{s['id']}: {s['title']} ({s['frequency_short']}, {s['units_short']})")
159
- # UNRATE: Unemployment Rate (M, %)
160
- # UNEMPLOY: Unemployment Level (M, Thous. of Persons)
161
- ```
162
-
163
- ### Multiple series — parallel fetch
164
-
165
- ```python
166
- import json, os
167
- from concurrent.futures import ThreadPoolExecutor
168
- FRED_KEY = os.environ["FRED_KEY"]
169
- BASE = "https://api.stlouisfed.org/fred"
170
-
171
- def fetch_latest(series_id):
172
- obs = json.loads(http_get(
173
- f"{BASE}/series/observations?series_id={series_id}"
174
- f"&api_key={FRED_KEY}&file_type=json&limit=1&sort_order=desc"
175
- ))
176
- o = obs['observations'][0]
177
- return series_id, o['date'], o['value']
178
-
179
- series_ids = ["GDP", "UNRATE", "CPIAUCSL", "FEDFUNDS", "DGS10", "SP500"]
180
- with ThreadPoolExecutor(max_workers=6) as ex:
181
- results = list(ex.map(fetch_latest, series_ids))
182
-
183
- for sid, date, val in results:
184
- print(f"{sid:15} {date}: {val}")
185
- # GDP 2025-10-01: 29726.4
186
- # UNRATE 2026-03-01: 4.3
187
- # CPIAUCSL 2026-02-01: 321.457
188
- # FEDFUNDS 2026-03-01: 4.33
189
- # DGS10 2026-04-17: 4.34
190
- # SP500 2026-04-17: 5282.70
191
- # Confirmed: 6 parallel requests complete in ~0.4s
192
- ```
193
-
194
- ### Parse observations into a list of (date, float) tuples
195
-
196
- ```python
197
- import json, os
198
- FRED_KEY = os.environ["FRED_KEY"]
199
- BASE = "https://api.stlouisfed.org/fred"
200
-
201
- obs = json.loads(http_get(
202
- f"{BASE}/series/observations?series_id=DGS10&api_key={FRED_KEY}&file_type=json"
203
- f"&observation_start=2024-01-01&sort_order=asc"
204
- ))
205
-
206
- data = [
207
- (o['date'], float(o['value']))
208
- for o in obs['observations']
209
- if o['value'] != '.' # '.' = missing value, skip it
210
- ]
211
- print(f"{len(data)} observations")
212
- print(f"First: {data[0]}") # ('2024-01-02', 3.91)
213
- print(f"Last: {data[-1]}") # ('2026-04-17', 4.34)
214
- ```
215
-
216
- ### Handle errors
217
-
218
- ```python
219
- import urllib.error, json
220
-
221
- try:
222
- r = http_get(f"https://api.stlouisfed.org/fred/series?series_id=BADID&api_key={FRED_KEY}&file_type=json")
223
- print(json.loads(r))
224
- except urllib.error.HTTPError as e:
225
- err = json.loads(e.read().decode())
226
- # err['error_code'] → 400
227
- # err['error_message'] → "Bad Request. The series does not exist."
228
- print(f"FRED error {err['error_code']}: {err['error_message']}")
229
- ```
230
-
231
- ---
232
-
233
- ## Option B: BLS API (no key required, confirmed live)
234
-
235
- Bureau of Labor Statistics. Covers unemployment, CPI, payrolls — the most-queried FRED series. **Without a key: 10 requests/day limit.** Free key registration at `https://www.bls.gov/developers/` gives 500 req/day and 10 years of data per call (vs 3 years without key).
236
-
237
- ```python
238
- import json
239
- # Single series GET — no auth needed
240
- r = http_get("https://api.bls.gov/publicAPI/v2/timeseries/data/LNS14000000?startyear=2024&endyear=2024")
241
- data = json.loads(r)
242
- # data['status'] == 'REQUEST_SUCCEEDED'
243
- series = data['Results']['series'][0]
244
- for point in series['data'][:3]:
245
- print(f"{point['year']}-{point['period']} ({point['periodName']}): {point['value']}")
246
- # 2024-M12 (December): 4.1
247
- # 2024-M11 (November): 4.2
248
- # 2024-M10 (October): 4.1
249
- ```
250
-
251
- ### Multi-series POST (single call, multiple series)
252
-
253
- ```python
254
- import json, urllib.request
255
-
256
- payload = json.dumps({
257
- "seriesid": ["LNS14000000", "CUSR0000SA0", "CES0000000001"],
258
- "startyear": "2023",
259
- "endyear": "2024"
260
- # "registrationkey": "YOUR_BLS_KEY" # optional: lifts to 500/day, 10yr range
261
- }).encode()
262
-
263
- req = urllib.request.Request(
264
- "https://api.bls.gov/publicAPI/v2/timeseries/data/",
265
- data=payload,
266
- headers={"Content-Type": "application/json"}
267
- )
268
- with urllib.request.urlopen(req, timeout=20) as resp:
269
- data = json.loads(resp.read().decode())
270
-
271
- for s in data['Results']['series']:
272
- pts = s['data']
273
- print(f"{s['seriesID']}: {len(pts)} points, latest={pts[0]['value']}")
274
- # LNS14000000: 24 points, latest=4.1 (unemployment %)
275
- # CUSR0000SA0: 24 points, latest=317.604 (CPI index)
276
- # CES0000000001: 24 points, latest=158316 (nonfarm payrolls, thousands)
277
- ```
278
-
279
- ### Key BLS series (FRED equivalents)
280
-
281
- | BLS Series ID | FRED Equivalent | Description |
282
- |---------------|-----------------|-------------|
283
- | `LNS14000000` | `UNRATE` | Unemployment rate, SA (%) |
284
- | `CUSR0000SA0` | `CPIAUCSL` | CPI-U All Urban, SA |
285
- | `CUUR0000SA0` | `CPIAUCNS` | CPI-U All Urban, not SA |
286
- | `CUSR0000SA0L1E` | `CPILFESL` | CPI less food and energy, SA |
287
- | `CES0000000001` | `PAYEMS` | Total nonfarm payrolls (thousands) |
288
- | `LNS11000000` | `CLF16OV` | Civilian labor force (thousands) |
289
- | `LNS12000000` | `CE16OV` | Civilian employment (thousands) |
290
-
291
- ### BLS rate limits
292
-
293
- | | Without key | With free key |
294
- |--|--|--|
295
- | Requests/day | **10** (confirmed: call 11 returns `REQUEST_NOT_PROCESSED`) | 500 |
296
- | Series per request | 25 | 50 |
297
- | Years per request | 3 | 10 |
298
- | Daily or seasonal adjustment | No | Yes |
299
-
300
- ---
301
-
302
- ## Option C: World Bank API (no key, unlimited, annual data)
303
-
304
- Free, no registration, no rate limit observed (10 rapid calls completed in 2.0s). Annual data only — no monthly or quarterly frequency.
305
-
306
- ```python
307
- import json
308
-
309
- # Single country, single indicator
310
- r = http_get("https://api.worldbank.org/v2/country/US/indicator/NY.GDP.MKTP.CD?format=json&per_page=5&mrv=5")
311
- data = json.loads(r)
312
- page_info = data[0] # {'page': 1, 'pages': 1, 'per_page': 5, 'total': 5, 'lastupdated': '2026-04-08'}
313
- items = data[1] # list of observations
314
-
315
- for item in items:
316
- if item['value']:
317
- print(f"{item['date']}: ${item['value']/1e12:.2f}T")
318
- # 2024: $28.75T
319
- # 2023: $27.29T
320
- # 2022: $25.60T
321
- ```
322
-
323
- ### Date range filter and multi-country
324
-
325
- ```python
326
- import json
327
-
328
- # Historical range: date=YYYY:YYYY
329
- r = http_get("https://api.worldbank.org/v2/country/US/indicator/FP.CPI.TOTL.ZG?format=json&date=2015:2024&per_page=15")
330
- data = json.loads(r)
331
- items = [i for i in data[1] if i['value'] is not None]
332
- for item in items:
333
- print(f"{item['date']}: {item['value']:.2f}%")
334
- # 2024: 2.95%
335
- # 2023: 4.12%
336
- # 2022: 8.00%
337
- # ...
338
-
339
- # Multi-country: semicolon-separated ISO codes
340
- r = http_get("https://api.worldbank.org/v2/country/US;CN;DE;JP;GB/indicator/NY.GDP.MKTP.CD?format=json&date=2023&per_page=10")
341
- data = json.loads(r)
342
- items = sorted([i for i in data[1] if i['value']], key=lambda x: x['value'], reverse=True)
343
- for item in items:
344
- print(f"{item['country']['value']}: ${item['value']/1e12:.2f}T")
345
- # United States: $27.29T
346
- # China: $18.27T
347
- # Germany: $4.56T
348
- ```
349
-
350
- ### Key World Bank indicators (FRED equivalents)
351
-
352
- | WB Indicator Code | FRED Equivalent | Description |
353
- |-------------------|-----------------|-------------|
354
- | `NY.GDP.MKTP.CD` | `GDP` | GDP, current USD |
355
- | `NY.GDP.MKTP.KD.ZG` | `A191RL1Q225SBEA` | GDP growth rate (%) |
356
- | `NY.GDP.PCAP.CD` | `A939RX0Q048SBEA` | GDP per capita (USD) |
357
- | `FP.CPI.TOTL.ZG` | `FPCPITOTLZGUSA` | CPI inflation, annual % |
358
- | `FP.CPI.TOTL` | `CPIAUCSL` (annual) | CPI level, 2010=100 |
359
- | `SL.UEM.TOTL.ZS` | `UNRATE` (annual) | Unemployment rate, ILO model |
360
- | `CM.MKT.LCAP.GD.ZS` | — | Stock market cap / GDP ratio |
361
-
362
- ---
363
-
364
- ## Option D: Alpha Vantage (free registered key, select indicators)
365
-
366
- Some economic indicators work with the `demo` key (no registration); most require a free registered key (25 requests/day, instant signup at `https://www.alphavantage.co/support/#api-key`).
367
-
368
- ```python
369
- import json
370
- AV_KEY = "demo" # or your registered key
371
-
372
- # Unemployment rate (works with demo key — confirmed)
373
- r = http_get(f"https://www.alphavantage.co/query?function=UNEMPLOYMENT&apikey={AV_KEY}")
374
- data = json.loads(r)
375
- # data['name'] = 'Unemployment Rate'
376
- # data['interval'] = 'monthly'
377
- # data['unit'] = 'percent'
378
- # data['data'] → list of {date, value}, newest first
379
-
380
- print(data['data'][0]) # {'date': '2026-03-01', 'value': '4.3'}
381
- print(f"Total: {len(data['data'])} months since {data['data'][-1]['date']}")
382
- # Total: 939 months since 1948-01-01
383
- ```
384
-
385
- ### Which indicators work with demo vs registered key
386
-
387
- | Function | demo key | Registered key |
388
- |----------|----------|----------------|
389
- | `UNEMPLOYMENT` | YES | YES |
390
- | `INFLATION` | YES (annual) | YES |
391
- | `RETAIL_SALES` | YES | YES |
392
- | `DURABLES` | YES | YES |
393
- | `NONFARM_PAYROLL` | YES | YES |
394
- | `REAL_GDP_PER_CAPITA` | YES | YES |
395
- | `REAL_GDP` | NO (rate-limited) | YES |
396
- | `CPI` | NO (rate-limited) | YES |
397
- | `FEDERAL_FUNDS_RATE` | NO (rate-limited) | YES |
398
- | `TREASURY_YIELD` | NO (rate-limited) | YES |
399
- | `CONSUMER_SENTIMENT` | NO (rate-limited) | YES |
400
-
401
- ```python
402
- import json
403
- AV_KEY = "YOUR_FREE_KEY" # from alphavantage.co/support/#api-key
404
-
405
- # Federal Funds Rate — monthly (requires registered key)
406
- r = http_get(f"https://www.alphavantage.co/query?function=FEDERAL_FUNDS_RATE&interval=monthly&apikey={AV_KEY}")
407
- data = json.loads(r)
408
- for item in data['data'][:3]:
409
- print(f"{item['date']}: {item['value']}%")
410
- # 2026-03-01: 4.33%
411
- # 2026-02-01: 4.33%
412
- # 2026-01-01: 4.33%
413
-
414
- # 10-Year Treasury Yield
415
- r = http_get(f"https://www.alphavantage.co/query?function=TREASURY_YIELD&maturity=10year&interval=monthly&apikey={AV_KEY}")
416
- data = json.loads(r)
417
- print(data['data'][0]) # {'date': '2026-04-17', 'value': '4.34'}
418
- ```
419
-
420
- ---
421
-
422
- ## Option E: Browser + CDP (for interactive FRED charts)
423
-
424
- When you need data from `fred.stlouisfed.org` that has no API equivalent (custom chart combos, release dates visible on page) — or when you have no API key — use the browser.
425
-
426
- ```python
427
- # Navigate to a series page
428
- goto_url("https://fred.stlouisfed.org/series/GDP")
429
- wait_for_load()
430
-
431
- # Option 1: Intercept the fredgraph XHR that the chart fires
432
- # The page's chart JS calls fredgraph.csv internally — intercept it
433
- events = drain_events()
434
- # Look for network events with fredgraph.csv in URL
435
-
436
- # Option 2: Extract the latest value from the page text
437
- latest_val = js("""
438
- // The last observation appears in the meta section
439
- const el = document.querySelector('.series-meta-observation-end');
440
- el ? el.textContent.trim() : null
441
- """)
442
-
443
- # Option 3: Read the data table if present
444
- table_data = js("""
445
- const rows = Array.from(document.querySelectorAll('table.series-observations tr'));
446
- rows.map(r => {
447
- const cells = r.querySelectorAll('td');
448
- return cells.length >= 2 ? [cells[0].textContent.trim(), cells[1].textContent.trim()] : null;
449
- }).filter(Boolean);
450
- """)
451
- ```
452
-
453
- ---
454
-
455
- ## Rate limits
456
-
457
- | API | Limit | Notes |
458
- |-----|-------|-------|
459
- | FRED REST API | 120 req/min | With registered key (free) |
460
- | FRED REST API | blocked | Without key — HTTP 400 |
461
- | BLS (no key) | 10 req/day | Confirmed: call 11 → `REQUEST_NOT_PROCESSED` |
462
- | BLS (with key) | 500 req/day, 50 series/req | Free registration at bls.gov/developers |
463
- | World Bank | No limit observed | 10 rapid calls: 2.0s, no 429 |
464
- | Alpha Vantage (demo) | 2 req/sec | Demo key rate-limited for most functions |
465
- | Alpha Vantage (free key) | 25 req/day | Free at alphavantage.co/support/#api-key |
466
-
467
- ---
468
-
469
- ## Gotchas
470
-
471
- - **fred.stlouisfed.org web endpoints ALL timeout** — The CSV download (`fredgraph.csv`), JSON graph (`fredgraph.json`), text format (`/data/*.txt`), and HTML series pages all hang indefinitely from headless HTTP. This is not a UA or header issue — the server simply does not respond to non-browser connections. Confirmed with multiple UA strings, TCP connect succeeds but no HTTP response is sent.
472
-
473
- - **FRED API key is mandatory and must be exactly 32 lowercase alphanumeric chars** — "test", "demo", "guest", and keys shorter/longer than 32 chars all return HTTP 400: `"not a 32 character alpha-numeric lower-case string"`. An unregistered 32-char key returns: `"not registered"`.
474
-
475
- - **Observation values are always strings, not numbers** — The `value` field in FRED observations is always a JSON string: `"4.1"`, not `4.1`. Also `"."` (dot) means missing/not-yet-released. Always check `if o['value'] != '.'` before `float(o['value'])`.
476
-
477
- - **BLS 10 req/day without key burns fast** — The limit is per-IP per-day. 10 calls is exhausted in one moderate script run. Either register a free BLS key immediately or use World Bank for the same data annually.
478
-
479
- - **BLS data range: 3 years without key, 10 years with key** — Requesting `startyear=2000&endyear=2024` without a key silently truncates to the most recent 3 years. With a key it returns up to 10 years and includes a `message` field if the range was truncated: `['Year range has been reduced to the system-allowed limit of 10 years.']`.
480
-
481
- - **World Bank is annual only** — No monthly or quarterly data. For monthly UNRATE or CPI, use BLS. For quarterly GDP, use FRED API or Alpha Vantage `REAL_GDP`.
482
-
483
- - **World Bank response is a 2-element array** — `data[0]` is pagination metadata, `data[1]` is the observations list. Missing years have `value: null` (not `"."`). Filter with `if item['value'] is not None`.
484
-
485
- - **Alpha Vantage demo key: 2 req/sec, covers only 6 economic functions** — The other 6 economic functions (`REAL_GDP`, `CPI`, `TREASURY_YIELD`, etc.) return `{"Information": "The demo API key is for demo purposes only..."}`. No error code — just check for the `Information` key in the response.
486
-
487
- - **FRED `sort_order=desc` returns newest first** — Default is `asc` (oldest first, starting from observation_start). For "get the latest value" use `limit=1&sort_order=desc`.
488
-
489
- - **FRED series IDs are case-sensitive and exact** — `gdp` returns an error; must be `GDP`. Check `fred.stlouisfed.org/series/{ID}` to verify a series exists before scripting.
490
-
491
- - **Some FRED series have gaps** — Daily series like `DGS10` and `SP500` skip weekends and holidays. Those dates simply don't appear in the observations array (not represented as `"."`). Weekly and monthly series use the first day of the period as the date (e.g., `2024-01-01` = January 2024).
492
-
493
- - **FRED `realtime_start`/`realtime_end` in observations** — Every observation has these fields reflecting vintage data. For current data, ignore them. They matter only for "real-time" research (what was the published value on a specific past date).
1
+ # FRED — Federal Reserve Economic Data
2
+
3
+ `https://fred.stlouisfed.org` / `https://api.stlouisfed.org` — the canonical source for US macroeconomic time series (800,000+ series). The REST API at `api.stlouisfed.org` requires a free registered key. The web endpoints at `fred.stlouisfed.org` (CSV, JSON, HTML) are all blocked to headless HTTP — they consistently timeout with no response. For zero-key access use the BLS API (unemployment, CPI, payrolls) or World Bank API (GDP, growth rates, annual data).
4
+
5
+ ## Do this first
6
+
7
+ **Decision tree: pick one approach.**
8
+
9
+ ```
10
+ Need GDP, CPI, UNRATE, payrolls only? → use BLS + World Bank (no key, free forever)
11
+ Need FEDFUNDS, DGS10, SP500, any FRED series? → get a free FRED API key (5 min)
12
+ Need browser-visible chart data? → use CDP to intercept network requests
13
+ ```
14
+
15
+ **The web CSV/JSON/TXT URLs all timeout — do NOT attempt them:**
16
+ ```python
17
+ # ALL OF THESE TIMEOUT — confirmed dead from headless HTTP:
18
+ # https://fred.stlouisfed.org/graph/fredgraph.csv?id=GDP ← timeout
19
+ # https://fred.stlouisfed.org/graph/fredgraph.json?id=GDP ← timeout
20
+ # https://fred.stlouisfed.org/data/GDP.txt ← timeout
21
+ # https://fred.stlouisfed.org/series/GDP ← timeout
22
+ ```
23
+
24
+ ## Getting a free FRED API key
25
+
26
+ 1. Go to `https://fred.stlouisfed.org/docs/api/api_key.html`
27
+ 2. Click "Request or view your API Keys"
28
+ 3. Sign in / register (free St. Louis Fed account)
29
+ 4. Key appears immediately — it's a 32-character lowercase alphanumeric string
30
+
31
+ The key is free, instant, and unlimited for reasonable use (120 req/min cap).
32
+
33
+ ---
34
+
35
+ ## Option A: FRED REST API (requires free key, 800K+ series)
36
+
37
+ The only way to get FRED data programmatically. Set `FRED_KEY` in your `.env` file.
38
+
39
+ ```python
40
+ import json, os
41
+ FRED_KEY = os.environ["FRED_KEY"] # 32-char lowercase alphanumeric
42
+ BASE = "https://api.stlouisfed.org/fred"
43
+ ```
44
+
45
+ ### Series metadata
46
+
47
+ ```python
48
+ import json, os
49
+ FRED_KEY = os.environ["FRED_KEY"]
50
+ BASE = "https://api.stlouisfed.org/fred"
51
+
52
+ meta = json.loads(http_get(f"{BASE}/series?series_id=GDP&api_key={FRED_KEY}&file_type=json"))
53
+ s = meta['seriess'][0]
54
+ print(s['title']) # "Gross Domestic Product"
55
+ print(s['observation_start']) # "1947-01-01"
56
+ print(s['observation_end']) # "2025-10-01"
57
+ print(s['frequency']) # "Quarterly"
58
+ print(s['frequency_short']) # "Q"
59
+ print(s['units']) # "Billions of Dollars"
60
+ print(s['units_short']) # "Bil. of $"
61
+ print(s['seasonal_adjustment']) # "Seasonally Adjusted Annual Rate"
62
+ print(s['popularity']) # 81 (0-100)
63
+ print(s['last_updated']) # "2025-12-19 08:00:06-06"
64
+ ```
65
+
66
+ ### Observations (the actual data)
67
+
68
+ ```python
69
+ import json, os
70
+ FRED_KEY = os.environ["FRED_KEY"]
71
+ BASE = "https://api.stlouisfed.org/fred"
72
+
73
+ # Latest 10 values, most recent first
74
+ obs = json.loads(http_get(
75
+ f"{BASE}/series/observations"
76
+ f"?series_id=GDP"
77
+ f"&api_key={FRED_KEY}"
78
+ f"&file_type=json"
79
+ f"&limit=10"
80
+ f"&sort_order=desc" # "desc" = newest first, "asc" = oldest first (default)
81
+ ))
82
+ print(obs['count']) # 314 (total observations)
83
+ print(obs['observation_start']) # "1947-01-01" (what's in the full series)
84
+
85
+ for o in obs['observations']:
86
+ date = o['date'] # "2025-10-01"
87
+ value = o['value'] # "29726.4" — always a STRING, may be "." for missing
88
+ if value != '.':
89
+ print(f"{date}: ${float(value):,.1f}B")
90
+ # 2025-10-01: $29,726.4B
91
+ # 2025-07-01: $29,339.1B
92
+ # 2025-04-01: $29,119.3B
93
+ ```
94
+
95
+ ### Date-range filtering
96
+
97
+ ```python
98
+ import json, os
99
+ FRED_KEY = os.environ["FRED_KEY"]
100
+ BASE = "https://api.stlouisfed.org/fred"
101
+
102
+ obs = json.loads(http_get(
103
+ f"{BASE}/series/observations"
104
+ f"?series_id=UNRATE"
105
+ f"&api_key={FRED_KEY}"
106
+ f"&file_type=json"
107
+ f"&observation_start=2020-01-01"
108
+ f"&observation_end=2024-12-31"
109
+ f"&sort_order=desc"
110
+ ))
111
+ for o in obs['observations'][:5]:
112
+ print(f"{o['date']}: {o['value']}%")
113
+ # 2024-12-01: 4.1%
114
+ # 2024-11-01: 4.2%
115
+ # 2024-10-01: 4.1%
116
+ ```
117
+
118
+ ### Key series IDs
119
+
120
+ | FRED ID | Description | Frequency | Unit |
121
+ |---------|-------------|-----------|------|
122
+ | `GDP` | Gross Domestic Product | Quarterly | Billions of $, SAAR |
123
+ | `GDPC1` | Real GDP (chained 2017 $) | Quarterly | Billions of chained 2017 $ |
124
+ | `UNRATE` | Unemployment Rate | Monthly | Percent, SA |
125
+ | `CPIAUCSL` | CPI: All Urban Consumers, SA | Monthly | Index 1982-84=100 |
126
+ | `CPIAUCNS` | CPI: All Urban Consumers, not SA | Monthly | Index 1982-84=100 |
127
+ | `FEDFUNDS` | Federal Funds Effective Rate | Monthly | Percent |
128
+ | `DFF` | Federal Funds Rate (daily) | Daily | Percent |
129
+ | `DGS10` | 10-Year Treasury Constant Maturity | Daily | Percent |
130
+ | `DGS2` | 2-Year Treasury | Daily | Percent |
131
+ | `SP500` | S&P 500 | Daily | Index |
132
+ | `NASDAQCOM` | NASDAQ Composite | Daily | Index |
133
+ | `PAYEMS` | Total Nonfarm Payrolls | Monthly | Thousands of persons, SA |
134
+ | `PCEPI` | PCE Price Index | Monthly | Index 2017=100, SA |
135
+ | `PCEPILFE` | Core PCE Price Index | Monthly | Index 2017=100, SA |
136
+ | `DCOILBRENTEU` | Brent Crude Oil | Daily | $ per Barrel |
137
+ | `DEXUSEU` | USD/EUR Exchange Rate | Daily | USD per EUR |
138
+ | `M2SL` | M2 Money Stock | Monthly | Billions of $, SA |
139
+ | `MORTGAGE30US` | 30-Year Fixed Mortgage Rate | Weekly | Percent |
140
+
141
+ ### Series search
142
+
143
+ ```python
144
+ import json, os
145
+ FRED_KEY = os.environ["FRED_KEY"]
146
+ BASE = "https://api.stlouisfed.org/fred"
147
+
148
+ results = json.loads(http_get(
149
+ f"{BASE}/series/search"
150
+ f"?search_text=unemployment+rate"
151
+ f"&api_key={FRED_KEY}"
152
+ f"&file_type=json"
153
+ f"&limit=5"
154
+ f"&order_by=popularity" # "popularity" | "search_rank" | "series_id" | "title" | "units" | "frequency" | "seasonal_adjustment" | "realtime_start" | "realtime_end" | "last_updated" | "observation_start" | "observation_end"
155
+ f"&sort_order=desc" # most popular first
156
+ ))
157
+ for s in results['seriess']:
158
+ print(f"{s['id']}: {s['title']} ({s['frequency_short']}, {s['units_short']})")
159
+ # UNRATE: Unemployment Rate (M, %)
160
+ # UNEMPLOY: Unemployment Level (M, Thous. of Persons)
161
+ ```
162
+
163
+ ### Multiple series — parallel fetch
164
+
165
+ ```python
166
+ import json, os
167
+ from concurrent.futures import ThreadPoolExecutor
168
+ FRED_KEY = os.environ["FRED_KEY"]
169
+ BASE = "https://api.stlouisfed.org/fred"
170
+
171
+ def fetch_latest(series_id):
172
+ obs = json.loads(http_get(
173
+ f"{BASE}/series/observations?series_id={series_id}"
174
+ f"&api_key={FRED_KEY}&file_type=json&limit=1&sort_order=desc"
175
+ ))
176
+ o = obs['observations'][0]
177
+ return series_id, o['date'], o['value']
178
+
179
+ series_ids = ["GDP", "UNRATE", "CPIAUCSL", "FEDFUNDS", "DGS10", "SP500"]
180
+ with ThreadPoolExecutor(max_workers=6) as ex:
181
+ results = list(ex.map(fetch_latest, series_ids))
182
+
183
+ for sid, date, val in results:
184
+ print(f"{sid:15} {date}: {val}")
185
+ # GDP 2025-10-01: 29726.4
186
+ # UNRATE 2026-03-01: 4.3
187
+ # CPIAUCSL 2026-02-01: 321.457
188
+ # FEDFUNDS 2026-03-01: 4.33
189
+ # DGS10 2026-04-17: 4.34
190
+ # SP500 2026-04-17: 5282.70
191
+ # Confirmed: 6 parallel requests complete in ~0.4s
192
+ ```
193
+
194
+ ### Parse observations into a list of (date, float) tuples
195
+
196
+ ```python
197
+ import json, os
198
+ FRED_KEY = os.environ["FRED_KEY"]
199
+ BASE = "https://api.stlouisfed.org/fred"
200
+
201
+ obs = json.loads(http_get(
202
+ f"{BASE}/series/observations?series_id=DGS10&api_key={FRED_KEY}&file_type=json"
203
+ f"&observation_start=2024-01-01&sort_order=asc"
204
+ ))
205
+
206
+ data = [
207
+ (o['date'], float(o['value']))
208
+ for o in obs['observations']
209
+ if o['value'] != '.' # '.' = missing value, skip it
210
+ ]
211
+ print(f"{len(data)} observations")
212
+ print(f"First: {data[0]}") # ('2024-01-02', 3.91)
213
+ print(f"Last: {data[-1]}") # ('2026-04-17', 4.34)
214
+ ```
215
+
216
+ ### Handle errors
217
+
218
+ ```python
219
+ import urllib.error, json
220
+
221
+ try:
222
+ r = http_get(f"https://api.stlouisfed.org/fred/series?series_id=BADID&api_key={FRED_KEY}&file_type=json")
223
+ print(json.loads(r))
224
+ except urllib.error.HTTPError as e:
225
+ err = json.loads(e.read().decode())
226
+ # err['error_code'] → 400
227
+ # err['error_message'] → "Bad Request. The series does not exist."
228
+ print(f"FRED error {err['error_code']}: {err['error_message']}")
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Option B: BLS API (no key required, confirmed live)
234
+
235
+ Bureau of Labor Statistics. Covers unemployment, CPI, payrolls — the most-queried FRED series. **Without a key: 10 requests/day limit.** Free key registration at `https://www.bls.gov/developers/` gives 500 req/day and 10 years of data per call (vs 3 years without key).
236
+
237
+ ```python
238
+ import json
239
+ # Single series GET — no auth needed
240
+ r = http_get("https://api.bls.gov/publicAPI/v2/timeseries/data/LNS14000000?startyear=2024&endyear=2024")
241
+ data = json.loads(r)
242
+ # data['status'] == 'REQUEST_SUCCEEDED'
243
+ series = data['Results']['series'][0]
244
+ for point in series['data'][:3]:
245
+ print(f"{point['year']}-{point['period']} ({point['periodName']}): {point['value']}")
246
+ # 2024-M12 (December): 4.1
247
+ # 2024-M11 (November): 4.2
248
+ # 2024-M10 (October): 4.1
249
+ ```
250
+
251
+ ### Multi-series POST (single call, multiple series)
252
+
253
+ ```python
254
+ import json, urllib.request
255
+
256
+ payload = json.dumps({
257
+ "seriesid": ["LNS14000000", "CUSR0000SA0", "CES0000000001"],
258
+ "startyear": "2023",
259
+ "endyear": "2024"
260
+ # "registrationkey": "YOUR_BLS_KEY" # optional: lifts to 500/day, 10yr range
261
+ }).encode()
262
+
263
+ req = urllib.request.Request(
264
+ "https://api.bls.gov/publicAPI/v2/timeseries/data/",
265
+ data=payload,
266
+ headers={"Content-Type": "application/json"}
267
+ )
268
+ with urllib.request.urlopen(req, timeout=20) as resp:
269
+ data = json.loads(resp.read().decode())
270
+
271
+ for s in data['Results']['series']:
272
+ pts = s['data']
273
+ print(f"{s['seriesID']}: {len(pts)} points, latest={pts[0]['value']}")
274
+ # LNS14000000: 24 points, latest=4.1 (unemployment %)
275
+ # CUSR0000SA0: 24 points, latest=317.604 (CPI index)
276
+ # CES0000000001: 24 points, latest=158316 (nonfarm payrolls, thousands)
277
+ ```
278
+
279
+ ### Key BLS series (FRED equivalents)
280
+
281
+ | BLS Series ID | FRED Equivalent | Description |
282
+ |---------------|-----------------|-------------|
283
+ | `LNS14000000` | `UNRATE` | Unemployment rate, SA (%) |
284
+ | `CUSR0000SA0` | `CPIAUCSL` | CPI-U All Urban, SA |
285
+ | `CUUR0000SA0` | `CPIAUCNS` | CPI-U All Urban, not SA |
286
+ | `CUSR0000SA0L1E` | `CPILFESL` | CPI less food and energy, SA |
287
+ | `CES0000000001` | `PAYEMS` | Total nonfarm payrolls (thousands) |
288
+ | `LNS11000000` | `CLF16OV` | Civilian labor force (thousands) |
289
+ | `LNS12000000` | `CE16OV` | Civilian employment (thousands) |
290
+
291
+ ### BLS rate limits
292
+
293
+ | | Without key | With free key |
294
+ |--|--|--|
295
+ | Requests/day | **10** (confirmed: call 11 returns `REQUEST_NOT_PROCESSED`) | 500 |
296
+ | Series per request | 25 | 50 |
297
+ | Years per request | 3 | 10 |
298
+ | Daily or seasonal adjustment | No | Yes |
299
+
300
+ ---
301
+
302
+ ## Option C: World Bank API (no key, unlimited, annual data)
303
+
304
+ Free, no registration, no rate limit observed (10 rapid calls completed in 2.0s). Annual data only — no monthly or quarterly frequency.
305
+
306
+ ```python
307
+ import json
308
+
309
+ # Single country, single indicator
310
+ r = http_get("https://api.worldbank.org/v2/country/US/indicator/NY.GDP.MKTP.CD?format=json&per_page=5&mrv=5")
311
+ data = json.loads(r)
312
+ page_info = data[0] # {'page': 1, 'pages': 1, 'per_page': 5, 'total': 5, 'lastupdated': '2026-04-08'}
313
+ items = data[1] # list of observations
314
+
315
+ for item in items:
316
+ if item['value']:
317
+ print(f"{item['date']}: ${item['value']/1e12:.2f}T")
318
+ # 2024: $28.75T
319
+ # 2023: $27.29T
320
+ # 2022: $25.60T
321
+ ```
322
+
323
+ ### Date range filter and multi-country
324
+
325
+ ```python
326
+ import json
327
+
328
+ # Historical range: date=YYYY:YYYY
329
+ r = http_get("https://api.worldbank.org/v2/country/US/indicator/FP.CPI.TOTL.ZG?format=json&date=2015:2024&per_page=15")
330
+ data = json.loads(r)
331
+ items = [i for i in data[1] if i['value'] is not None]
332
+ for item in items:
333
+ print(f"{item['date']}: {item['value']:.2f}%")
334
+ # 2024: 2.95%
335
+ # 2023: 4.12%
336
+ # 2022: 8.00%
337
+ # ...
338
+
339
+ # Multi-country: semicolon-separated ISO codes
340
+ r = http_get("https://api.worldbank.org/v2/country/US;CN;DE;JP;GB/indicator/NY.GDP.MKTP.CD?format=json&date=2023&per_page=10")
341
+ data = json.loads(r)
342
+ items = sorted([i for i in data[1] if i['value']], key=lambda x: x['value'], reverse=True)
343
+ for item in items:
344
+ print(f"{item['country']['value']}: ${item['value']/1e12:.2f}T")
345
+ # United States: $27.29T
346
+ # China: $18.27T
347
+ # Germany: $4.56T
348
+ ```
349
+
350
+ ### Key World Bank indicators (FRED equivalents)
351
+
352
+ | WB Indicator Code | FRED Equivalent | Description |
353
+ |-------------------|-----------------|-------------|
354
+ | `NY.GDP.MKTP.CD` | `GDP` | GDP, current USD |
355
+ | `NY.GDP.MKTP.KD.ZG` | `A191RL1Q225SBEA` | GDP growth rate (%) |
356
+ | `NY.GDP.PCAP.CD` | `A939RX0Q048SBEA` | GDP per capita (USD) |
357
+ | `FP.CPI.TOTL.ZG` | `FPCPITOTLZGUSA` | CPI inflation, annual % |
358
+ | `FP.CPI.TOTL` | `CPIAUCSL` (annual) | CPI level, 2010=100 |
359
+ | `SL.UEM.TOTL.ZS` | `UNRATE` (annual) | Unemployment rate, ILO model |
360
+ | `CM.MKT.LCAP.GD.ZS` | — | Stock market cap / GDP ratio |
361
+
362
+ ---
363
+
364
+ ## Option D: Alpha Vantage (free registered key, select indicators)
365
+
366
+ Some economic indicators work with the `demo` key (no registration); most require a free registered key (25 requests/day, instant signup at `https://www.alphavantage.co/support/#api-key`).
367
+
368
+ ```python
369
+ import json
370
+ AV_KEY = "demo" # or your registered key
371
+
372
+ # Unemployment rate (works with demo key — confirmed)
373
+ r = http_get(f"https://www.alphavantage.co/query?function=UNEMPLOYMENT&apikey={AV_KEY}")
374
+ data = json.loads(r)
375
+ # data['name'] = 'Unemployment Rate'
376
+ # data['interval'] = 'monthly'
377
+ # data['unit'] = 'percent'
378
+ # data['data'] → list of {date, value}, newest first
379
+
380
+ print(data['data'][0]) # {'date': '2026-03-01', 'value': '4.3'}
381
+ print(f"Total: {len(data['data'])} months since {data['data'][-1]['date']}")
382
+ # Total: 939 months since 1948-01-01
383
+ ```
384
+
385
+ ### Which indicators work with demo vs registered key
386
+
387
+ | Function | demo key | Registered key |
388
+ |----------|----------|----------------|
389
+ | `UNEMPLOYMENT` | YES | YES |
390
+ | `INFLATION` | YES (annual) | YES |
391
+ | `RETAIL_SALES` | YES | YES |
392
+ | `DURABLES` | YES | YES |
393
+ | `NONFARM_PAYROLL` | YES | YES |
394
+ | `REAL_GDP_PER_CAPITA` | YES | YES |
395
+ | `REAL_GDP` | NO (rate-limited) | YES |
396
+ | `CPI` | NO (rate-limited) | YES |
397
+ | `FEDERAL_FUNDS_RATE` | NO (rate-limited) | YES |
398
+ | `TREASURY_YIELD` | NO (rate-limited) | YES |
399
+ | `CONSUMER_SENTIMENT` | NO (rate-limited) | YES |
400
+
401
+ ```python
402
+ import json
403
+ AV_KEY = "YOUR_FREE_KEY" # from alphavantage.co/support/#api-key
404
+
405
+ # Federal Funds Rate — monthly (requires registered key)
406
+ r = http_get(f"https://www.alphavantage.co/query?function=FEDERAL_FUNDS_RATE&interval=monthly&apikey={AV_KEY}")
407
+ data = json.loads(r)
408
+ for item in data['data'][:3]:
409
+ print(f"{item['date']}: {item['value']}%")
410
+ # 2026-03-01: 4.33%
411
+ # 2026-02-01: 4.33%
412
+ # 2026-01-01: 4.33%
413
+
414
+ # 10-Year Treasury Yield
415
+ r = http_get(f"https://www.alphavantage.co/query?function=TREASURY_YIELD&maturity=10year&interval=monthly&apikey={AV_KEY}")
416
+ data = json.loads(r)
417
+ print(data['data'][0]) # {'date': '2026-04-17', 'value': '4.34'}
418
+ ```
419
+
420
+ ---
421
+
422
+ ## Option E: Browser + CDP (for interactive FRED charts)
423
+
424
+ When you need data from `fred.stlouisfed.org` that has no API equivalent (custom chart combos, release dates visible on page) — or when you have no API key — use the browser.
425
+
426
+ ```python
427
+ # Navigate to a series page
428
+ goto_url("https://fred.stlouisfed.org/series/GDP")
429
+ wait_for_load()
430
+
431
+ # Option 1: Intercept the fredgraph XHR that the chart fires
432
+ # The page's chart JS calls fredgraph.csv internally — intercept it
433
+ events = drain_events()
434
+ # Look for network events with fredgraph.csv in URL
435
+
436
+ # Option 2: Extract the latest value from the page text
437
+ latest_val = js("""
438
+ // The last observation appears in the meta section
439
+ const el = document.querySelector('.series-meta-observation-end');
440
+ el ? el.textContent.trim() : null
441
+ """)
442
+
443
+ # Option 3: Read the data table if present
444
+ table_data = js("""
445
+ const rows = Array.from(document.querySelectorAll('table.series-observations tr'));
446
+ rows.map(r => {
447
+ const cells = r.querySelectorAll('td');
448
+ return cells.length >= 2 ? [cells[0].textContent.trim(), cells[1].textContent.trim()] : null;
449
+ }).filter(Boolean);
450
+ """)
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Rate limits
456
+
457
+ | API | Limit | Notes |
458
+ |-----|-------|-------|
459
+ | FRED REST API | 120 req/min | With registered key (free) |
460
+ | FRED REST API | blocked | Without key — HTTP 400 |
461
+ | BLS (no key) | 10 req/day | Confirmed: call 11 → `REQUEST_NOT_PROCESSED` |
462
+ | BLS (with key) | 500 req/day, 50 series/req | Free registration at bls.gov/developers |
463
+ | World Bank | No limit observed | 10 rapid calls: 2.0s, no 429 |
464
+ | Alpha Vantage (demo) | 2 req/sec | Demo key rate-limited for most functions |
465
+ | Alpha Vantage (free key) | 25 req/day | Free at alphavantage.co/support/#api-key |
466
+
467
+ ---
468
+
469
+ ## Gotchas
470
+
471
+ - **fred.stlouisfed.org web endpoints ALL timeout** — The CSV download (`fredgraph.csv`), JSON graph (`fredgraph.json`), text format (`/data/*.txt`), and HTML series pages all hang indefinitely from headless HTTP. This is not a UA or header issue — the server simply does not respond to non-browser connections. Confirmed with multiple UA strings, TCP connect succeeds but no HTTP response is sent.
472
+
473
+ - **FRED API key is mandatory and must be exactly 32 lowercase alphanumeric chars** — "test", "demo", "guest", and keys shorter/longer than 32 chars all return HTTP 400: `"not a 32 character alpha-numeric lower-case string"`. An unregistered 32-char key returns: `"not registered"`.
474
+
475
+ - **Observation values are always strings, not numbers** — The `value` field in FRED observations is always a JSON string: `"4.1"`, not `4.1`. Also `"."` (dot) means missing/not-yet-released. Always check `if o['value'] != '.'` before `float(o['value'])`.
476
+
477
+ - **BLS 10 req/day without key burns fast** — The limit is per-IP per-day. 10 calls is exhausted in one moderate script run. Either register a free BLS key immediately or use World Bank for the same data annually.
478
+
479
+ - **BLS data range: 3 years without key, 10 years with key** — Requesting `startyear=2000&endyear=2024` without a key silently truncates to the most recent 3 years. With a key it returns up to 10 years and includes a `message` field if the range was truncated: `['Year range has been reduced to the system-allowed limit of 10 years.']`.
480
+
481
+ - **World Bank is annual only** — No monthly or quarterly data. For monthly UNRATE or CPI, use BLS. For quarterly GDP, use FRED API or Alpha Vantage `REAL_GDP`.
482
+
483
+ - **World Bank response is a 2-element array** — `data[0]` is pagination metadata, `data[1]` is the observations list. Missing years have `value: null` (not `"."`). Filter with `if item['value'] is not None`.
484
+
485
+ - **Alpha Vantage demo key: 2 req/sec, covers only 6 economic functions** — The other 6 economic functions (`REAL_GDP`, `CPI`, `TREASURY_YIELD`, etc.) return `{"Information": "The demo API key is for demo purposes only..."}`. No error code — just check for the `Information` key in the response.
486
+
487
+ - **FRED `sort_order=desc` returns newest first** — Default is `asc` (oldest first, starting from observation_start). For "get the latest value" use `limit=1&sort_order=desc`.
488
+
489
+ - **FRED series IDs are case-sensitive and exact** — `gdp` returns an error; must be `GDP`. Check `fred.stlouisfed.org/series/{ID}` to verify a series exists before scripting.
490
+
491
+ - **Some FRED series have gaps** — Daily series like `DGS10` and `SP500` skip weekends and holidays. Those dates simply don't appear in the observations array (not represented as `"."`). Weekly and monthly series use the first day of the period as the date (e.g., `2024-01-01` = January 2024).
492
+
493
+ - **FRED `realtime_start`/`realtime_end` in observations** — Every observation has these fields reflecting vintage data. For current data, ignore them. They matter only for "real-time" research (what was the published value on a specific past date).