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/mcpand pass your key asAuthorization: Bearer cf_live_.... Catalog tools work without a key. See Connect over 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 | Free, no key needed |
get_latest_observation | GET /v1/observations/latest | Free-tier indicators with a free key; see Pricing |
Get a free key with one request (see Signup), then export it:
curl -X POST "https://api.creativeforesight.io/v1/signup" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","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.
import os
import urllib.error
import urllib.parse
import urllib.request
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:
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:
import anthropic
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:
import json
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:
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_observationfor/v1/observationsto give the model a full series, or/v1/panelto compare up to 10 indicators on one date grid. /v1/askanswers 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).