To make a finance chatbot cite its sources, use the freshness fields in each response: period and released_on for economics, timestamp and stale for prices, and as_of for history. Refuse to quote a price when stale is true.
Read the source fields in each response
A chatbot can quote a figure only as well as it can say where it came from. The fields for this sit in the responses themselves. An economics figure has a period (the month the number describes) and released_on (the date it was published). A price has a timestamp (the moment the price belongs to) and stale. This request asks for US inflation.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/economics/USA/inflation"{
"country": "USA",
"period": "2026-09",
"year_over_year": "3.7",
"month_over_month": "0.6",
"year_to_date": "4.0",
"released_on": "2026-10-14"
}The 3.7 is the change over twelve months to period 2026-09, and it was published on 2026-10-14. An economics response has no stale field, because a monthly figure is not out of date merely because days have passed. The chatbot should name the period and the release date and no more.
Prices carry a timestamp and a stale flag
A spot price and an exchange rate describe a moment, so the answer must say when. The gold price below is in US dollars per troy ounce.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/commodities/spot/XAU/USD"{
"symbol": "XAU-USD",
"commodity_key": "gold",
"price": "3946.9530",
"currency": "USD",
"unit": "troy_ounce",
"unit_key": "troy_ounce",
"timestamp": "2026-11-03T02:28:00Z",
"stale": false
}curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/fx/rates/USD?currencies=EUR"{
"base": "USD",
"market_session": "open",
"rates": [
{"currency": "EUR", "rate": "0.89433", "timestamp": "2026-11-03T02:28:56.515185Z", "stale": false}
]
}On the gold response, stale sits at the top level. On the exchange rate response it sits inside each entry of rates, next to that entry's timestamp, and market_session reports the state of the market. When stale is true, the value is the last known one and timestamp says when it was set. The freshness page defines the flag: for spot and FX it means the value is out of date while its market is open. A closed market never makes a value stale.
Use as_of for history
History responses add as_of, the time the source last updated the newest observation on the page. Use it when the chatbot quotes a series rather than the latest figure.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/economics/USA/inflation/observations?start=2026-08"{
"country": "USA",
"as_of": "2026-10-14T00:00:00Z",
"observations": [
{"period": "2026-08", "year_over_year": "3.4", "month_over_month": "0.3", "year_to_date": "3.4"},
{"period": "2026-09", "year_over_year": "3.7", "month_over_month": "0.6", "year_to_date": "4.0"}
],
"next_cursor": null
}Each observation has its own period. The page as a whole has one as_of, so a chatbot that quotes the 2026-08 figure can still say the source was last updated at 2026-10-14T00:00:00Z.
Format an answer with its sources and refuse when stale
The function below turns a response into a sentence that carries its own source fields. It returns a refusal when stale is true, so the model never receives a price it should not state. The last line passes a constructed response with stale set to true to show the refusal; the API did not return it.
import json, os, urllib.request
API = "https://api.narwhalapi.com"
def get(path):
req = urllib.request.Request(API + path, headers={"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"})
return json.load(urllib.request.urlopen(req))
def answer(kind, data):
"""Return the sentence the chatbot may say, with its source fields, or a refusal."""
if kind == "inflation":
return (f"US inflation was {data['year_over_year']}% over the year to {data['period']} "
f"(released {data['released_on']}).")
item = data["rates"][0] if kind == "fx" else data
if item["stale"]:
return "I can't quote that price right now: its data is marked out of date."
when = item["timestamp"]
if kind == "gold":
return f"Gold is {item['price']} {item['currency']} per troy ounce (as of {when})."
return f"1 {data['base']} buys {item['rate']} {item['currency']} (as of {when})."
print(answer("inflation", get("/v1/economics/USA/inflation")))
print(answer("gold", get("/v1/commodities/spot/XAU/USD")))
print(answer("fx", get("/v1/fx/rates/USD?currencies=EUR")))
print(answer("gold", {"stale": True}))US inflation was 3.7% over the year to 2026-09 (released 2026-10-14).
Gold is 3946.9530 USD per troy ounce (as of 2026-11-03T02:28:00Z).
1 USD buys 0.89433 EUR (as of 2026-11-03T02:28:56.515185Z).
I can't quote that price right now: its data is marked out of date.Pass the returned sentence to the model as the only text it may use for that figure, or ask the model to quote the source fields verbatim. Do not let it round a timestamp to "today", since the date and time are the evidence.
Which fields to quote for which figure
| Figure | Fields to quote | Refuse when |
|---|---|---|
| Inflation, CPI and other monthly figures | period and released_on | The response is an error or has no value |
| A history series | period for each value and as_of | The response is an error or has no observations |
| Gold and other spot prices | price, currency, unit and timestamp | stale is true |
| An exchange rate | rate, the pair and its timestamp | stale is true on that rate |
Headline economics figures can be provisional first estimates that the source later replaces, and the response does not mark them. Tell users that a figure is the latest published value for its period, as the freshness page describes. If the chatbot reads data through MCP, the same fields appear in each tool's structuredContent; see the agent access guide and the MCP documentation.
What should the chatbot say when stale is true?
It should decline to quote the number and say that the price is temporarily out of date. The API still returns the last known value in that case, but a stale price may differ from the market, so the safest answer for a chatbot that users trust is a refusal and a later retry. Do not substitute a cached figure without saying when it was set.
Does a closed market make a price stale?
No. For spot prices and exchange rates, stale turns true only while the market is open and the value is out of date: the source marks it stale, or the value is more than 10 minutes old, counting only open-market time. When the market is closed, the last price is the current one, and market_session or the timestamp lets the chatbot say so.
Is released_on the date Narwhal received the data?
No. released_on is the official publication date. It is not the time Narwhal retrieved or published the figure, and the responses expose no ingestion time. If a distributor supplied the data, a source object appears and released_on is the distributor's publication date.
Published .