Ground a finance chatbot's answers in figures with their sources and dates

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, US inflation
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/economics/USA/inflation"
Response
{
  "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, the gold spot price
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/commodities/spot/XAU/USD"
Response
{
  "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, the USD to EUR rate
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/fx/rates/USD?currencies=EUR"
Response
{
  "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, inflation history
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/economics/USA/inflation/observations?start=2026-08"
Response
{
  "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.

Python, an answer with its source fields
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}))
Output
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

FigureFields to quoteRefuse when
Inflation, CPI and other monthly figuresperiod and released_onThe response is an error or has no value
A history seriesperiod for each value and as_ofThe response is an error or has no observations
Gold and other spot pricesprice, currency, unit and timestampstale is true
An exchange raterate, the pair and its timestampstale 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 .

Next steps