# Changelog
What's new in the Foresight API.
## August 2026
### Pricing corrections
- **Payable surface updates** — `GET /v1/flows` is now payable at `$0.01`; batch latest requests with `indicators=` are a flat `$0.01` per request; concept aliases are priced like the series they resolve to; and premium `cf:*` requests now correctly charge `$0.05`.
## July 2026
### County-depth, everywhere
- **Employment by industry for every US county** — how many people work in
health care, construction, manufacturing, retail, and 17 other sectors, in
any county in America, quarterly back to 2020. Rankings come free: ask for
insights and you'll get lines like *"Among Tennessee counties, Williamson
ranks 5th of 80 for health care and social assistance employment."*
- **County GDP and incomes** — GDP, total personal income, and per-capita
income for ~3,100 counties, annual back to 2005.
- **County essentials from the American Community Survey** — median household
income, home values, rents, population, and more, for every county, back
to 2013.
- **~90 marquee national series** — the numbers people actually watch:
mortgage rates, core CPI, oil and gas prices, Treasury yields and spreads,
job openings and quits, credit-card delinquencies, the Fed's balance
sheet, consumer sentiment, and dozens more, full history included.
### Pay with Lightning
- **L402** — paid endpoints now accept Lightning alongside x402. The same `402` that carries USDC payment requirements also carries a `WWW-Authenticate: L402` challenge with a bolt11 invoice; pay it with any Lightning wallet and retry with the proof of payment. Same prices on both rails. See [Pay per request](/guides/paying).
### Ask for the thing, not the source
- **Concepts** — `indicator=population`, `gdp`, `median_income`, `median_home_value`, `unemployment_rate` and more now work on every read endpoint, no agency codes required. The API editorially picks the best series for each kind of place — Census for a county, FRED for the nation — and discloses the choice in `meta.concept` on every response. See [Concepts](/api/concepts).
- **Honest refusals** — ask for a combination no source publishes (county-level CPI, say) and the API says so plainly and names the closest available alternative, instead of returning something misleading.
- **Derived concepts** — measures no agency publishes, computed on demand: `employment_rate` (employed ÷ labor force) is live for counties and states, with every input series disclosed in `meta.concept.inputs`.
### Ask in plain language
- **`GET /v1/ask`** — a natural-language question in, a grounded answer out. Every number in the prose traces to a cited tool call you can replay against the deterministic endpoints, and the `grounded` flag tells you the verification passed. You don't pay when we don't have the data: questions with no coverage return `no_coverage` for free. Also available as the `ask` MCP tool. See [Ask](/api/ask). Ask now resolves questions through the concept layer deterministically — identical questions cite identical series.
### News sentiment as data
- **Economic news tone and attention** — `cf:osint.econ_news_tone` and `cf:osint.econ_news_volume` turn the global news firehose (GDELT) into daily time series: how positive or negative economic coverage is, and how much of it there is. Official statistics tell you what happened last month; these tell you what the conversation is doing today — same API, same citations, updated daily.
### Ask for more, get more
- **Panels** — `GET /v1/panel` returns up to 10 indicators aligned on one date grid in a single call, ready for charts and models. See [Panel](/api/panel).
- **Query-time transforms** — add `transform=yoy`, `mom`, `qoq`, `index:2020-01-01`, or `log` to observations and get derived values with the unit already updated. Percent changes compare calendar periods, so gaps yield honest nulls instead of misleading numbers. See [Observations](/api/observations#transforms).
- **Every US county on the map** — all 50 states, DC, and 3,144 county equivalents are first-class regions. If you ask for a series we're still preparing, the API answers `202` with `Retry-After` — retry with the code from the response body and the data will be there.
### Get access in one request
- **Self-serve API keys** — `POST /v1/signup` with an email returns a free key instantly. No forms, no waiting. See [Signup](/api/signup).
- **Access tiers in the catalog** — every indicator now says whether it's `free` or `premium`, so you know before you call. See [Indicators](/api/indicators).
- **Agent onboarding** — the docs, [llms.txt](https://api.creativeforesight.io/llms.txt), and the API's discovery index all route agents to a key or to x402 without human help.
### County finance, in depth
- **46 new Williamson County finance indicators** — revenue by category, spending by function, and debt by type, from audited TN Comptroller data, 2007 through FY2025. The county's full budget story is now queryable, not just the totals.
- **Complete demographics** — population by age and sex (36 series, 2010–2024) joins the race and age-group breakdowns, all from the Census Bureau.
### Signals that read like a colleague wrote them
- **Shareable sentences everywhere** — every insight fact and every signal carries `text`: a plain-spoken, ready-to-display sentence like *"Arts and entertainment employment in Williamson County is picking up."* See [Insights](/api/insights).
- **Chips you can render as-is** — signals ship a `vocabulary` of glyph, label, and tone, already adjusted for whether up is good or bad for that indicator. See [Signals](/api/signals).
- **Honest timelines** — when a signal says *"since April,"* that's the start of the current move, not a stale first detection.
### Documentation
- **New docs site** at [docs.creativeforesight.io](https://docs.creativeforesight.io) — guides, the full API reference, the [Derived Indicators catalog](/guides/derived), and agent-readable [llms-full.txt](https://api.creativeforesight.io/llms-full.txt).
## June 2026
### Pay per call with x402
- **No-account access** — call a paid endpoint without a key, get a `402` with payment instructions, pay on-chain, retry. Standard series cost $0.01 per request; premium series and signals cost $0.05. See [Pay per request](/guides/paying).
### Derived indicators
- **The `cf:*` series** — Creative Foresight originals like `cf:labor_composite` (a single monthly read on the US labor market) and `cf:recession_signal` (a monthly recession-risk gauge), each traceable to the public data behind it. See [Derived Indicators](/guides/derived).
---
# Foresight API
**Ask one API for the economy.** Jobs, prices, housing, government budgets — the numbers people actually argue about, served as clean, citable time series from the national level down to a single county. Creative Foresight's own derived series sit alongside the public data and tell you when something starts moving.
Two ways in, both self-serve: one request gets you a free key, or your agent can pay per call — USDC (x402) or Lightning (L402) — and skip accounts entirely.
## Point your agent at it
Using Claude, ChatGPT, Cursor, or any agent with web access? Paste this and you're onboarded:
```text
Read https://api.creativeforesight.io/llms-full.txt, then walk me
through my two ways in — a free API key or paying per call (x402 or L402) —
and set up whichever fits how I'll use it.
```
## Why use it?
Economic data is scattered across providers, each with its own identifiers, region model, release cadence, and revision semantics. Foresight API normalizes those differences into a small set of resources: sources, indicators, regions, observations, and computed insights.
Creative Foresight also publishes derived `cf:*` indicators. These combine source data into opinionated signals with explicit provenance, so agents can cite both the derived series and the underlying public indicators.
## Start here
Quickstart
Get a key, make the first observations request, or connect through MCP and per-request payment.
Concepts
Understand indicators, observations, regions, revisions, and the cf:* derived layer.
API Reference
Endpoint-by-endpoint request and response details for the v1 read API.
Pricing and Limits
Plan limits, per-request pricing, rate-limit headers, and standard error codes.
## API basics
| Item | Value |
| --- | --- |
| Base URL | `https://api.creativeforesight.io` |
| OpenAPI Spec | [`/openapi.json`](/openapi.json) |
| Short agent file | [`/llms.txt`](/llms.txt) |
| Full agent file | [`/llms-full.txt`](/llms-full.txt) |
| API key headers | `Authorization: Bearer cf_live_...` or `X-API-Key: cf_live_...` |
## Surface map
```mermaid
flowchart LR
Sources["Sources"] --> Indicators["Indicators"]
Indicators --> Regions["Available regions"]
Indicators --> Observations["Observations"]
Observations --> Revisions["Revision history"]
Observations --> Insights["Computed insights"]
Sources --> Derived["cf:* derived layer"]
Derived --> Observations
```
Catalog routes help applications discover what is available. Observation routes return the values, periods, revision state, and source-update timestamps that production systems usually need. Insight routes add deterministic descriptive facts, such as trend, latest delta, watermarks, and longest runs.
---
# Quickstart
Use the human path when you want a reusable API key. Use the agent path when a model or tool runtime should discover the catalog, call MCP tools, or pay per request with x402.
## Humans: get a key
Create a free-tier key, store it, then make the first call:
```bash
curl -X POST "https://api.creativeforesight.io/v1/signup" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
```
The response returns the raw `cf_live_` key exactly once. Store it as `CF_API_KEY`, then call the observations endpoint:
```bash
curl "https://api.creativeforesight.io/v1/observations?indicator=UNRATE&limit=2" \
-H "Authorization: Bearer $CF_API_KEY"
```
The response uses a stable `{ data, meta }` envelope:
```json
{
"data": [
{
"date": "2026-05-01",
"value": 4.2,
"indicator": "UNRATE",
"region": "US",
"unit": "percent",
"source": "fred",
"source_updated_at": "2026-06-06T12:00:00.000Z",
"is_preliminary": false,
"revision": 0
}
],
"meta": {
"indicator": "UNRATE",
"region": "US",
"unit": "percent",
"total": 1,
"limit": 2,
"offset": 0
}
}
```
## JavaScript
```js
const response = await fetch(
'https://api.creativeforesight.io/v1/observations?indicator=UNRATE&limit=2',
{
headers: {
Authorization: `Bearer ${process.env.CF_API_KEY}`,
},
},
)
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
for (const row of body.data) {
console.log(row.date, row.value, row.revision, row.is_preliminary)
}
```
## Python
```python
response = requests.get(
"https://api.creativeforesight.io/v1/observations",
params={"indicator": "UNRATE", "limit": 2},
headers={"Authorization": f"Bearer {os.environ['CF_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
body = response.json()
for row in body["data"]:
print(row["date"], row["value"], row["revision"], row["is_preliminary"])
```
## Agents: MCP
The streamable HTTP MCP server is at `https://api.creativeforesight.io/api/mcp`. It exposes catalog tools, observation tools, and a question-answering tool:
| Tool | Purpose |
| --- | --- |
| `search_indicators` | Find indicators by text, source, category, or frequency. |
| `list_sources` | List data providers and source metadata. |
| `list_regions` | Discover regions and filter by type or parent region. |
| `get_observations` | Fetch observation rows for an indicator. |
| `get_latest` | Fetch the latest observation for an indicator. |
| `get_insights` | Compute deterministic facts plus active signals for an indicator. |
| `get_active_signals` | List currently open Creative Foresight signal intervals. |
| `ask` | Answer a plain-language question with a citation trace. |
Catalog tools work without a key. For ready-made Claude Code, Claude Desktop, and Cursor configs, see [Connect over MCP](/guides/mcp). A client that accepts a URL and headers takes:
```json
{
"mcpServers": {
"foresight": {
"url": "https://api.creativeforesight.io/api/mcp",
"headers": {
"Authorization": "Bearer cf_live_..."
}
}
}
}
```
Manual discovery call:
```bash
curl -X POST "https://api.creativeforesight.io/api/mcp" \
-H "Authorization: Bearer $CF_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
## Agents: pay per request
Observation, insight, and signal endpoints also support anonymous per-request payment — USDC via x402, or Lightning via L402.
1. Call `/v1/observations`, `/v1/observations/latest`, `/v1/insights`, `/v1/signals`, or `/v1/signals/active` without an API key.
2. Read the HTTP 402 payment requirements in the response (x402 in the body, a Lightning invoice in the `WWW-Authenticate` header).
3. Pay on either rail.
4. Retry with `X-PAYMENT` (x402) or `Authorization: L402 …` (Lightning).
```bash
curl -i "https://api.creativeforesight.io/v1/observations/latest?indicator=UNRATE"
curl "https://api.creativeforesight.io/v1/observations/latest?indicator=UNRATE" \
-H "X-PAYMENT: BASE64_PAYMENT_PAYLOAD"
```
Paid retries that settle successfully include `X-Payment-Settled: true`.
See **[Pay per request](/guides/paying)** for both rails in full — the runnable x402
client, the Lightning flow, the payable endpoints and prices, and discovery.
---
# Connect over MCP
The Foresight API runs a hosted MCP server. There is nothing to install.
| | |
| --- | --- |
| URL | `https://api.creativeforesight.io/api/mcp` |
| Transport | Streamable HTTP (stateless, JSON responses) |
| Auth | Optional header `Authorization: Bearer cf_live_...` |
| Registry name | `io.creativeforesight/foresight-api` |
## With or without a key
Catalog tools work without a key. The data tools need a key. Called without one, they return an error that names the equivalent REST endpoint, which an agent can [pay for per request](/guides/paying) instead.
| Tool | Without a key | With a key |
| --- | --- | --- |
| `search_indicators` | Yes | Yes |
| `list_sources` | Yes | Yes |
| `list_regions` | Yes | Yes |
| `get_observations` | Payment instructions | Yes, within your tier |
| `get_latest` | Payment instructions | Yes, within your tier |
| `get_insights` | Payment instructions | Yes, within your tier |
| `get_active_signals` | Payment instructions | Yes, within your tier |
| `ask` | Payment instructions | Yes, within your tier |
Get a free key with one request (see [Signup](/api/signup)), then export it so the configs below can read it:
```bash
export FORESIGHT_API_KEY=cf_live_...
```
If you don't have a key, leave the `Authorization` header out. An empty or invalid key returns `INVALID_API_KEY` instead of the payment instructions.
## Claude Code
Without a key:
```bash
claude mcp add --transport http foresight https://api.creativeforesight.io/api/mcp
```
With a key, add the header. Your shell expands `$FORESIGHT_API_KEY` when you run the command, so the key is saved in your Claude Code config:
```bash
claude mcp add --transport http foresight https://api.creativeforesight.io/api/mcp \
--header "Authorization: Bearer $FORESIGHT_API_KEY"
```
To share the server with a team without committing a key, put it in the project's `.mcp.json`. Claude Code expands `${FORESIGHT_API_KEY}` from each person's environment:
```json
{
"mcpServers": {
"foresight": {
"type": "http",
"url": "https://api.creativeforesight.io/api/mcp",
"headers": {
"Authorization": "Bearer ${FORESIGHT_API_KEY}"
}
}
}
}
```
Check it with `claude mcp get foresight`, then ask Claude something like "What is the latest US unemployment rate?"
## Claude Desktop and claude.ai
Claude Desktop connects to remote MCP servers as custom connectors. `claude_desktop_config.json` is only for local servers.
1. Open **Customize → Connectors**.
2. Click **+**, then **Add custom connector**.
3. Name it `Foresight API` and paste `https://api.creativeforesight.io/api/mcp` as the URL.
4. Leave the OAuth fields empty and save.
A custom connector can't send an API key, so it gets the keyless behavior above. For keyed access from Claude, use Claude Code.
On Team and Enterprise plans an Owner adds the connector under **Organization settings → Connectors**, and members then click **Connect**.
## Cursor
Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):
```json
{
"mcpServers": {
"foresight": {
"url": "https://api.creativeforesight.io/api/mcp",
"headers": {
"Authorization": "Bearer ${env:FORESIGHT_API_KEY}"
}
}
}
}
```
Without a key, delete the `headers` block. Cursor reads `${env:FORESIGHT_API_KEY}` from the environment it was launched from.
## Any streamable HTTP client
Every client needs the same two values: the URL, plus the `Authorization` header when you have a key. Pick the streamable HTTP transport; some clients label it "HTTP". The field names vary by client, so check its MCP docs.
With the official TypeScript SDK (`npm install @modelcontextprotocol/sdk`):
```ts
const apiKey = process.env.FORESIGHT_API_KEY
const transport = new StreamableHTTPClientTransport(
new URL('https://api.creativeforesight.io/api/mcp'),
{ requestInit: { headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {} } },
)
const client = new Client({ name: 'my-agent', version: '1.0.0' })
await client.connect(transport)
const { tools } = await client.listTools()
console.log(tools.map((tool) => tool.name))
const result = await client.callTool({
name: 'search_indicators',
arguments: { query: 'unemployment rate', limit: 3 },
})
console.log(result.content)
```
The server is stateless, so plain JSON-RPC over HTTP works too:
```bash
curl -X POST "https://api.creativeforesight.io/api/mcp" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
## Next steps
- [Use with AI Agents](/guides/agents): call the REST API from your own tool-use code instead of MCP.
- [Pay per request (402)](/guides/paying): how a keyless agent pays for a data call.
- [Pricing & Limits](/guides/pricing): what each tier includes.
---
# Use with AI Agents
There are two ways to put Foresight data in front of a model.
- **MCP (no code).** Point any MCP client at `https://api.creativeforesight.io/api/mcp` and pass your key as `Authorization: Bearer cf_live_...`. Catalog tools work without a key. See [Connect over MCP](/guides/mcp) for Claude Code, Claude Desktop, and Cursor configs.
- **Tool use (your code).** Define a couple of functions that call the REST API and hand them to the model. The snippets below do exactly that for Claude, OpenAI, and LangChain.
Every snippet uses the same two tools:
| Tool | Calls | Cost |
| --- | --- | --- |
| `search_indicators` | [`GET /v1/indicators`](/api/indicators) | Free, no key needed |
| `get_latest_observation` | [`GET /v1/observations/latest`](/api/observations/latest) | Free-tier indicators with a free key; see [Pricing](/guides/pricing) |
Get a free key with one request (see [Signup](/api/signup)), then export it:
```bash
curl -X POST "https://api.creativeforesight.io/v1/signup" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","source":"agents guide"}'
export FORESIGHT_API_KEY=cf_live_...
```
## Shared tool code
Save this as `foresight_tools.py`. It uses only the Python standard library. Error responses are JSON too, so the snippets pass them straight to the model: a `402` without a key, for example, tells the model exactly what access is missing.
```python
FORESIGHT_API = "https://api.creativeforesight.io"
def call_foresight(path: str, params: dict) -> tuple[str, bool]:
"""GET a Foresight API endpoint. Returns (json_body, is_error)."""
headers = {}
api_key = os.environ.get("FORESIGHT_API_KEY") # cf_live_... from POST /v1/signup
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
url = f"{FORESIGHT_API}{path}?{urllib.parse.urlencode(params)}"
try:
with urllib.request.urlopen(urllib.request.Request(url, headers=headers), timeout=30) as response:
return response.read().decode(), False
except urllib.error.HTTPError as error:
return error.read().decode(), True
def search_indicators(query: str, limit: int = 5) -> tuple[str, bool]:
return call_foresight("/v1/indicators", {"search": query, "limit": limit})
def get_latest_observation(indicator: str, region: str = "US") -> tuple[str, bool]:
return call_foresight("/v1/observations/latest", {"indicator": indicator, "region": region})
TOOL_FUNCTIONS = {
"search_indicators": search_indicators,
"get_latest_observation": get_latest_observation,
}
```
Add the tools' JSON Schemas to the same file. Every snippet reuses them:
```python
SEARCH_INDICATORS_SCHEMA = {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Plain-language search, e.g. 'unemployment rate'."},
"limit": {"type": "integer", "minimum": 1, "maximum": 20, "description": "Rows to return (default 5)."},
},
"required": ["query"],
}
GET_LATEST_OBSERVATION_SCHEMA = {
"type": "object",
"properties": {
"indicator": {"type": "string", "description": "Indicator code from search_indicators, e.g. 'UNRATE'."},
"region": {"type": "string", "description": "'US', a 2-digit state FIPS, or a 5-digit county FIPS (default 'US')."},
},
"required": ["indicator"],
}
```
## Claude (Anthropic SDK)
`pip install anthropic`, set `ANTHROPIC_API_KEY`, then:
```python
from foresight_tools import GET_LATEST_OBSERVATION_SCHEMA, SEARCH_INDICATORS_SCHEMA, TOOL_FUNCTIONS
client = anthropic.Anthropic()
tools = [
{
"name": "search_indicators",
"description": "Search the Foresight API indicator catalog. Returns indicator codes, units and access tiers.",
"input_schema": SEARCH_INDICATORS_SCHEMA,
},
{
"name": "get_latest_observation",
"description": "Get the most recent value of a Foresight indicator for a region.",
"input_schema": GET_LATEST_OBSERVATION_SCHEMA,
},
]
messages = [{"role": "user", "content": "What is the latest US unemployment rate?"}]
response = client.messages.create(model="claude-opus-5", max_tokens=16000, tools=tools, messages=messages)
while response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
results = []
for block in response.content:
if block.type == "tool_use":
body, is_error = TOOL_FUNCTIONS[block.name](**block.input)
results.append({"type": "tool_result", "tool_use_id": block.id, "content": body, "is_error": is_error})
messages.append({"role": "user", "content": results})
response = client.messages.create(model="claude-opus-5", max_tokens=16000, tools=tools, messages=messages)
print("".join(block.text for block in response.content if block.type == "text"))
```
## OpenAI (function calling)
`pip install openai`, set `OPENAI_API_KEY`, then:
```python
from openai import OpenAI
from foresight_tools import GET_LATEST_OBSERVATION_SCHEMA, SEARCH_INDICATORS_SCHEMA, TOOL_FUNCTIONS
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "search_indicators",
"description": "Search the Foresight API indicator catalog. Returns indicator codes, units and access tiers.",
"parameters": SEARCH_INDICATORS_SCHEMA,
},
},
{
"type": "function",
"function": {
"name": "get_latest_observation",
"description": "Get the most recent value of a Foresight indicator for a region.",
"parameters": GET_LATEST_OBSERVATION_SCHEMA,
},
},
]
messages = [{"role": "user", "content": "What is the latest US unemployment rate?"}]
while True:
message = client.chat.completions.create(model="gpt-5", messages=messages, tools=tools).choices[0].message
if not message.tool_calls:
break
messages.append(message)
for call in message.tool_calls:
body, _ = TOOL_FUNCTIONS[call.function.name](**json.loads(call.function.arguments))
messages.append({"role": "tool", "tool_call_id": call.id, "content": body})
print(message.content)
```
Any tool-capable model works; swap `gpt-5` for the one you use.
## LangChain
`pip install langchain-core langchain-anthropic` (LangChain 1.x), set `ANTHROPIC_API_KEY`, then:
```python
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from foresight_tools import call_foresight
@tool
def search_indicators(query: str, limit: int = 5) -> str:
"""Search the Foresight API indicator catalog. Returns indicator codes, units and access tiers."""
return call_foresight("/v1/indicators", {"search": query, "limit": limit})[0]
@tool
def get_latest_observation(indicator: str, region: str = "US") -> str:
"""Get the most recent value of a Foresight indicator. region is 'US', a 2-digit state FIPS, or a 5-digit county FIPS."""
return call_foresight("/v1/observations/latest", {"indicator": indicator, "region": region})[0]
tools = {t.name: t for t in (search_indicators, get_latest_observation)}
llm = ChatAnthropic(model="claude-opus-5").bind_tools(list(tools.values()))
messages = [HumanMessage("What is the latest US unemployment rate?")]
while True:
reply = llm.invoke(messages)
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
messages.append(tools[call["name"]].invoke(call)) # a ToolMessage
print(reply.text)
```
The same `@tool` functions work with any LangChain chat model that supports tool calling. To use OpenAI, swap in `ChatOpenAI` from `langchain-openai`.
## Going further
- Swap `get_latest_observation` for [`/v1/observations`](/api/observations) to give the model a full series, or [`/v1/panel`](/api/panel) to compare up to 10 indicators on one date grid.
- [`/v1/ask`](/api/ask) answers a plain-language question with a citation trace. Expose it as a single tool when you want Foresight to do the retrieval.
- An agent without a key can pay per request instead. See [Pay per request (402)](/guides/paying).
---
# Concepts
Foresight API normalizes provider-specific economic data into a small resource model.
## Sources
A source is a data provider or derived-data publisher. Examples include public providers such as FRED, BLS, BEA, and BIS, plus Creative Foresight itself.
Source records include a stable `slug`, display `name`, provider metadata, category, documentation URL, and active indicator count.
## Indicators
An indicator is a named time series such as `UNRATE` or `cf:labor_composite`. Indicator records include the source, category, subcategory, `access_tier`, unit, frequency, active state, default region, and available regions.
Some providers reuse codes. Use the `source` query parameter when a code is ambiguous.
Categories are taxonomy only. The top-level `category` is one of 13 fixed domains: `macro`, `labor`, `prices`, `markets`, `housing`, `government-finance`, `demographics`, `business`, `energy`, `crypto`, `health`, `environment`, or `education`. `subcategory` is a meaningful kebab-case second level within that domain.
Access is controlled by `access_tier`, not by category. `free` indicators are available to free-tier keys. `premium` indicators require an unrestricted key, a key granting `creative-foresight`, or x402 payment on payable endpoints.
## Observations
Observations are dated values for an indicator and region. Observation rows include:
| Field | Meaning |
| --- | --- |
| `date` | Observation date. |
| `value` | Numeric value, or `null` when the source publishes a gap. |
| `indicator` | Indicator code returned by the query. |
| `region` | Region code for the row. |
| `unit` | Unit label, such as `percent`. |
| `source` | Source slug for the row. |
| `source_updated_at` | Provider update timestamp when available. |
| `is_preliminary` | Whether the source marks the value as preliminary. |
| `revision` | Revision number for the period. |
## Regions
Regions identify geography. National series commonly use `US`. County and subnational series use FIPS-style codes where applicable.
`GET /v1/regions` can filter by `type` and `parent`, which lets clients discover available counties for a state or local regions under a parent geography.
## Revisions and preliminary values
By default, observation queries return the latest revision for each period. Use `include_revisions=true` when you need revision history.
Preliminary source releases are marked with `is_preliminary`. Treat preliminary values as live release data rather than final historical truth.
## The cf:* derived layer
Creative Foresight original indicators use `cf:*` codes and `access_tier: "premium"`. These are derived series no provider publishes directly.
Every derived value traces back to the public series behind it, and those series stay queryable through the same API — so agents and applications get a stable signal whose underlying data chain stays auditable.
The full catalog — what each series measures, its inputs, and cadence — is on **[Derived Indicators](/guides/derived)**.
---
# Derived Indicators (`cf:*`)
Creative Foresight publishes original series no provider offers directly. Each one distills several public indicators into a single number you can chart, alert on, or hand to an agent — and every value traces back to the public series behind it, so anything you cite stays auditable. All `cf:*` series are `access_tier: "premium"`: an unrestricted key, a key granting `creative-foresight`, or a `$0.05` x402 payment per request.
Query them like any other indicator:
```bash
curl "https://api.creativeforesight.io/v1/observations/latest?indicator=cf%3Alabor_composite®ion=US" \
-H "Authorization: Bearer $CF_API_KEY"
```
## Catalog
| Code | What it tells you | Draws on | Cadence | Region |
| --- | --- | --- | --- | --- |
| `cf:labor_composite` | One monthly read on the health of the US labor market — is it strengthening or weakening, and how fast. | Unemployment, participation, and payrolls (FRED) | Monthly | `US` |
| `cf:macroscope_regime.regime` | Which macro regime the US economy is in right now — how growth and inflation currently sit relative to each other. | Industrial production and core inflation (FRED) | Monthly | `US` |
| `cf:macroscope_regime.confidence` | How firmly the current regime call holds. | Same as above | Monthly | `US` |
| `cf:recession_signal` | A monthly US recession-risk gauge. | The yield curve, jobless claims, and building permits (FRED) | Monthly | `US` |
| `cf:real_income_pc` | What the average American actually earns per year, adjusted for inflation. | Personal income, population, and CPI (FRED, Census) | Annual | `US` |
| `cf:housing_affordability` | How far the median household income goes against the median home price. | Median household income and median home price (Census, FRED) | Annual | `US` |
| `cf:fiscal_health_tn.47187.debt_per_capita` | Williamson County, TN outstanding debt per resident. | County debt and population (TN Comptroller, Census) | Annual (fiscal year) | `47187` |
| `cf:fiscal_health_tn.47187.debt_to_revenue` | Williamson County, TN debt as a share of county revenue. | County debt and revenue (TN Comptroller) | Annual (fiscal year) | `47187` |
Multi-output series (like `cf:macroscope_regime` and `cf:fiscal_health_tn`) publish each output under its own indicator code, and each output updates independently as its inputs arrive.
## Provenance
Every derived observation traces back to public data: the input series remain queryable through the same API, so an agent citing a `cf:*` value can also cite the exact public numbers behind it.
## Freshness
Derived series update automatically when their inputs do — monthly series follow their inputs' release schedules; annual series extend when the yearly data (ACS, PEP, county audit) lands. The `date_range.end` on [`GET /v1/indicators/{code}`](/api/indicators) always tells you the current frontier.
Signals and insights run over `cf:*` series exactly as they do over source indicators — [`GET /v1/insights?indicator=cf:labor_composite`](/api/insights) returns computed facts, prose, and any active signals.
---
# Pay per request (HTTP 402)
`402 Payment Required` is the HTTP status code the web reserved for machine
payments — and Foresight API uses it exactly that way. Call a paid endpoint
with no API key and the response is a `402` carrying everything needed to pay:
no signup, no dashboard, no stored card. Pay, retry, get the data.
Two payment rails are supported, and the same `402` advertises both:
- **[x402](#pay-with-x402-usdc-on-base)** — a few cents of USDC on Base,
settled on-chain.
- **[L402](#pay-with-lightning-l402)** — the same price in bitcoin over the
Lightning Network, settled instantly.
Use whichever your wallet or agent stack speaks. Catalog endpoints stay free;
only observation, insight, and signal endpoints are payable. Payment only
happens on a `2xx` — a `400`/`404`/`5xx` never charges the buyer.
## The 402 response
```bash
curl -i "https://api.creativeforesight.io/v1/observations/latest?indicator=unemployment_rate®ion=47187"
```
The response carries the x402 payment requirements twice: as JSON in the body,
and base64-encoded in the `PAYMENT-REQUIRED` header. The Lightning challenge
is in `WWW-Authenticate`:
```http
HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Mi…
WWW-Authenticate: L402 macaroon="AgEEbHNhdC…", invoice="lnbc100n1p4x…"
```
```json
{
"x402Version": 2,
"error": "PAYMENT-SIGNATURE header is required",
"resource": {
"url": "https://api.creativeforesight.io/v1/observations/latest",
"description": "Foresight API latest observations",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x9eDB521A5aD936Ef5D7054Ed94252D4d7978cf90",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2" }
}
],
"extensions": { "bazaar": { "info": { "input": { "type": "http", "method": "GET" } } } }
}
```
`amount` is in USDC base units (6 decimals), so `10000` = `$0.01`.
`eip155:8453` is Base mainnet. The Lightning invoice is the same price
converted to sats at the current BTC-USD rate.
## Pay with x402 (USDC on Base)
[x402](https://x402.org) settles in USDC on Base. The buyer pays gaslessly
(EIP-3009); Creative Foresight settles on-chain through the Coinbase CDP
facilitator and returns the data.
1. Call a payable endpoint **without** an API key.
2. Read the `accepts` array from the `402` (amount, asset, network, `payTo`).
3. Sign a USDC `transferWithAuthorization` and retry with a
`PAYMENT-SIGNATURE` header (the legacy `X-PAYMENT` name is also accepted).
4. The facilitator verifies and **settles**; the API returns **200** with the
data and a `PAYMENT-RESPONSE` header (the on-chain settlement receipt).
### The x402 client
The simplest path is the official x402 client,
[`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch). It turns any
`fetch` into one that pays automatically: it catches the `402`, signs the
payment, retries, and returns the `200` with a decoded on-chain receipt. Fund a
wallet with a little USDC on Base, then:
```js
const account = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY)
const fetchWithPay = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
})
const res = await fetchWithPay(
"https://api.creativeforesight.io/v1/observations/latest?indicator=unemployment_rate®ion=47187",
)
console.log(await res.json())
// The settlement receipt (tx hash, network, payer).
console.log(decodePaymentResponseHeader(res.headers.get("PAYMENT-RESPONSE")))
```
### End to end: an agent discovers, gets a 402, pays, gets the data
This script covers the whole loop with no account and no API key. It reads the
[discovery manifest](#discovery), calls a paid endpoint unauthenticated, reads
the `402`, pays per call with the x402 client, and prints the data and the
on-chain receipt. It needs Node 20+ and a wallet holding a little USDC on Base.
```bash
npm install @x402/fetch @x402/evm viem
```
```js
// agent-demo.mjs — run with: BUYER_PRIVATE_KEY=0x… node agent-demo.mjs
const api = "https://api.creativeforesight.io"
const maxAmount = 50_000n // refuse anything above $0.05 (USDC has 6 decimals)
// 1. Discover: the manifest lists every payable endpoint, its price, and an
// example input that returns real data (here: Williamson County, TN's BLS
// unemployment rate, indicator=unemployment_rate®ion=47187).
const manifest = await (await fetch(`${api}/.well-known/x402.json`)).json()
const entry = manifest.resources.find((r) => r.resource.url.endsWith("/v1/observations/latest"))
const example = entry.extensions.bazaar.info.input.queryParams
const url = `${entry.resource.url}?${new URLSearchParams(example)}`
// 2. Challenge: an unauthenticated call returns 402 with the requirements.
const challenge = await fetch(url)
const required = JSON.parse(atob(challenge.headers.get("PAYMENT-REQUIRED")))
console.log(challenge.status, required.accepts[0]) // 402 { amount: "10000", network: "eip155:8453", … }
// 3. Pay: the x402 client signs a USDC transferWithAuthorization and retries.
const account = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY)
const paidFetch = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
policies: [(_version, reqs) => reqs.filter((r) => BigInt(r.amount) <= maxAmount)],
})
const res = await paidFetch(url)
const receipt = decodePaymentResponseHeader(res.headers.get("PAYMENT-RESPONSE"))
console.log(res.status, await res.json())
console.log(`settled: https://basescan.org/tx/${receipt.transaction}`)
```
The payment is gasless for the buyer: it signs an EIP-3009 authorization, and
the facilitator submits the transfer and pays the gas. The `policies` cap is
the agent's spending guard. The client refuses any requirement priced above
it, so a price change can never drain the wallet. Each run spends one call's
price (`$0.01` here).
### Doing it by hand
If you build the `PAYMENT-SIGNATURE` header yourself, sign an
`exact`-scheme USDC `transferWithAuthorization` for the `payTo`/`asset`/`network`
in `accepts`, base64-encode the x402 payload, and retry:
```bash
curl "https://api.creativeforesight.io/v1/observations/latest?indicator=unemployment_rate®ion=47187" \
-H "PAYMENT-SIGNATURE: "
```
### x402 payment configuration
| | |
| --- | --- |
| Network | Base mainnet |
| Asset | USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) |
| Pay to | `0x9eDB521A5aD936Ef5D7054Ed94252D4d7978cf90` |
| Scheme | `exact` (EIP-3009 `transferWithAuthorization`) |
| Facilitator | Coinbase CDP (`api.cdp.coinbase.com/platform/v2/x402`) |
Settlement is **non-custodial** — funds move directly to the Creative Foresight
wallet; the facilitator only verifies signatures and submits the transfer.
## Pay with Lightning (L402)
[L402](https://l402.org) settles the same prices over the Lightning Network —
useful when your agent holds sats instead of stablecoins, and the cheapest rail
at sub-cent amounts.
1. Call a payable endpoint **without** an API key.
2. Read the `WWW-Authenticate: L402` header from the `402`. It carries two
values: a `macaroon` (your access token, held until payment completes) and
a bolt11 `invoice`.
3. Pay the invoice with any Lightning wallet. Paying reveals the **preimage**
— the proof of payment.
4. Retry with both joined by a colon:
```bash
curl "https://api.creativeforesight.io/v1/observations/latest?indicator=unemployment_rate®ion=47187" \
-H 'Authorization: L402 :'
```
The API verifies the proof and returns **200** with the data. No account is
created; nothing persists but your receipt.
Details worth knowing:
- The invoice is priced in sats from the USD price at the moment of the `402`
(e.g. `$0.01` ≈ 10 sats).
- A paid credential is valid for **10 minutes** and scoped to the endpoint and
indicator it was issued for — re-reads within that window are free; a new
request scope means a new `402`.
- Invoices expire with the credential; if one lapses unpaid, just request
again for a fresh challenge.
Lightning tooling that understands L402 (such as
[Aperture-compatible clients](https://github.com/lightninglabs/aperture) or
agent frameworks with L402 fetch wrappers) can drive the whole loop
automatically, the same way `@x402/fetch` does for USDC.
## Payable endpoints & prices
| Endpoint | Price |
| --- | --- |
| `GET /v1/observations` | `$0.01` (`free`) / `$0.05` (`premium`) |
| `GET /v1/observations/latest` | `$0.01` (`free`) / `$0.05` (`premium`) |
| `GET /v1/observations/latest?indicators=...` | `$0.01` per batch request |
| `GET /v1/insights` | `$0.01` (`free`) / `$0.05` (`premium`) |
| `GET /v1/flows` | `$0.01` |
| `GET /v1/panel` | `$0.05` |
| `GET /v1/ask` | `$0.25` / `$1.50` (`depth=briefing`) |
| `GET /v1/briefings/public/{slug}` | `$0.10` |
| `GET /v1/signals` | `$0.05` |
| `GET /v1/signals/active` | `$0.05` |
Prices are the same on both rails. `free` and `premium` are the indicator
`access_tier` values. Premium includes Creative Foresight derived series. See
[Pricing](/guides/pricing) for the full table. Batch latest requests using
`indicators=` are flat-priced at `$0.01` per request no matter how many
indicators are included.
## Discovery
Payable resources are advertised so agents can find them:
- `GET /.well-known/x402.json` — a machine-readable manifest of every payable
endpoint with its price and payment details.
- The [x402 Bazaar](https://docs.cdp.coinbase.com/x402/bazaar) — the CDP
facilitator catalogs the resource after its first settlement.
- Every `402` response itself — both rails, self-describing, no prior
knowledge needed.
---
# Pricing, Rate Limits, and Errors
Catalog endpoints are free. Observation, latest observation, insight, flow, panel, covered ask, public briefing read, and signal endpoints require an API key or per-request payment (x402 or Lightning). Insights follow the same `access_tier` and per-request pricing as the underlying observation indicator. Signals cost `$0.05` per paid request or require a premium API key.
## Plans
| Tier | Price | Included requests | Rate limit |
| --- | --- | --- | --- |
| Free | `$0` | No monthly quota | 60 requests/min |
| Developer | `$19/month` plus `$0.50` per 1,000 overage requests | 100,000/month | 300 requests/min |
| Professional | `$99/month` plus `$0.25` per 1,000 overage requests | 1,000,000/month | 1,000 requests/min |
## API key or pay per request — which one?
Both paths hit the same endpoints and return the same data. The difference is how you pay and how often you call.
**Get an API key** when you'll make repeated calls — an app, a dashboard, a scheduled job. Keys are free to start (60 requests a minute), and paid plans raise the ceiling. Your identity persists, so your limits and billing follow the key.
**Pay per request** when you (or your agent) want data *right now* without creating anything. Each request carries a small payment — $0.01 for standard series, $0.05 for premium, and fixed prices for flows, panels, covered asks, public briefing reads, and signals — in USDC ([x402](/guides/paying#pay-with-x402-usdc-on-base)) or over Lightning ([L402](/guides/paying#pay-with-lightning-l402)), and no account exists before or after. It's the vending machine; keys are the tab.
If you're unsure: start with the free key. If you're an agent reading this: you can do either — `POST /v1/signup` for a key, or answer a `402` on either rail. See [Pay per request](/guides/paying).
## Per-request prices (x402 and L402)
| Paid surface | Price |
| --- | --- |
| Passthrough source observations, latest observations, and insights | `$0.01` |
| `premium` / `cf:*` observations, latest observations, and insights | `$0.05` |
| `GET /v1/observations/latest?indicators=...` batch latest | `$0.01` per request |
| `GET /v1/flows` | `$0.01` |
| `GET /v1/panel` | `$0.05` |
| `GET /v1/ask` | `$0.25` / `$1.50` (`depth=briefing`) |
| `GET /v1/briefings/public/{slug}` | `$0.10` |
| Creative Foresight signals | `$0.05` per paid request or premium API key |
### Concept aliases
Concept words are priced like the series they resolve to. `indicator=population` resolves to a standard source series and costs `$0.01`; `indicator=misery_index` resolves to a `cf:*` premium series and costs `$0.05`.
Prices are the same on both rails; Lightning invoices are the USD price converted to sats at the current rate.
## Rate-limit headers
Authenticated observation and insight responses include rate-limit headers.
| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Configured request limit for the current one-minute window. |
| `X-RateLimit-Remaining` | Remaining requests in the current one-minute window. |
| `X-RateLimit-Reset` | Unix timestamp when the current one-minute window resets. |
| `Retry-After` | Seconds to wait before retrying after a `429`. |
| `X-Request-Id` | Request correlation identifier for support and logs. |
## Error envelope
Errors use a consistent envelope:
```json
{
"error": {
"code": "INVALID_PARAMETER",
"message": "The indicator query parameter is required.",
"details": {}
}
}
```
## Error codes
| Code | Meaning |
| --- | --- |
| `INVALID_API_KEY` | Missing, malformed, expired, or unknown API key. |
| `EXPIRED_API_KEY` | API key is no longer active. |
| `RATE_LIMIT_EXCEEDED` | Per-minute request limit was exceeded. |
| `INVALID_PARAMETER` | A query parameter is missing or invalid. |
| `RESOURCE_NOT_FOUND` | Requested indicator, source, region, or observation was not found. |
| `CATEGORY_NOT_ALLOWED` | The key does not include premium access for the requested indicator. The code name is retained for API stability. |
| `REGION_NOT_ALLOWED` | The key does not permit the requested region. |
| `INTERNAL_ERROR` | An internal operation failed, including failed x402 settlement. |
| `INTERNAL_SERVER_ERROR` | A catalog or server operation failed. |
---
# Rotate or revoke your key
You manage your API key through the API itself. There is no support mailbox to write to: every action on this page takes effect immediately.
| You want to | Call | Proof you own the key |
| --- | --- | --- |
| Replace a leaked or old key | `POST /v1/keys/rotate` | The current key |
| Stop a key for good | `POST /v1/keys/revoke` | The current key |
| End a paid subscription | `POST /v1/keys/revoke` with `cancel_subscription` | The current key |
| Replace a paid key you lost | `POST /v1/keys/recover`, then `POST /v1/keys/recover/confirm` | Your billing email |
Key-management calls are not metered, and they work even when a free key has used its monthly quota.
## Rotate a key
Rotation replaces the secret and leaves everything else in place: your plan, rate limit, usage this month, and billing carry over. The old key stops working the moment the call succeeds, so update your secrets manager straight away.
```bash
curl -X POST "https://api.creativeforesight.io/v1/keys/rotate" \
-H "Authorization: Bearer $CF_API_KEY"
```
Where the new key goes depends on how you got the original:
- **Free keys** get the new key in the response, just as [`POST /v1/signup`](/api/signup) returned the first one. It is shown once.
```json
{
"data": {
"key": "cf_live_…",
"key_hint": "9f3a61c2",
"previous_key_hint": "4b07d1e8",
"delivery": "response"
},
"meta": { "notice": "Your previous key stopped working immediately. Store this key now …" }
}
```
- **Paid keys** never get the new key in the response. It is emailed to the billing address on your subscription, the same way your first key arrived. If someone else has your key, rotating locks them out and the replacement goes only to you.
```json
{
"data": {
"key_hint": "9f3a61c2",
"previous_key_hint": "4b07d1e8",
"delivery": "email",
"delivered_to": "b***@example.com"
},
"meta": { "notice": "Your previous key stopped working immediately. The new key was emailed …" }
}
```
A key can be rotated 5 times in 24 hours. After that the call returns `429 RATE_LIMIT_EXCEEDED` and the current key keeps working.
If a paid key is rotated but the email can't be sent, the call returns `502` and the old key is already gone. Use [recovery](#recover-a-lost-paid-key) to get a working key.
## Revoke a key
Revoking is permanent. The key fails authentication immediately and can't be restored.
```bash
curl -X POST "https://api.creativeforesight.io/v1/keys/revoke" \
-H "Authorization: Bearer $CF_API_KEY"
```
```json
{ "data": { "revoked": true, "key_hint": "4b07d1e8", "subscription_cancelled": false } }
```
### Cancel a paid subscription
A key that pays for an active subscription can't be revoked on its own, because billing would keep running with no key to use. The call returns `409 SUBSCRIPTION_ACTIVE` until you confirm that you want the subscription to end too:
```bash
curl -X POST "https://api.creativeforesight.io/v1/keys/revoke" \
-H "Authorization: Bearer $CF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cancel_subscription": true}'
```
This cancels the subscription immediately and then revokes the key. The current month's base fee is not refunded, and any overage you have already used is invoiced. You'll get an email confirming the subscription has ended.
If you only want to replace a leaked key and keep your plan, [rotate it](#rotate-a-key) instead.
## Recover a lost paid key
If you've lost a paid key and no longer have the email it arrived in, prove you own the billing address instead.
1. Ask for a recovery token:
```bash
curl -X POST "https://api.creativeforesight.io/v1/keys/recover" \
-H "Content-Type: application/json" \
-d '{"email":"billing@example.com"}'
```
The answer is always `202`, whether or not the address matches a subscription. If it does, an email with a single-use `cfr_` token is on its way. The token expires in 30 minutes.
2. Redeem the token with the command from the email:
```bash
curl -X POST "https://api.creativeforesight.io/v1/keys/recover/confirm" \
-H "Content-Type: application/json" \
-d '{"token":"cfr_…"}'
```
Your old key stops working and the new key is emailed to the billing address. The response carries only the key hints.
The token works once. Opening the email doesn't use it; only the command does. If you didn't ask for a token, ignore the email and your key keeps working.
Recovery is limited to 10 requests per network per day and 3 tokens per key per hour. Free keys can't be recovered, because their email address is never verified: [create a new one](/api/signup) instead.
## Billing
- **A payment failed.** The payment-failed email links to Stripe's page for the unpaid invoice, where you can pay with an updated card. Your key keeps working through the 7-day grace period.
- **Your subscription ended.** Its key is revoked. To subscribe again, choose a plan on the [pricing page](https://creativeforesight.io/foresight-api/pricing).
---
# Data attribution
The Foresight API aggregates indicator data from public and licensed sources.
The following attributions apply to data served by this API; if you
redistribute data obtained from the Foresight API, you must preserve them.
- **FRED®** — This product uses the FRED® API but is not endorsed or certified
by the Federal Reserve Bank of St. Louis. Series sourced via FRED® cite the
original data provider (e.g., "Source: U.S. Bureau of Labor Statistics via
FRED®, Federal Reserve Bank of St. Louis"). Use of FRED-sourced data is
subject to the [FRED® API Terms of Use](https://fred.stlouisfed.org/docs/api/terms_of_use.html);
by consuming these series through the Foresight API you agree to be bound by them.
- **Federal Reserve Bank of New York (SOFR)** — The Secured Overnight Financing
Rate (SOFR) is subject to the Terms of Use posted at
[newyorkfed.org](https://www.newyorkfed.org/privacy/termsofuse). The New York
Fed is not responsible for publication of SOFR by Creative Foresight, does not
sanction or endorse any particular republication, and has no liability for your
use. Creative Foresight is not affiliated with the New York Fed. The New York
Fed does not sanction, endorse, or recommend any products or services offered
by Creative Foresight. SOFR is served via FRED®, Federal Reserve Bank of St. Louis.
- **Bank for International Settlements** — Source: Bank for International
Settlements (BIS), [https://data.bis.org](https://data.bis.org). The BIS is cited as the source of
these statistics; this product is not endorsed by or affiliated with the BIS,
and nothing in these statistics constitutes investment advice.
- **GDELT** — Contains data from the GDELT Project
([https://www.gdeltproject.org/](https://www.gdeltproject.org/)). News-sentiment indicators (gdelt_tone) are
derived by Creative Foresight from GDELT data.
- **U.S. federal sources** — Source: U.S. Bureau of Labor Statistics; U.S.
Bureau of Economic Analysis; U.S. Census Bureau; U.S. Energy Information
Administration; U.S. Department of the Treasury; U.S. Department of Housing
and Urban Development; Centers for Disease Control and Prevention; Internal
Revenue Service (Statistics of Income); FHFA. U.S. government works, public domain.
- **European Central Bank** — Source: European Central Bank. Reuse permitted
with attribution under the ECB's data policy.
- **Eurostat** — Source: Eurostat, © European Union, reuse permitted under the
Eurostat open-data licence (attribution required).
- **World Bank** — Source: World Bank Open Data (CC BY 4.0).
- **International Monetary Fund** — Source: International Monetary Fund, World
Economic Outlook database,
[https://data.imf.org/en/datasets/IMF.RES:WEO](https://data.imf.org/en/datasets/IMF.RES:WEO).
IMF data are available free of charge from the IMF.
- **OECD** — Source: OECD (2026), Composite leading indicators; Monthly
unemployment rates, OECD Data Explorer,
[https://data-explorer.oecd.org/](https://data-explorer.oecd.org/) (accessed daily).
Used under the [OECD terms and conditions](https://www.oecd.org/en/about/terms-conditions.html);
if you share or license work built on this data, pass this acknowledgment on.
- **ClinicalTrials.gov** — Source: ClinicalTrials.gov, a U.S. Government database
(U.S. National Library of Medicine). Values are Creative Foresight counts of
studies matching a query in active recruitment statuses, as processed by
ClinicalTrials.gov on the observation date.
- **Our World in Data** — Data: Our World in Data (CC BY 4.0).
- **Tennessee Comptroller of the Treasury** — Source: Tennessee Comptroller of
the Treasury, Transparency and Accountability for Governments (TAG) county
financial data (public record).
- **Mempool.space** — Data: mempool.space.
---
# Examples
Everything below was asked through
[`/v1/ask`](/api/ask) against the live API on July 22, 2026, and every answer
is reproduced **verbatim** — including the one the API initially refused to
answer.
## The local official
Tomorrow's post about the local economy needs numbers nobody can dispute — with
sources ready when someone asks.
They ask their AI agent:
> *How does Williamson County Tennessee's economy compare to the rest of the state right now?*
The answer:
> Williamson County significantly outperforms Tennessee on income and jobs, while both share the broader statewide labor-market recovery trend.
>
> **Income:** Williamson County's real median household income hit an all-time high of $135,594 in 2024, nearly double Tennessee's statewide high of $69,595 in the same year. Both grew steadily over the prior decade — Williamson up 51% since 2013 (3.8%/year), Tennessee up 57% (4.2%/year) — but Williamson's dollar level remains far above the state's throughout.
>
> **Unemployment:** Williamson County's unemployment rate is 2.4% (May 2026), the lowest of all 95 Tennessee counties, ranking 1st. Tennessee's statewide rate is 3.6%, meaning Williamson runs noticeably tighter/stronger in labor-market terms. Both have fallen sharply from their pandemic-era peaks (Williamson from 10.5% in April 2020, Tennessee from 15.7% in April 2020), reflecting a strong statewide recovery, but Williamson's rate has consistently sat below the state average.
>
> **Bottom line:** Williamson County is one of Tennessee's wealthiest and tightest labor markets — income nearly double the state median and the lowest unemployment rate among all 95 counties — while still tracking the same general upward income trend and post-pandemic labor recovery seen statewide.
API Request & Response
```bash
curl -G "https://api.creativeforesight.io/v1/ask" \
--data-urlencode "q=How does Williamson County Tennessee's economy compare to the rest of the state right now?" \
-H "X-API-Key: cf_live_..."
```
```json
{
"answer": "Williamson County significantly outperforms Tennessee on income and jobs, while both share the broader statewide labor-market recovery trend.\n\n**Income:** Williamson County's real median household income hit an all-time high of $135,594 in 2024, nearly double Tennessee's statewide high of $69,595 in the same year. Both grew steadily over the prior decade — Williamson up 51% since 2013 (3.8%/year), Tennessee up 57% (4.2%/year) — but Williamson's dollar level remains far above the state's throughout.\n\n**Unemployment:** Williamson County's unemployment rate is 2.4% (May 2026), the lowest of all 95 Tennessee counties, ranking 1st. Tennessee's statewide rate is 3.6%, meaning Williamson runs noticeably tighter/stronger in labor-market terms. Both have fallen sharply from their pandemic-era peaks (Williamson from 10.5% in April 2020, Tennessee from 15.7% in April 2020), reflecting a strong statewide recovery, but Williamson's rate has consistently sat below the state average.\n\n**Bottom line:** Williamson County is one of Tennessee's wealthiest and tightest labor markets — income nearly double the state median and the lowest unemployment rate among all 95 counties — while still tracking the same general upward income trend and post-pandemic labor recovery seen statewide.",
"citations": [
{
"tool": "search_indicators",
"args": {
"query": "median household income"
},
"summary": "{\"data\":[{\"id\":\"1ea921e3-f8c0-41fc-82d4-cd9544a9381e\",\"code\":\"census.acs_median_household_income.real_usd.y\",\"name\":\"ACS – Median Household Income (Real $)\",\"description\":null,\"source\":\"census\",\"categ"
},
{
"tool": "search_indicators",
"args": {
"query": "unemployment rate"
},
"summary": "{\"data\":[{\"id\":\"86d69c85-8507-4fc1-af32-5d2b49ceb29f\",\"code\":\"unemployment_rate\",\"name\":\"Unemployment Rate\",\"description\":\"Seasonally adjusted unemployment rate\",\"source\":\"bls\",\"category\":\"labor\",\"sub"
},
{
"tool": "search_indicators",
"args": {
"query": "gdp"
},
"summary": "{\"data\":[{\"id\":\"d9e073d5-7aa8-4fa5-b1f6-189ec706bad9\",\"code\":\"BEA:NIPA:T10101:1:Q\",\"name\":\"Real GDP % change\",\"description\":null,\"source\":\"bea\",\"category\":\"macro\",\"subcategory\":\"national-accounts\",\"ac"
},
{
"tool": "search_indicators",
"args": {
"query": "population"
},
"summary": "{\"data\":[{\"id\":\"12b4ca36-d4c2-43c9-a5b4-eccbdf18c70c\",\"code\":\"census.acs_foreign_born_share.pct.y\",\"name\":\"ACS – Foreign-Born Population Share (%)\",\"description\":null,\"source\":\"census\",\"category\":\"dem"
},
{
"tool": "get_insights",
"args": {
"indicator": "census.acs_median_household_income.real_usd.y",
"region": "47187"
},
"summary": "{\"facts\":[{\"kind\":\"watermark_high_distance\",\"params\":{\"latestDate\":\"2024-01-01\",\"latestValue\":135594,\"watermarkDate\":\"2024-01-01\",\"watermarkValue\":135594,\"distance\":0,\"percentDistance\":0,\"absPercentDi"
},
{
"tool": "get_insights",
"args": {
"indicator": "census.acs_median_household_income.real_usd.y",
"region": "47"
},
"summary": "{\"facts\":[{\"kind\":\"watermark_high_distance\",\"params\":{\"latestDate\":\"2024-01-01\",\"latestValue\":69595,\"watermarkDate\":\"2024-01-01\",\"watermarkValue\":69595,\"distance\":0,\"percentDistance\":0,\"absPercentDist"
},
{
"tool": "get_insights",
"args": {
"indicator": "unemployment_rate",
"region": "47187"
},
"summary": "{\"facts\":[{\"kind\":\"peer_rank\",\"params\":{\"rank\":95,\"total\":95,\"latestValue\":2.4,\"peers\":[{\"region\":\"47135\",\"value\":5.3},{\"region\":\"47067\",\"value\":4.7},{\"region\":\"47137\",\"value\":4.7},{\"region\":\"47069\",\""
},
{
"tool": "get_insights",
"args": {
"indicator": "unemployment_rate",
"region": "47"
},
"summary": "{\"facts\":[{\"kind\":\"trend\",\"params\":{\"direction\":\"down\",\"startDate\":\"2010-01-01\",\"endDate\":\"2026-05-01\",\"startValue\":10.5,\"endValue\":3.6,\"absoluteChange\":-6.9,\"percentChange\":-0.6571428571428571,\"absPe"
},
{
"tool": "get_insights",
"args": {
"indicator": "census.pep_total_population.count.y",
"region": "47187"
},
"summary": "{\"facts\":[],\"signals\":[],\"meta\":{\"indicator\":\"census.pep_total_population.count.y\",\"region\":\"47187\",\"unit\":\"count\",\"window\":\"all\",\"observation_count\":0,\"source_attribution\":\"Source: U.S. Census Bureau"
},
{
"tool": "get_insights",
"args": {
"indicator": "census.pep_total_population.count.y",
"region": "47"
},
"summary": "{\"facts\":[],\"signals\":[],\"meta\":{\"indicator\":\"census.pep_total_population.count.y\",\"region\":\"47\",\"unit\":\"count\",\"window\":\"all\",\"observation_count\":0,\"source_attribution\":\"Source: U.S. Census Bureau\"}}"
}
],
"resolved": {
"indicators": [
"census.acs_median_household_income.real_usd.y",
"unemployment_rate",
"census.pep_total_population.count.y"
],
"regions": [
"47187",
"47"
],
"window": "all"
},
"grounded": true,
"truncated": false,
"status": "ok",
"meta": {
"model": "anthropic/claude-sonnet-5",
"rounds": 3,
"toolCalls": 10,
"depth": "standard"
}
}
```
The prose above is the response's `answer` field, verbatim.
For the post itself, they follow up:
> *Give me three data points I can cite in a social media post about how strong Williamson County Tennessee's economy is.*
The answer:
> Here are three citable data points on Williamson County, TN's economy:
>
> 1. **Lowest unemployment in the state:** Williamson County posted a 2.4% unemployment rate as of May 2026 — the lowest of all 95 Tennessee counties (ranked 1st of 95).
>
> 2. **Record household income:** Real median household income hit an all-time high of $135,594 in 2024, up 51% from $89,779 in 2013 (about 3.8% annualized growth).
>
> 3. **Booming population:** The county's population reached an all-time high of 272,061 in 2025, up 48% since 2010 (roughly 2.6% annual growth) — the 6th-largest population among Tennessee's 95 counties.
API Request & Response
```bash
curl -G "https://api.creativeforesight.io/v1/ask" \
--data-urlencode "q=Give me three data points I can cite in a social media post about how strong Williamson County Tennessee's economy is." \
-H "X-API-Key: cf_live_..."
```
```json
{
"answer": "Here are three citable data points on Williamson County, TN's economy:\n\n1. **Lowest unemployment in the state:** Williamson County posted a 2.4% unemployment rate as of May 2026 — the lowest of all 95 Tennessee counties (ranked 1st of 95).\n\n2. **Record household income:** Real median household income hit an all-time high of $135,594 in 2024, up 51% from $89,779 in 2013 (about 3.8% annualized growth).\n\n3. **Booming population:** The county's population reached an all-time high of 272,061 in 2025, up 48% since 2010 (roughly 2.6% annual growth) — the 6th-largest population among Tennessee's 95 counties.",
"citations": [
{
"tool": "get_insights",
"args": {
"indicator": "population",
"region": "47187"
},
"summary": "{\"facts\":[{\"kind\":\"watermark_high_distance\",\"params\":{\"latestDate\":\"2025-01-01\",\"latestValue\":272061,\"watermarkDate\":\"2025-01-01\",\"watermarkValue\":272061,\"distance\":0,\"percentDistance\":0,\"absPercentDi"
},
{
"tool": "get_insights",
"args": {
"indicator": "median_income",
"region": "47187"
},
"summary": "{\"facts\":[{\"kind\":\"watermark_high_distance\",\"params\":{\"latestDate\":\"2024-01-01\",\"latestValue\":135594,\"watermarkDate\":\"2024-01-01\",\"watermarkValue\":135594,\"distance\":0,\"percentDistance\":0,\"absPercentDi"
},
{
"tool": "search_indicators",
"args": {
"query": "unemployment rate"
},
"summary": "{\"data\":[{\"id\":\"86d69c85-8507-4fc1-af32-5d2b49ceb29f\",\"code\":\"unemployment_rate\",\"name\":\"Unemployment Rate\",\"description\":\"Seasonally adjusted unemployment rate\",\"source\":\"bls\",\"category\":\"labor\",\"sub"
},
{
"tool": "get_insights",
"args": {
"indicator": "unemployment_rate",
"region": "47187"
},
"summary": "{\"facts\":[{\"kind\":\"peer_rank\",\"params\":{\"rank\":95,\"total\":95,\"latestValue\":2.4,\"peers\":[{\"region\":\"47135\",\"value\":5.3},{\"region\":\"47067\",\"value\":4.7},{\"region\":\"47137\",\"value\":4.7},{\"region\":\"47069\",\""
}
],
"resolved": {
"indicators": [
"population",
"median_income",
"unemployment_rate"
],
"regions": [
"47187"
],
"window": "all"
},
"grounded": true,
"truncated": false,
"status": "ok",
"meta": {
"model": "anthropic/claude-sonnet-5",
"rounds": 3,
"toolCalls": 4,
"depth": "standard"
}
}
```
The prose above is the response's `answer` field, verbatim.
## The economics commentator
The best take is the one that sounds outrageous and survives a fact-check.
They ask their AI agent:
> *Is the media mood about the economy more negative than what the economic data actually shows?*
The answer:
> Yes, media tone is running slightly negative while the underlying data still shows a mostly resilient economy — a modest but real mismatch.
>
> **Media mood:** The economic news tone score (global) sits at -0.02 as of 2026-07-20, on a -1 to 1 scale — i.e., mildly negative. It's actually improved recently, up 68% from a July 2026 low of -0.05 on 2026-07-19, but remains 27% below its July 2026 high of -0.01.
>
> **Actual economic data (US):**
> - GDP: 31,866 (2026-01-01), an all-time series high, up 1.4% from the prior year and up 130% since 1947 (~6.4%/year annualized). However, a signal flags GDP growth as "Slowing" — falling faster than its recent trend since July 2026, so there is a genuine deceleration underway.
> - Unemployment rate: 4.2% (June 2026), down slightly from 4.3% the prior month, and 72% below its April 2020 pandemic high — still historically low, though 24% above the April 2023 cyclical low of 3.4%.
>
> **Bottom line:** The hard data (record GDP, low unemployment) doesn't support outright negativity, but there is a real slowdown signal in GDP growth and unemployment ticking up from its 2023 low — so the negative mood isn't baseless, it's just more pessimistic than the level of economic performance itself suggests.
API Request & Response
```bash
curl -G "https://api.creativeforesight.io/v1/ask" \
--data-urlencode "q=Is the media mood about the economy more negative than what the economic data actually shows?" \
-H "X-API-Key: cf_live_..."
```
```json
{
"answer": "Yes, media tone is running slightly negative while the underlying data still shows a mostly resilient economy — a modest but real mismatch.\n\n**Media mood:** The economic news tone score (global) sits at -0.02 as of 2026-07-20, on a -1 to 1 scale — i.e., mildly negative. It's actually improved recently, up 68% from a July 2026 low of -0.05 on 2026-07-19, but remains 27% below its July 2026 high of -0.01.\n\n**Actual economic data (US):**\n- GDP: 31,866 (2026-01-01), an all-time series high, up 1.4% from the prior year and up 130% since 1947 (~6.4%/year annualized). However, a signal flags GDP growth as \"Slowing\" — falling faster than its recent trend since July 2026, so there is a genuine deceleration underway.\n- Unemployment rate: 4.2% (June 2026), down slightly from 4.3% the prior month, and 72% below its April 2020 pandemic high — still historically low, though 24% above the April 2023 cyclical low of 3.4%.\n\n**Bottom line:** The hard data (record GDP, low unemployment) doesn't support outright negativity, but there is a real slowdown signal in GDP growth and unemployment ticking up from its 2023 low — so the negative mood isn't baseless, it's just more pessimistic than the level of economic performance itself suggests.",
"citations": [
{
"tool": "get_latest",
"args": {
"indicator": "mood",
"region": "GLOBAL"
},
"summary": "{\"status\":\"ok\",\"data\":[{\"period_start\":\"2026-07-20\",\"period_end\":\"2026-07-20\",\"period_type\":\"daily\",\"value\":-0.01611,\"revision\":0,\"is_preliminary\":false,\"source_updated_at\":null}],\"meta\":{\"indicator\":"
},
{
"tool": "get_insights",
"args": {
"indicator": "cf:osint.econ_news_tone",
"region": "US"
},
"summary": "{\"facts\":[],\"signals\":[],\"meta\":{\"indicator\":\"cf:osint.econ_news_tone\",\"region\":\"US\",\"unit\":\"score (-1 to 1)\",\"window\":\"all\",\"observation_count\":0,\"source_attribution\":\"Creative Foresight original ind"
},
{
"tool": "get_insights",
"args": {
"indicator": "gdp",
"region": "US"
},
"summary": "{\"facts\":[{\"kind\":\"trend\",\"params\":{\"direction\":\"up\",\"startDate\":\"1947-01-01\",\"endDate\":\"2026-01-01\",\"startValue\":243.164,\"endValue\":31865.721,\"absoluteChange\":31622.557,\"percentChange\":130.0462116102"
},
{
"tool": "get_insights",
"args": {
"indicator": "unemployment_rate",
"region": "US"
},
"summary": "{\"facts\":[{\"kind\":\"trend\",\"params\":{\"direction\":\"down\",\"startDate\":\"2010-01-01\",\"endDate\":\"2026-06-01\",\"startValue\":9.8,\"endValue\":4.2,\"absoluteChange\":-5.6000000000000005,\"percentChange\":-0.571428571"
},
{
"tool": "get_insights",
"args": {
"indicator": "cf:osint.econ_news_tone",
"region": "GLOBAL"
},
"summary": "{\"facts\":[{\"kind\":\"watermark_low_distance\",\"params\":{\"latestDate\":\"2026-07-20\",\"latestValue\":-0.01611,\"watermarkDate\":\"2026-07-19\",\"watermarkValue\":-0.05012,\"distance\":0.03401,\"percentDistance\":-0.678"
}
],
"resolved": {
"indicators": [
"mood",
"cf:osint.econ_news_tone",
"gdp",
"unemployment_rate"
],
"regions": [
"GLOBAL",
"US"
],
"window": "all"
},
"grounded": true,
"truncated": false,
"status": "ok",
"meta": {
"model": "anthropic/claude-sonnet-5",
"rounds": 3,
"toolCalls": 5,
"depth": "standard"
}
}
```
The prose above is the response's `answer` field, verbatim.
Chasing the inequality angle, they push further:
> *Where in America is unemployment still painfully high even though the national rate is near record lows?*
The first answer is a refusal — the API doesn't guess, and unanswerable
questions are free:
> — I searched for regional/state/county-level unemployment_rate data (California, Nevada, New Mexico, D.C., Illinois, and several specific counties) to identify pockets of high unemployment, but no sub-national observations were returned (either empty results or "provisioning" status with no data yet held). Only the national unemployment_rate series was available, showing the U.S. rate at 4.2% as of June 2026 — I cannot verify which specific regions remain elevated without that data.
But the question itself taught the API what was missing: every series it
probed was queued and fetched automatically. Minutes later, the same
question:
> National unemployment sits near historic lows at **4.2%** (June 2026, U.S., BLS, seasonally adjusted). But that headline number masks sharp regional divergence.
>
> **Imperial County, California** stands out as one of the most persistently distressed labor markets in the country: its unemployment rate was **16.9%** in May 2026 — roughly four times the national rate. This gap has been a long-standing feature of the region, driven by its agriculture-dependent, seasonal economy along the border.
>
> Other traditionally high-unemployment areas (e.g., counties in Arizona, New Mexico, and West Virginia) are still being provisioned in this dataset and weren't available to pull specific current figures for this answer, so I can't cite exact numbers for them right now. Based on available data, Imperial County, CA is the clearest documented case of "painfully high" local unemployment persisting despite the strong national rate.
API Request & Response
```bash
curl -G "https://api.creativeforesight.io/v1/ask" \
--data-urlencode "q=Where in America is unemployment still painfully high even though the national rate is near record lows?" \
-H "X-API-Key: cf_live_..."
```
**First ask — honest refusal, provisioning begins**
```json
{
"answer": null,
"status": "no_data",
"message": "— I searched for regional/state/county-level unemployment_rate data (California, Nevada, New Mexico, D.C., Illinois, and several specific counties) to identify pockets of high unemployment, but no sub-national observations were returned (either empty results or \"provisioning\" status with no data yet held). Only the national unemployment_rate series was available, showing the U.S. rate at 4.2% as of June 2026 — I cannot verify which specific regions remain elevated without that data."
}
```
**Same question, minutes later**
```json
{
"answer": "National unemployment sits near historic lows at **4.2%** (June 2026, U.S., BLS, seasonally adjusted). But that headline number masks sharp regional divergence.\n\n**Imperial County, California** stands out as one of the most persistently distressed labor markets in the country: its unemployment rate was **16.9%** in May 2026 — roughly four times the national rate. This gap has been a long-standing feature of the region, driven by its agriculture-dependent, seasonal economy along the border.\n\nOther traditionally high-unemployment areas (e.g., counties in Arizona, New Mexico, and West Virginia) are still being provisioned in this dataset and weren't available to pull specific current figures for this answer, so I can't cite exact numbers for them right now. Based on available data, Imperial County, CA is the clearest documented case of \"painfully high\" local unemployment persisting despite the strong national rate.",
"citations": [
{
"tool": "search_indicators",
"args": {
"query": "unemployment rate"
},
"summary": "{\"data\":[{\"id\":\"86d69c85-8507-4fc1-af32-5d2b49ceb29f\",\"code\":\"unemployment_rate\",\"name\":\"Unemployment Rate\",\"description\":\"Seasonally adjusted unemployment rate\",\"source\":\"bls\",\"category\":\"labor\",\"sub"
},
{
"tool": "get_latest",
"args": {
"indicator": "unemployment_rate",
"region": "US"
},
"summary": "{\"status\":\"ok\",\"data\":[{\"period_start\":\"2026-06-01\",\"period_end\":null,\"period_type\":\"monthly\",\"value\":4.2,\"revision\":0,\"is_preliminary\":false,\"source_updated_at\":null}],\"meta\":{\"indicator\":\"unemployme"
},
{
"tool": "get_active_signals",
"args": {
"indicators": [
"unemployment_rate"
],
"type": "extreme",
"strength": "strong"
},
"summary": "{\"data\":[],\"meta\":{\"count\":0,\"limit\":12,\"filters\":{\"indicator\":[\"unemployment_rate\"],\"type\":\"extreme\",\"strength\":\"strong\",\"active\":true}}}"
},
{
"tool": "get_active_signals",
"args": {
"indicators": [
"unemployment_rate"
],
"strength": "neutral"
},
"summary": "{\"data\":[],\"meta\":{\"count\":0,\"limit\":12,\"filters\":{\"indicator\":[\"unemployment_rate\"],\"strength\":\"neutral\",\"active\":true}}}"
},
{
"tool": "get_active_signals",
"args": {
"indicators": [
"unemployment_rate"
],
"type": "extreme",
"strength": "weak"
},
"summary": "{\"data\":[],\"meta\":{\"count\":0,\"limit\":12,\"filters\":{\"indicator\":[\"unemployment_rate\"],\"type\":\"extreme\",\"strength\":\"weak\",\"active\":true}}}"
},
{
"tool": "get_latest",
"args": {
"indicator": "unemployment_rate",
"region": "06025"
},
"summary": "{\"status\":\"ok\",\"data\":[{\"period_start\":\"2026-05-01\",\"period_end\":\"2026-05-01\",\"period_type\":\"monthly\",\"value\":16.9,\"revision\":0,\"is_preliminary\":false,\"source_updated_at\":null}],\"meta\":{\"indicator\":\"u"
},
{
"tool": "get_latest",
"args": {
"indicator": "unemployment_rate",
"region": "54047"
},
"summary": "{\"status\":\"provisioning\",\"message\":\"No data held yet for unemployment_rate in 54047; automatic provisioning has started — retry this question later and the series should be available.\"}"
},
{
"tool": "get_latest",
"args": {
"indicator": "unemployment_rate",
"region": "04027"
},
"summary": "{\"status\":\"provisioning\",\"message\":\"No data held yet for unemployment_rate in 04027; automatic provisioning has started — retry this question later and the series should be available.\"}"
},
{
"tool": "get_latest",
"args": {
"indicator": "unemployment_rate",
"region": "35039"
},
"summary": "{\"status\":\"provisioning\",\"message\":\"No data held yet for unemployment_rate in 35039; automatic provisioning has started — retry this question later and the series should be available.\"}"
}
],
"resolved": {
"indicators": [
"unemployment_rate"
],
"regions": [
"US",
"06025",
"54047",
"04027",
"35039"
],
"window": "all"
},
"grounded": true,
"truncated": false,
"status": "ok",
"meta": {
"model": "anthropic/claude-sonnet-5",
"rounds": 5,
"toolCalls": 9,
"depth": "standard"
}
}
```
The prose above is the response's `answer` field, verbatim.
## The dashboard builder
A "what changed?" situation monitor needs one feed that already did the
synthesis — trend breaks, momentum, and mood in a single call.
They ask their AI agent:
> *What changed in the economy this month — which indicators broke from trend, and what is the economic news tone right now?*
The answer:
> ## Economic News Tone: Right at Neutral, Recovering from a Recent Low
>
> The global economic news tone score sits at **-0.02** as of July 20, 2026 — essentially neutral (scale is -1 to 1). This is up 68% from its July 2026 low of **-0.05** (July 19), but still 27% below its July 2026 high of **-0.01** (July 10). Tone rose over a 4-day run from July 12–15, and over the broader July 7–20 window, tone is up 25% (from -0.02 to -0.02, i.e., a small absolute move of 0.005). Bottom line: sentiment is mixed-to-flat, drifting slightly positive off a recent dip, not decisively bullish or bearish.
>
> ## What Broke From Trend
>
> **Strong trend breaks (most notable):**
> - **Euro-area corporate lending (France):** outstanding bank loans to non-financial corporations reversed direction as of October 2024.
> - **Italy & Spain short-term corporate lending rates:** both reversed trend in October 2024, moving from rising to falling.
> - **Ireland & France inflation:** Irish HICP and French core HICP both broke trend in 2024 (May 2024), turning from rising to falling.
> - **Williamson County, TX — company management employment:** sharp reversal starting December 2023.
>
> **Neutral-strength breaks worth flagging:**
> - **US building permits** reversed trend in October 2024.
> - **US crude oil production** changed direction in December 2024.
> - **Germany unemployment rate** shifted trend in October 2024.
> - **US 3-month T-bill rate** and **US CPI inflation** both show long-standing trend breaks (1936 and 2008 respectively — likely structural, not new news).
> - **Turkish lira/EUR exchange rate** broke trend as recently as **June 2026** — the most current of the group.
>
> ## What's Accelerating Right Now (last few weeks)
>
> - **Euro-area government bond yields (1Y, 2Y, 5Y, 10Y, and AAA-rated equivalents)** are all falling faster than their recent trend since **July 9, 2026** — a synchronized, strong move down across the curve.
> - **US 2-year Treasury yield** has been falling faster than trend since **June 23, 2026**.
> - **US crude oil production** is rising faster than trend (strong signal, since October 2024).
> - **US GDP** is sliding — falling faster than trend since **October 2025** (flagged negative tone).
> - **Germany core inflation** is easing faster than trend since **February 2025** (flagged positive).
> - **UK central bank policy rate** has been rising faster than trend since early 2026.
> - **Williamson County, TX employment rate** is picking up, accelerating since May 2026 (positive).
>
> No indicators currently show "extreme" readings (outside historical bounds).
>
> **Net read:** The most current, live-market signal is the euro-area yield curve dropping sharply since July 9 alongside a weakening Turkish lira trend break in June — both pointing to shifting rate expectations in Europe. US GDP deceleration is the key negative macro signal, while news tone itself remains close to flat/neutral.
API Request & Response
```bash
curl -G "https://api.creativeforesight.io/v1/ask" \
--data-urlencode "q=What changed in the economy this month — which indicators broke from trend, and what is the economic news tone right now?" \
-H "X-API-Key: cf_live_..."
```
```json
{
"answer": "## Economic News Tone: Right at Neutral, Recovering from a Recent Low\n\nThe global economic news tone score sits at **-0.02** as of July 20, 2026 — essentially neutral (scale is -1 to 1). This is up 68% from its July 2026 low of **-0.05** (July 19), but still 27% below its July 2026 high of **-0.01** (July 10). Tone rose over a 4-day run from July 12–15, and over the broader July 7–20 window, tone is up 25% (from -0.02 to -0.02, i.e., a small absolute move of 0.005). Bottom line: sentiment is mixed-to-flat, drifting slightly positive off a recent dip, not decisively bullish or bearish.\n\n## What Broke From Trend\n\n**Strong trend breaks (most notable):**\n- **Euro-area corporate lending (France):** outstanding bank loans to non-financial corporations reversed direction as of October 2024.\n- **Italy & Spain short-term corporate lending rates:** both reversed trend in October 2024, moving from rising to falling.\n- **Ireland & France inflation:** Irish HICP and French core HICP both broke trend in 2024 (May 2024), turning from rising to falling.\n- **Williamson County, TX — company management employment:** sharp reversal starting December 2023.\n\n**Neutral-strength breaks worth flagging:**\n- **US building permits** reversed trend in October 2024.\n- **US crude oil production** changed direction in December 2024.\n- **Germany unemployment rate** shifted trend in October 2024.\n- **US 3-month T-bill rate** and **US CPI inflation** both show long-standing trend breaks (1936 and 2008 respectively — likely structural, not new news).\n- **Turkish lira/EUR exchange rate** broke trend as recently as **June 2026** — the most current of the group.\n\n## What's Accelerating Right Now (last few weeks)\n\n- **Euro-area government bond yields (1Y, 2Y, 5Y, 10Y, and AAA-rated equivalents)** are all falling faster than their recent trend since **July 9, 2026** — a synchronized, strong move down across the curve.\n- **US 2-year Treasury yield** has been falling faster than trend since **June 23, 2026**.\n- **US crude oil production** is rising faster than trend (strong signal, since October 2024).\n- **US GDP** is sliding — falling faster than trend since **October 2025** (flagged negative tone).\n- **Germany core inflation** is easing faster than trend since **February 2025** (flagged positive).\n- **UK central bank policy rate** has been rising faster than trend since early 2026.\n- **Williamson County, TX employment rate** is picking up, accelerating since May 2026 (positive).\n\nNo indicators currently show \"extreme\" readings (outside historical bounds).\n\n**Net read:** The most current, live-market signal is the euro-area yield curve dropping sharply since July 9 alongside a weakening Turkish lira trend break in June — both pointing to shifting rate expectations in Europe. US GDP deceleration is the key negative macro signal, while news tone itself remains close to flat/neutral.",
"citations": [
{
"tool": "get_latest",
"args": {
"indicator": "tone",
"region": "GLOBAL"
},
"summary": "{\"status\":\"ok\",\"data\":[{\"period_start\":\"2026-07-20\",\"period_end\":\"2026-07-20\",\"period_type\":\"daily\",\"value\":-0.01611,\"revision\":0,\"is_preliminary\":false,\"source_updated_at\":null}],\"meta\":{\"indicator\":"
},
{
"tool": "get_active_signals",
"args": {
"type": "trend_break",
"limit": 20
},
"summary": "{\"data\":[{\"id\":\"b65f4abd-b544-43bd-9271-385eb90c7e40\",\"indicator\":{\"code\":\"une_rt_m:M.SA.TOTAL.PC_ACT.T\",\"name\":\"Unemployment by sex and age - monthly data — Seasonally adjusted data, not calendar adj"
},
{
"tool": "get_insights",
"args": {
"indicator": "cf:osint.econ_news_tone",
"region": "GLOBAL",
"window": "3m"
},
"summary": "{\"facts\":[{\"kind\":\"watermark_low_distance\",\"params\":{\"latestDate\":\"2026-07-20\",\"latestValue\":-0.01611,\"watermarkDate\":\"2026-07-19\",\"watermarkValue\":-0.05012,\"distance\":0.03401,\"percentDistance\":-0.678"
},
{
"tool": "get_active_signals",
"args": {
"type": "acceleration",
"limit": 15
},
"summary": "{\"data\":[{\"id\":\"da26aae0-b64e-492a-a97a-794da4f0c4b0\",\"indicator\":{\"code\":\"PAPRPUS\",\"name\":\"Crude oil production, total (thousand barrels/day, monthly)\",\"category\":\"energy\",\"unit\":\"thousand barrels pe"
},
{
"tool": "get_active_signals",
"args": {
"type": "extreme",
"limit": 15
},
"summary": "{\"data\":[],\"meta\":{\"count\":0,\"limit\":15,\"filters\":{\"type\":\"extreme\",\"strength\":\"neutral\",\"active\":true}}}"
}
],
"resolved": {
"indicators": [
"tone",
"cf:osint.econ_news_tone"
],
"regions": [
"GLOBAL"
],
"window": "all"
},
"grounded": true,
"truncated": false,
"status": "ok",
"meta": {
"model": "anthropic/claude-sonnet-5",
"rounds": 3,
"toolCalls": 5,
"depth": "standard"
}
}
```
The prose above is the response's `answer` field, verbatim.
---
Every response above is reproducible: take the question, run the call, and
compare. The numbers will move as new data arrives — that's the point. Get a
key with one request at [Signup](/api/signup), or pay per request with
[x402 or Lightning](/guides/paying).
---
# API Reference
The v1 API exposes anonymous catalog routes, self-serve free-tier signup, and API-key or x402-paid observation, insight, and signal routes.
## Contract
Use the OpenAPI document for generated clients, schema validation, and endpoint discovery:
OpenAPI JSON
OpenAPI 3.1 contract for the public Foresight API read surface.
LLM Reference
Concatenated docs for agents that prefer a plain-text reference.
## Authentication
Catalog endpoints are anonymous. `POST /v1/signup` issues a free-tier API key. Observation, insight, and signal endpoints accept either an API key or an x402 payment.
```bash
curl "https://api.creativeforesight.io/v1/observations?indicator=UNRATE" \
-H "Authorization: Bearer $CF_API_KEY"
```
API keys can be sent as `Authorization: Bearer cf_live_...` or `X-API-Key: cf_live_...`.
## Endpoints
| Endpoint | Auth | Description |
| --- | --- | --- |
| [`POST /v1/signup`](/api/signup) | Anonymous | Create a free-tier API key. |
| [`GET /v1/sources`](/api/sources) | Anonymous | List data sources. |
| [`GET /v1/indicators`](/api/indicators) | Anonymous | Search and page through indicators. |
| [`GET /v1/indicators/{code}`](/api/indicators) | Anonymous | Fetch indicator detail by code. |
| [`GET /v1/regions`](/api/regions) | Anonymous | List regions and filter by type or parent. |
| [`GET /v1/observations`](/api/observations) | API key or x402 | List observations for an indicator. |
| [`GET /v1/observations/latest`](/api/observations/latest) | API key or x402 | Fetch the latest observation for an indicator. |
| [`GET /v1/insights`](/api/insights) | API key or x402 | Compute deterministic facts and active signals for an indicator. |
| [`GET /v1/signals`](/api/signals) | API key or x402 | List signal interval history. |
| [`GET /v1/signals/active`](/api/signals) | API key or x402 | List currently open signal intervals. |
| `GET /v1/ping` | API key | Validate bearer API key connectivity. |
## Response conventions
Successful list and detail responses use `{ "data": ..., "meta": ... }` when pagination or query metadata is available. Errors use `{ "error": { "code": "...", "message": "...", "details": ... } }`.
---
# Signup
`POST /v1/signup` creates a free-tier live API key. The raw `cf_live_` key is returned exactly once; store it immediately.
## Request
```bash
curl -X POST "https://api.creativeforesight.io/v1/signup" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","source":"Hacker News"}'
```
## Body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | Yes | Email address used to label the key. |
| `source` | string | No | Where did you find us? Free text, up to 120 characters. |
| `utm_source` | string | No | UTM source tag from the link that referred you, up to 120 characters. |
| `utm_medium` | string | No | UTM medium tag, up to 120 characters. |
| `utm_campaign` | string | No | UTM campaign tag, up to 120 characters. |
The optional fields are used only for anonymous attribution analytics. They are never stored with your key, and a value that contains an email address is dropped.
## Response
```json
{
"data": {
"key": "cf_live_0000000000000000000000000000000000000000000000000000000000000000",
"key_hint": "...0000",
"rate_limit_per_minute": 60,
"docs_url": "https://docs.creativeforesight.io"
},
"meta": {
"notice": "Store this key now — it is shown exactly once and cannot be retrieved again."
}
}
```
## Rate limit
Signup is limited to 5 keys per IP address per day. After that limit, the endpoint returns `429 RATE_LIMIT_EXCEEDED` with the message `Too many API keys created from this network today. Try again tomorrow, or contact support@creativeforesight.io.`
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | `email` is missing or invalid, or an optional field is not a string of at most 120 characters. |
| `429` | `RATE_LIMIT_EXCEEDED` | Too many API keys were created from this network today. |
| `500` | `INTERNAL_ERROR` | The key could not be created. |
---
# Sources
`GET /v1/sources` lists active data sources and their indicator counts. This endpoint is anonymous and cached.
## Request
```bash
curl "https://api.creativeforesight.io/v1/sources"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `include_inactive` | boolean | No | Include inactive sources. Defaults to `false`. |
## Source object
| Field | Type | Description |
| --- | --- | --- |
| `slug` | string | Stable source identifier. |
| `name` | string | Display name. |
| `provider` | string or null | Provider label when different from the source. |
| `category` | string or null | Source category. |
| `indicator_count` | integer | Number of indicators attached to the source. |
| `docs_url` | string or null | Provider documentation URL. |
## Response
```json
{
"data": [
{
"slug": "fred",
"name": "FRED",
"provider": "Federal Reserve Bank of St. Louis",
"category": "macro",
"indicator_count": 150,
"docs_url": "https://fred.stlouisfed.org/docs/api/fred/"
}
]
}
```
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `500` | `INTERNAL_SERVER_ERROR` | The sources catalog could not be loaded. |
---
# Indicators
Indicators describe available time series. Use `GET /v1/indicators` for search and paging, then `GET /v1/indicators/{code}` for detail.
## List indicators
`GET /v1/indicators` is anonymous and cached.
```bash
curl "https://api.creativeforesight.io/v1/indicators?search=unemployment&limit=5"
```
### Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | string | No | Filter by source slug. |
| `category` | string | No | Filter by top-level taxonomy domain, such as `labor` or `government-finance`. |
| `frequency` | string | No | Filter by source frequency. |
| `search` | string | No | Text search across indicator metadata. |
| `include_inactive` | boolean | No | Include inactive indicators. Defaults to `false`. |
| `limit` | integer | No | Page size, 0 to 100. Defaults to 50. |
| `offset` | integer | No | Page offset. Defaults to 0. |
### Indicator object
| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | Internal stable identifier. |
| `code` | string | Provider or Creative Foresight indicator code. |
| `name` | string | Display name. |
| `description` | string or null | Description when available. |
| `source` | string or null | Source slug. |
| `category` | string or null | Top-level taxonomy domain. |
| `subcategory` | string or null | Kebab-case taxonomy subcategory. |
| `access_tier` | `free` or `premium` | Access tier. `premium` requires an API key granting `creative-foresight`, or x402. |
| `unit` | string or null | Unit label. |
| `frequency` | string or null | Source frequency. |
| `is_active` | boolean | Whether the indicator is currently active. |
### Response
```json
{
"data": [
{
"id": "00000000-0000-0000-0000-000000000000",
"code": "UNRATE",
"name": "Unemployment Rate",
"description": "Civilian unemployment rate.",
"source": "fred",
"category": "labor",
"subcategory": "unemployment",
"access_tier": "free",
"unit": "percent",
"frequency": "monthly",
"is_active": true
}
],
"meta": {
"total": 1,
"limit": 5,
"offset": 0
}
}
```
## Get indicator detail
`GET /v1/indicators/{code}` returns source detail, default region, available regions, and date range.
```bash
curl "https://api.creativeforesight.io/v1/indicators/UNRATE"
```
### Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | string | No | Disambiguate duplicate provider codes. |
| `include_inactive` | boolean | No | Include inactive indicators. Defaults to `false`. |
### Response
```json
{
"data": {
"id": "00000000-0000-0000-0000-000000000000",
"code": "UNRATE",
"name": "Unemployment Rate",
"description": "Civilian unemployment rate.",
"source": {
"slug": "fred",
"name": "FRED"
},
"category": "labor",
"subcategory": "unemployment",
"access_tier": "free",
"unit": "percent",
"frequency": "monthly",
"default_region": {
"code": "US",
"name": "United States"
},
"available_regions": ["US"],
"date_range": {
"start": "1948-01-01",
"end": "2026-05-01"
}
},
"meta": {
"ambiguous_with": []
}
}
```
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | A query parameter is invalid. |
| `404` | `RESOURCE_NOT_FOUND` | The requested indicator was not found. |
| `500` | `INTERNAL_SERVER_ERROR` | Indicator catalog or availability could not be loaded. |
---
# Regions
`GET /v1/regions` lists available geographies. This endpoint is anonymous and cached.
## Request
```bash
curl "https://api.creativeforesight.io/v1/regions?type=county&parent=TN"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | string | No | Filter by region type, such as `country`, `state`, or `county`. |
| `parent` | string | No | Filter by parent region code. |
## Region object
| Field | Type | Description |
| --- | --- | --- |
| `code` | string | Stable region code. Counties commonly use 5-digit FIPS codes. |
| `type` | string | Region type. |
| `name` | string | Display name. |
| `slug` | string | URL-safe region slug. |
| `parent_code` | string or null | Parent geography when available. |
| `fips_state` | string or null | State FIPS code when applicable. |
| `fips_county` | string or null | County FIPS code when applicable. |
## Response
```json
{
"data": [
{
"code": "47187",
"type": "county",
"name": "Williamson County, Tennessee",
"slug": "williamson-county-tennessee",
"parent_code": "TN",
"fips_state": "47",
"fips_county": "187"
}
]
}
```
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `500` | `INTERNAL_SERVER_ERROR` | The regions catalog could not be loaded. |
---
# Concepts
Ask for the thing, not the source. A **concept** is a plain word — `population`, `gdp`, `median_income`, `median_home_value`, `unemployment_rate` — that works anywhere an indicator code does. Creative Foresight chooses which underlying series answers it for each kind of place, and every response tells you exactly which series that was.
```bash
curl "https://api.creativeforesight.io/v1/observations?indicator=population®ion=47187" \
-H "Authorization: Bearer cf_live_REPLACE_ME"
```
```json
{
"data": [{ "period_start": "2025-01-01", "value": 272061, ... }],
"meta": {
"indicator": "population_total",
"region": "47187",
"concept": {
"code": "population",
"resolved_indicator": "population_total",
"source": "census"
}
}
}
```
## How resolution works
- **Exact indicator codes always win.** If you ask for `UNRATE` or `census.b25077_001e.y`, you get exactly that series — concepts never intercept a real code.
- **Concepts resolve by place kind.** The same concept can map to different series for a county, a state, the US, or another country. `gdp` for Germany answers from the World Bank's annual series; `gdp` for the US answers from FRED's richer quarterly series. The choice is editorial — that's the point — and `meta.concept` always discloses it.
- **Concepts require a `region`.** A concept is a question about a place.
- **Honest refusals.** If a concept isn't curated for the kind of place you asked about, you get a clear `404`: *"'gdp' isn't available at the county level; the closest available is national."* — never a coarser number dressed up as your region.
- **Data that isn't loaded yet** may return `202` with `Retry-After`, exactly like any other request for a series being prepared — retry with the indicator named in the envelope, or just repeat your concept request.
## Current concepts
| Concept | Works for | Answers from |
| --- | --- | --- |
| `population` | county, state, national | Census total population |
| `unemployment_rate` | county, state, national | BLS unemployment rate |
| `labor_force`, `employed` | county, state | BLS labor-force measures |
| `median_income` | county, state | ACS median household income |
| `median_home_value` | county, state | ACS median home value |
| `gdp` | country (US answers from FRED quarterly; other countries from World Bank annual) | FRED / World Bank |
| `employment_rate` | county, state | Computed: employed ÷ labor force |
| `sentiment`, `tone`, `mood` | global (`region=GLOBAL`) | GDELT-derived daily economic news tone |
## Derived concepts
Some concepts are measures no source publishes — we compute them. `employment_rate` (employed residents as a share of the labor force) is the first: ask for it like any concept, and the response's `meta.concept.inputs` discloses every underlying series that fed the calculation.
```json
"concept": {
"code": "employment_rate",
"resolved_indicator": "cf:employment_rate.47187",
"source": "creative-foresight",
"inputs": { "employed": "employed", "labor_force": "labor_force" }
}
```
If any ingredient isn't available for the kind of place you asked about, the refusal names it: *"'employment_rate' isn't available at the national level; its employed input is only available at the county level."*
## Concepts in `/v1/ask`
Natural-language questions resolve to concepts first, deterministically — ask "what's the population of Williamson County?" twice and both answers cite the same series, because the choice is a registry lookup, not model judgment.
Concepts work on every read endpoint — observations, latest, insights, signals, and panel — and the catalog grows as we curate more. Each entry carries a documented selection rationale on our side.
---
# Flows
`GET /v1/flows` returns directed origin-to-destination flow rows for a region filter. Use it for questions like "where are Williamson County's in-migrants coming from?" or "where are residents leaving Davidson County going?"
When `period` is omitted, the API uses the latest `period_start` available for the supplied filters and reports that date in `meta.period`.
## Request
```bash
curl "https://api.creativeforesight.io/v1/flows?destination=47187&measure=agi&limit=3" \
-H "Authorization: Bearer $CF_API_KEY"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `origin` | string | No | Origin region code. At least one of `origin` or `destination` is required. |
| `destination` | string | No | Destination region code. At least one of `origin` or `destination` is required. |
| `measure` | string | No | Flow measure, such as `agi`, `returns`, or `individuals`. |
| `period` | string | No | `period_start` date in `YYYY-MM-DD` format. Defaults to the latest available period for the filters. |
| `limit` | integer | No | Maximum rows to return. Defaults to `50` and caps at `500`. |
## Response
```json
{
"data": [
{
"origin": {
"code": "47037",
"name": "Davidson County"
},
"destination": {
"code": "47187",
"name": "Williamson County"
},
"measure": "agi",
"period_start": "2022-01-01",
"value": 250000
}
],
"meta": {
"count": 1,
"limit": 3,
"period": "2022-01-01",
"filters": {
"destination": "47187",
"measure": "agi"
},
"units": {
"agi": "USD, thousands",
"returns": "count",
"individuals": "count"
}
}
}
```
`value` is reported exactly as published by the source. For IRS migration flow measures, `agi` values are thousands of USD; `returns` and `individuals` are counts.
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | Required or optional query parameters are invalid, including missing both `origin` and `destination`. |
| `401` | `INVALID_API_KEY` | The request did not include a valid API key and was not a settled x402 request. |
| `404` | `RESOURCE_NOT_FOUND` | The requested origin or destination region was not found. |
| `429` | `RATE_LIMIT_EXCEEDED` | The API key exceeded its one-minute request limit. |
| `500` | `INTERNAL_ERROR` | Flow data could not be loaded. |
---
# Panel
`GET /v1/panel` returns multiple indicators aligned on a single date grid — one call instead of N, with the alignment already done. Use it to feed charts, models, or spreadsheets that need several series over the same dates.
Pricing: **$0.05 per paid x402 request** or a premium API key.
## Request
```bash
curl "https://api.creativeforesight.io/v1/panel?indicators=UNRATE,PAYEMS®ion=US&start_date=2025-01-01" \
-H "Authorization: Bearer cf_live_REPLACE_ME"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `indicators` | string | Yes | Comma-separated indicator codes, 1–10. Duplicates are ignored. |
| `region` | string | No | One region code applied to every series. |
| `frequency` | string | No | Target frequency: `monthly`, `quarterly`, or `annual`. Finer series are downsampled to it; series coarser than the target return `400`. `daily` is accepted only when every series is natively daily. Omit it to align same-frequency panels at their native frequency, or mixed panels at the coarsest. |
| `resample_method` | string | No | How finer series aggregate into target buckets: `mean` (default), `last`, or `sum`. Applies to every resampled series. |
| `start_date` | string | No | `YYYY-MM-DD` inclusive lower bound. |
| `end_date` | string | No | `YYYY-MM-DD` inclusive upper bound. |
| `missing` | string | No | Missing-data policy. `drop` (default) keeps only dates present in every series; `ffill` uses every date any series has and carries each series' last value forward (gaps before a series begins stay `null`). |
## Response
```json
{
"dates": ["2025-01-01", "2025-02-01"],
"series": {
"UNRATE": [4.0, 4.2],
"PAYEMS": [158268, 158310]
},
"meta": {
"units": { "UNRATE": "percent", "PAYEMS": "thousands" },
"alignment": { "missing": "drop", "grid_points": 2 },
"region": "US",
"frequency": "monthly"
}
}
```
`dates` is the shared grid; each `series` array is positionally aligned to it.
## Limits
- Mixed-frequency panels resample finer series to the coarsest frequency (or to an explicit `frequency=`); `meta.resample` lists each resampled series' native frequency and the method used. Incomplete trailing buckets are dropped per series before alignment, so a partial month never masquerades as a complete one.
- Weekly-only panels must pass `frequency=monthly` (or coarser) — weekly is not a resampling target.
- The aligned grid holds at most 5,000 points (dates × series). A series with more history than the window allows returns `400` — narrow `start_date`/`end_date`.
- A panel containing any `cf:*` series requires premium access; a panel of standard series works with any valid key.
- Unknown indicator codes return `404` naming the codes.
---
# Insights
`GET /v1/insights` returns salience-ranked computed facts for one indicator series and includes active signals when available. Facts are deterministic descriptive calculations over observations; they are not LLM-generated text.
## Request
```bash
curl "https://api.creativeforesight.io/v1/insights?indicator=UNRATE®ion=US&window=5y" \
-H "Authorization: Bearer $CF_API_KEY"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `indicator` | string | Yes | Indicator code, such as `UNRATE` or `cf:labor_composite`. |
| `source` | string | No | Source slug for duplicate provider codes. |
| `region` | string | No | Region code. Uses the indicator default region when omitted. |
| `window` | string | No | `all` or an integer followed by `d`, `m`, or `y`, such as `90d`, `12m`, or `5y`. Defaults to `all`. |
## Fact kinds
| Kind | Meaning |
| --- | --- |
| `trend` | Direction, absolute change, percent change, and CAGR over the selected window. |
| `latest_delta` | Latest observation compared with the prior observation. |
| `watermark_high_distance` | Distance from the series high watermark and when that high occurred. |
| `watermark_low_distance` | Distance from the series low watermark and when that low occurred. |
| `longest_run` | Longest consecutive up, down, or flat run in the window. |
| `peer_rank` | Rank among the region's peers — siblings of the same region type under the same parent that have data for the indicator (e.g. Tennessee counties, US metros, US census divisions). Emitted only when at least 3 peers have data; ties share a rank (competition ranking). |
Every fact includes `kind`, typed `params`, `humanTemplate`, `salience`, and `text`. `text` is a server-rendered, shareable plain-spoken sentence (see [Shareable prose](#shareable-prose)); it is **non-null for every active indicator** — display-metadata coverage is 100% and a daily monitor keeps it there. The field is typed nullable for defense-in-depth (see [Shareable prose](#shareable-prose)). Salience is a deterministic score from `0` to `1` based on magnitude, recency, and extremeness.
## Response
```json
{
"facts": [
{
"kind": "watermark_high_distance",
"params": {
"latestDate": "2024-01-01",
"latestValue": 160,
"watermarkDate": "2024-01-01",
"watermarkValue": 160,
"distance": 0,
"percentDistance": 0
},
"humanTemplate": "Latest value is {distance} from the series high of {watermarkValue} on {watermarkDate}.",
"salience": 1,
"text": "The unemployment rate in the United States hit an all-time high in 2024."
}
],
"signals": [
{
"id": "sig_1",
"indicator": "UNRATE",
"region": "US",
"kind": "acceleration",
"strength": "strong",
"direction": "up",
"salience": 0.9,
"summary": "Worsening faster – Unemployment Rate (United States)",
"text": "The unemployment rate in the United States is mounting – it's been rising faster than its recent trend since April 2026.",
"observed_at": "2026-07-10T00:00:00Z",
"evidence": {
"…": "…"
},
"vocabulary": {
"glyph": "trending-up",
"label": "Worsening faster",
"tone": "negative"
}
}
],
"meta": {
"indicator": "UNRATE",
"region": "US",
"unit": "percent",
"window": "5y",
"observation_count": 5
}
}
```
## Signals
When an active signal is available for the resolved indicator and region, the `signals` array includes it alongside the computed facts. Signal `strength` is `weak`, `neutral`, or `strong`; `direction` is `up` or `down` when applicable; `vocabulary` provides the shared `glyph`, polarity-aware `label`, and `tone` (`positive`, `negative`, or `neutral`).
## Shareable prose
Every fact and signal carries a `text` field: a server-rendered, plain-spoken sentence that reads cleanly on its own — a chart caption, a chat reply, or a social post — with no knowledge of the underlying schema required.
- **Deterministic, not LLM-generated.** `text` is rendered at request time from the same computed `params` by a pure function; identical inputs always produce identical copy.
- **Polarity-aware language.** Verbs come from `indicators.extra.display.polarity`: `higher-is-better`, `lower-is-better`, or `neutral`. Missing polarity uses neutral wording.
- **Guaranteed for active indicators.** Every active indicator carries a curated display noun and unit, so `text` is non-null across the catalog; a daily coverage monitor alerts on any regression. The field stays typed nullable as a defensive contract — if you want a belt-and-suspenders fallback, `humanTemplate` + `params` always reconstruct a value — but in practice `text` is always present.
- **Formatting.** Large values are spelled out (`$821 million`, `$1.1 billion`); values below a million are written in full with separators (`$457,172`). Prose uses en-dashes, never em-dashes.
For example, a `trend` fact renders as *"Between 2007 and 2021, Williamson County's revenue grew 119% – from \$374 million to \$821 million, about 5.8% a year."*, and an `acceleration` signal renders as *"The unemployment rate in the United States is mounting – it's been rising faster than its recent trend since April 2026."*
## Access
Insights use the same `access_tier` rule as observations. If the resolved indicator is `premium`, the request needs an unrestricted key, a key granting `creative-foresight`, or x402.
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | Required or optional query parameters are invalid. |
| `401` | `INVALID_API_KEY` | The request did not include a valid API key and was not a settled x402 request. |
| `403` | `CATEGORY_NOT_ALLOWED` | The key does not include premium access for the requested indicator. |
| `404` | `RESOURCE_NOT_FOUND` | The requested indicator or region was not found. |
| `429` | `RATE_LIMIT_EXCEEDED` | The API key exceeded its one-minute request limit. |
| `500` | `INTERNAL_ERROR` | Insight data could not be loaded. |
---
# Ask
`GET /v1/ask` takes a plain-language question and returns a grounded answer
built exclusively from Foresight data. It is the same data the deterministic
endpoints serve — observations, computed insights, active signals — selected
and narrated for your question.
```bash
curl "https://api.creativeforesight.io/v1/ask?q=How+has+unemployment+in+Williamson+County,+Texas+trended+over+the+past+two+years" \
-H "Authorization: Bearer cf_live_REPLACE_ME"
```
## The grounding contract
The answer engine is an orchestrator, not an oracle. It selects from the same
typed tools the MCP server exposes, and it may only phrase facts those tools
returned:
- Every number, direction, and date in the prose appears verbatim in a cited
tool payload. The engine verifies this mechanically on every response; the
`grounded` flag tells you whether verification passed.
- The `citations` array is assembled from the tool calls the engine actually
executed — it is not self-reported by the model.
- If the tools return nothing relevant, the answer says so. The engine never
improvises and never answers from general model knowledge.
- One statistic, one source. When several datasets could answer the same
question — population, say — the answer uses the editorially chosen series
for that kind of place and cites it, rather than juxtaposing competing
figures.
- Statistics — trends, CAGR, deltas, watermarks — come from the deterministic
facts layer. The model narrates them; it does not compute them.
## You don't pay when we don't have the data
Before any payment is requested, a deterministic coverage check screens the
question against the indicator catalog. No coverage means a free
`no_coverage` response, and on the x402 path no `402` challenge is ever
issued. Covered questions are $0.25 per paid x402 ask, or any Foresight API
key.
## Briefing depth
Add `depth=briefing` for a full briefing instead of a focused answer: the
engine runs a deeper orchestration pass (more tool calls, a longer answer)
on a premium model, priced at $1.50 per paid x402 request. Everything else
is unchanged — the grounding contract, the citation trace, and the rule
that uncovered questions are free at any depth.
## Curated briefings
Foresight also publishes curated briefings on a schedule. The catalog at
[`GET /v1/briefings/public`](/api/ask#curated-briefings) is free; the latest
run of any briefing — full grounded prose plus its citation trace — is
$0.10 per paid x402 read (or included with a premium API key) at
`GET /v1/briefings/public/{slug}`. Saved private briefings on your own
schedule are available to premium API keys at `/v1/briefings`.
## Request
| Param | Required | Meaning |
|---|---|---|
| `q` | yes | The question, 1–500 characters. |
| `region` | no | FIPS region hint: 5-digit county, 2-digit state, `US`. |
| `window` | no | `all` (default), or `Nd`/`Nm`/`Ny` such as `90d`, `12m`, `5y`. |
| `depth` | no | `standard` (default) or `briefing` — deeper pass, premium model, higher price. |
## Response
```json
{
"answer": "Over the past two years, Williamson County's unemployment rate has held a narrow 3.2%–3.9% band ...",
"citations": [
{
"tool": "search_indicators",
"args": { "query": "unemployment" },
"summary": "{\"data\":[{\"code\":\"LAUS_unemploymentRate\" ..."
},
{
"tool": "get_insights",
"args": { "indicator": "LAUS_unemploymentRate", "region": "48491" },
"summary": "{\"facts\":[..."
}
],
"resolved": {
"indicators": ["LAUS_unemploymentRate"],
"regions": ["48491"],
"window": "all"
},
"grounded": true,
"truncated": false,
"status": "ok",
"meta": { "model": "...", "rounds": 4, "toolCalls": 5 }
}
```
Other outcomes:
- **`no_coverage`** — the catalog has nothing matching the question. Free.
- **`no_data`** — the catalog matched, but the tools returned nothing for
your specific scope (for example, a region we don't hold that series for).
- **`503`** — the ask engine is unavailable; the deterministic endpoints are
unaffected.
`truncated: true` means a per-request ceiling ended the work early — the
answer is grounded but partial, and usually means the question's scope was
too broad for one ask. Narrow the region, window, or subject and ask again.
## Verifying an answer
Every citation's `args` are replayable against the deterministic endpoints:
call [`/v1/observations`](/api/observations), [`/v1/insights`](/api/insights),
or [`/v1/signals`](/api/signals) with the same arguments and check the numbers
yourself. That is the point of the trace — an ask answer is an index into data
you can independently fetch.
## MCP
The same capability is exposed as the `ask` tool on the hosted MCP server
(`POST /api/mcp`). Agents that don't know the indicator vocabulary should
prefer `ask`; agents that do should use the typed tools directly.
---
# Signals
Signals are Creative Foresight's premium alerts: when a series starts moving in a way worth knowing about — accelerating, breaking from its trend, or hitting unusual territory — an interval opens, and it closes when the move ends. Use `GET /v1/signals/active` for currently open intervals and `GET /v1/signals` for interval history.
## Active signals request
```bash
curl "https://api.creativeforesight.io/v1/signals/active?indicator=UNRATE®ion=US&strength=strong" \
-H "Authorization: Bearer $CF_API_KEY"
```
## History request
```bash
curl "https://api.creativeforesight.io/v1/signals?indicator=UNRATE®ion=US&from=2026-01-01&limit=25" \
-H "Authorization: Bearer $CF_API_KEY"
```
## Query parameters
| Parameter | Type | Endpoint | Description |
| --- | --- | --- | --- |
| `indicator` | string | Both | Indicator code filter. May be repeated or comma-separated, such as `indicator=UNRATE,PAYEMS&indicator=cf:labor_composite`. |
| `region` | string | Both | Region code. Counties use 5-digit FIPS codes. |
| `type` | `acceleration`, `extreme`, or `trend_break` | Both | Signal type filter. |
| `strength` | `weak`, `neutral`, or `strong` | Both | Minimum signal strength. Defaults to `neutral`, so weak signals are hidden unless `strength=weak` is requested. |
| `limit` | integer | Both | Page size. Defaults to 100 for HTTP routes. Active signals are capped to the current open intervals; history uses cursor pagination. |
| `active` | boolean | `GET /v1/signals` | `true` returns open intervals, `false` returns closed intervals, and omitted returns both. |
| `from` | date | `GET /v1/signals` | Inclusive lower bound for intervals overlapping this date. |
| `to` | date | `GET /v1/signals` | Inclusive upper bound for intervals starting on or before this date. |
| `cursor` | string | `GET /v1/signals` | Cursor from `meta.next_cursor` for the next history page. Ignored by the active endpoint. |
## Signal object
| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Stable signal interval identifier. |
| `indicator` | object | Indicator code, name, category, unit, frequency, and source metadata. |
| `region` | object | Region code and name. |
| `type` | string | What kind of move: `acceleration`, `extreme`, or `trend_break`. |
| `strength` | string | How pronounced the move is: `weak`, `neutral`, or `strong`. |
| `direction` | string or null | Which way the series is moving, such as `up` or `down`, when applicable. |
| `valid_from` | date-time | Start of the signal interval. |
| `valid_to` | date-time or null | End of the interval. Active intervals have `null`. |
| `detected_at` | date-time | First detection timestamp. |
| `last_seen_at` | date-time | Latest materialization timestamp for the interval. |
| `evidence` | object | Opaque supporting detail. Its shape is not part of the API contract and may change — use `text` and `vocabulary` for display. |
| `vocabulary` | object | Shared chip vocabulary: `glyph`, polarity-aware `label`, and `tone`. `tone` is `positive`, `negative`, or `neutral`. |
| `text` | string or null | Server-rendered, shareable plain-spoken sentence describing the signal. Non-null for every active indicator (display-metadata coverage is 100% and monitored); typed nullable for defense-in-depth. |
## Response
```json
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000001",
"indicator": {
"code": "UNRATE",
"name": "Unemployment Rate",
"category": "labor",
"unit": "percent",
"frequency": "monthly",
"source": {
"slug": "fred",
"name": "FRED"
}
},
"region": {
"code": "US",
"name": "United States"
},
"type": "acceleration",
"strength": "strong",
"direction": "up",
"valid_from": "2026-06-01T00:00:00Z",
"valid_to": null,
"detected_at": "2026-06-15T00:00:00Z",
"last_seen_at": "2026-07-10T00:00:00Z",
"evidence": {
"…": "…"
},
"vocabulary": {
"glyph": "trending-up",
"label": "Worsening faster",
"tone": "negative"
},
"text": "The unemployment rate in the United States is mounting – it's been rising faster than its recent trend since June 2026."
}
],
"meta": {
"count": 1,
"limit": 25,
"filters": {
"indicator": ["UNRATE"],
"region": "US",
"strength": "neutral",
"active": true
},
"next_cursor": "eyJsYXN0U2VlbkF0IjoiMjAyNi0wNy0xMFQwMDowMDowMFoiLCJpZCI6InNpZ18xIn0"
}
}
```
`meta.next_cursor` appears only on `GET /v1/signals` when another history page is available. Pass it back as `cursor` with the same filters to continue paging.
## Vocabulary and intervals
The signal vocabulary is server-rendered for UI and agent surfaces. `glyph` is direction-aware, including `trending-down` for down-accelerations. `label` is polarity-aware, so the same kind of move can render as `Improving faster` or `Slowing` depending on whether higher or lower values are better. `tone` is derived from direction and indicator polarity — the example above shows a rising unemployment rate (a lower-is-better indicator), so the up-move renders as `Worsening faster` with `negative` tone.
A direction flip closes the open interval and starts a new current run. When `text` says `since `, that date is the start of the current interval, not a stale first-ever detection date.
## Access
Signals require a premium API key or x402 payment. Restricted API keys must include premium access through `creative-foresight`.
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | Required or optional query parameters are invalid. |
| `401` | `INVALID_API_KEY` | The request did not include a valid API key. |
| `403` | `CATEGORY_NOT_ALLOWED` | The key does not include premium access for signals. |
| `429` | `RATE_LIMIT_EXCEEDED` | The API key exceeded its one-minute request limit. |
| `500` | `INTERNAL_ERROR` | Signal data could not be loaded. |
---
# OpenAPI Spec
The entire Foresight API is described in one machine-readable file: an [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) document listing every endpoint, parameter, response shape, and error code — the same contract this reference is written from.
You don't read it; you feed it to things:
- **Generate a typed client** in your language with any OpenAPI generator.
- **Import it** into Postman, Insomnia, or Bruno and every request is pre-built.
- **Hand it to your agent** — most coding agents can turn a spec URL into working calls on the first try.
```bash
curl https://api.creativeforesight.io/openapi.json
```
It's served from both hosts — [`api.creativeforesight.io/openapi.json`](https://api.creativeforesight.io/openapi.json) and [`docs.creativeforesight.io/openapi.json`](/openapi.json) — and the two are kept identical by a build-time check.
---
# Observations
`GET /v1/observations` returns time-series rows for an indicator and optional region. This endpoint requires an API key or x402 payment.
## Request
```bash
curl "https://api.creativeforesight.io/v1/observations?indicator=UNRATE®ion=US&limit=12" \
-H "Authorization: Bearer $CF_API_KEY"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `indicator` | string | Yes | Indicator code, such as `UNRATE` or `cf:labor_composite`. |
| `source` | string | No | Source slug for duplicate provider codes. |
| `region` | string | No | Region code. Uses the indicator default region when omitted. |
| `start_date` | date | No | Inclusive lower date bound. |
| `end_date` | date | No | Inclusive upper date bound. |
| `include_revisions` | boolean | No | Include revision history instead of only latest revisions. Defaults to `false`. Cannot combine with `transform`. |
| `transform` | string | No | Query-time transform: `yoy`, `mom`, `qoq`, `index:YYYY-MM-DD`, or `log`. See [Transforms](#transforms). |
| `frequency` | string | No | Downsample to `monthly`, `quarterly`, or `annual` calendar buckets. See [Resampling](#resampling). |
| `resample_method` | string | No | Bucket aggregation: `mean` (default), `last`, or `sum`. Requires `frequency`. |
| `limit` | integer | No | Page size, 0 to 1000. Defaults to 100. |
| `offset` | integer | No | Page offset. Defaults to 0. |
| `order` | `asc` or `desc` | No | Sort order. Defaults to `desc`. |
## Observation object
| Field | Type | Description |
| --- | --- | --- |
| `date` | date | Observation date. |
| `value` | number or null | Numeric value. |
| `indicator` | string | Indicator code. |
| `region` | string | Region code. |
| `unit` | string or null | Unit label. |
| `source` | string or null | Source slug. |
| `source_updated_at` | date-time or null | Provider update timestamp. |
| `is_preliminary` | boolean | Whether the row is preliminary. |
| `revision` | integer | Revision number. |
## Response
```json
{
"data": [
{
"date": "2026-05-01",
"value": 4.2,
"indicator": "UNRATE",
"region": "US",
"unit": "percent",
"source": "fred",
"source_updated_at": "2026-06-06T12:00:00.000Z",
"is_preliminary": false,
"revision": 0
}
],
"meta": {
"indicator": "UNRATE",
"region": "US",
"unit": "percent",
"total": 1,
"limit": 12,
"offset": 0
}
}
```
## Transforms
Add `transform=` to get derived values without post-processing:
| Transform | Result | Unit |
| --- | --- | --- |
| `yoy` | Percent change vs the same period one year earlier. | `percent` |
| `mom` | Percent change vs the previous period. | `percent` |
| `qoq` | Percent change vs the previous quarter (quarterly series only). | `percent` |
| `index:YYYY-MM-DD` | Values rescaled so the base date equals 100. | `index (BASE=100)` |
| `log` | Natural log of each value. | `log(unit)` |
```bash
curl "https://api.creativeforesight.io/v1/observations?indicator=UNRATE®ion=US&transform=yoy" \
-H "Authorization: Bearer cf_live_REPLACE_ME"
```
Percent changes compare calendar periods, not row positions: a row whose comparator period is missing from the series — or falls outside the requested date window or page — returns `null` rather than a misleading number. The response `meta.unit` reflects the transform and `meta.transform` echoes what was applied. `yoy` is unavailable on daily series at their native frequency — add `frequency=monthly` to get monthly year-over-year from daily data. `transform` cannot combine with `include_revisions`.
## Resampling
Add `frequency=` to downsample a series to calendar buckets before any transform runs:
```bash
curl "https://api.creativeforesight.io/v1/observations?indicator=DGS2&frequency=monthly&transform=yoy" \
-H "Authorization: Bearer cf_live_REPLACE_ME"
```
Buckets are labeled by their calendar start date (`2026-07-01` for July, Q3, or 2026) and aggregated with `resample_method` — `mean` (default), `last`, or `sum`. Resampled rows are synthetic aggregates carrying only `period_start`, `period_end`, `period_type`, and `value`; per-row fields like `revision` don't survive aggregation. Requesting a series' own native frequency returns it unchanged.
Two honesty rules protect you from partial buckets: a trailing bucket the source hasn't finished (say, July from daily data that ends July 14th) is dropped rather than served as if complete, and when a page is truncated by `limit`/`offset`, the buckets at the truncated edges are dropped because they may be missing source rows. `meta.resample` reports the method, the source and target frequencies, and both kinds of drops. Prefer `start_date`/`end_date` windows over pagination when resampling — a full window has no truncated edges.
## Paying per request (x402 or Lightning)
Unauthenticated calls return HTTP 402 with two ways to pay:
- **x402 (USDC on Base):** the JSON body's `accepts` describes the payment; retry with `X-PAYMENT`.
- **Lightning (L402):** the `WWW-Authenticate` header carries a macaroon and a bolt11 invoice. Pay the invoice from any Lightning wallet, then retry the same request with `Authorization: L402 :` (your wallet shows the preimage after paying). The macaroon covers the requested indicator for 10 minutes.
Either proof settles the request.
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | Required or optional query parameters are invalid. |
| `401` | `INVALID_API_KEY` | The request did not include a valid API key and was not a settled x402 request. |
| `404` | `RESOURCE_NOT_FOUND` | The requested indicator or region was not found. |
| `429` | `RATE_LIMIT_EXCEEDED` | The API key exceeded its one-minute request limit. |
| `500` | `INTERNAL_SERVER_ERROR` | Observation data could not be loaded. |
---
# Latest Observation
`GET /v1/observations/latest` returns the latest row for an indicator and optional region. This endpoint requires an API key or x402 payment.
## Request
```bash
curl "https://api.creativeforesight.io/v1/observations/latest?indicator=UNRATE®ion=US" \
-H "Authorization: Bearer $CF_API_KEY"
```
## Query parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `indicator` | string | Yes | Indicator code, such as `UNRATE` or `cf:labor_composite`. |
| `source` | string | No | Source slug for duplicate provider codes. |
| `region` | string | No | Region code. Uses the indicator default region when omitted. |
## Latest observation object
| Field | Type | Description |
| --- | --- | --- |
| `indicator` | string | Indicator code. |
| `region` | string | Region code. |
| `period_start` | date | Start of the observation period. |
| `period_end` | date or null | End of the observation period when available. |
| `period_type` | string or null | Period type, such as `month`. |
| `value` | number | Latest value. |
| `revision` | integer | Revision number. |
| `is_preliminary` | boolean | Whether the latest value is preliminary. |
| `source_updated_at` | date-time or null | Provider update timestamp. |
| `unit` | string or null | Unit label. |
## Response
```json
{
"data": {
"indicator": "UNRATE",
"region": "US",
"period_start": "2026-05-01",
"period_end": null,
"period_type": "month",
"value": 4.2,
"revision": 0,
"is_preliminary": false,
"source_updated_at": "2026-06-06T12:00:00.000Z",
"unit": "percent"
},
"meta": {
"indicator": "UNRATE",
"region": "US",
"unit": "percent",
"total": 1,
"limit": 1,
"offset": 0
}
}
```
If no row exists for a valid indicator and region, `data` can be `null` and `meta.total` is `0`.
## Errors
| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_PARAMETER` | Required or optional query parameters are invalid. |
| `401` | `INVALID_API_KEY` | The request did not include a valid API key and was not a settled x402 request. |
| `404` | `RESOURCE_NOT_FOUND` | The requested indicator or region was not found. |
| `429` | `RATE_LIMIT_EXCEEDED` | The API key exceeded its one-minute request limit. |
| `500` | `INTERNAL_SERVER_ERROR` | Latest observation data could not be loaded. |