A procurement team can track input costs from published monthly series. Narwhal returns copper, aluminium, iron ore and energy prices by month with the unit and currency, and a live copper quote, so the change since a base month is a short calculation.
Pull the monthly input prices
curl -s "https://api.narwhalapi.com/v1/commodities/physical/series/worldbank-cmo-aluminum/observations?start=2026-03-01&end=2026-05-01" \
-H "Authorization: Bearer $NARWHAL_API_KEY"{
"series_id": "worldbank-cmo-aluminum",
"currency": "USD",
"unit": "metric_ton",
"unit_key": "tonne",
"unit_quantity": "1",
"attribution": "The World Bank: World Bank Commodity Price Data (The Pink Sheet), CC BY 4.0",
"observations": [
{
"period_start": "2026-03-01",
"period_end": "2026-03-31",
"price": "3373",
"revision": 1
},
{
"period_start": "2026-04-01",
"period_end": "2026-04-30",
"price": "3600",
"revision": 1
}
],
"next_cursor": null
}Each row is one month, with period_start, period_end, price as a decimal string and revision. The unit and currency are on the response, USD per metric_ton here. Iron ore is quoted in USD per dry_metric_ton_unit, so keep the unit with each series and never add prices across series.
The series list has no steel price. Iron ore, a steel input, is there as worldbank-cmo-iron-ore-cfr-spot. The energy inputs include worldbank-cmo-natural-gas-us in mmbtu and worldbank-cmo-coal-australian in metric_ton. Search GET /v1/commodities/physical/series with the category filter, for example metals or energy, to see what else a buyer can track.
Calculate the change since a base month
A procurement report usually fixes a base month, the month of the last budget, and shows each input as a percentage against it. The change is the latest price divided by the base price, minus one.
- Choose the series Copper, aluminium and iron ore. The script requests March to September 2026 for each, with
endset to 2026-10-01 becauseendis exclusive. - Key by month Each price is stored under the first seven characters of
period_start, such as 2026-09, and the base month is 2026-03. - Add the live copper quote The script reads the spot quote, which is in USD per pound, and multiplies by 2204.62 pounds to compare it with the monthly series per tonne.
import os import requests API = "https://api.narwhalapi.com" HEAD = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"} BASE_MONTH = "2026-03" LB_PER_TONNE = 2204.62 series = ["worldbank-cmo-copper", "worldbank-cmo-aluminum", "worldbank-cmo-iron-ore-cfr-spot"] prices = {} for sid in series: r = requests.get(f"{API}/v1/commodities/physical/series/{sid}/observations", params={"start": "2026-03-01", "end": "2026-10-01"}, headers=HEAD, timeout=15) r.raise_for_status() body = r.json() prices[sid] = {o["period_start"][:7]: float(o["price"]) for o in body["observations"]} base, last_month = prices[sid][BASE_MONTH], max(prices[sid]) last = prices[sid][last_month] print(f"{sid}: {body['currency']} per {body['unit']}, {BASE_MONTH} {base:g}, " f"{last_month} {last:g}, change {100 * (last / base - 1):+.1f}%") spot = requests.get(f"{API}/v1/commodities/spot/XCU/USD", headers=HEAD, timeout=15).json() per_tonne = float(spot["price"]) * LB_PER_TONNE copper_base = prices["worldbank-cmo-copper"][BASE_MONTH] print(f"copper spot {spot['price']} USD per {spot['unit']} at {spot['timestamp']}: " f"{per_tonne:.0f} per tonne, {100 * (per_tonne / copper_base - 1):+.1f}% against {BASE_MONTH}")
| Input | Unit | 2026-03 | 2026-09 | Change |
|---|---|---|---|---|
| Copper | USD per metric_ton | 12529 | 14474 | +15.5% |
| Aluminium | USD per metric_ton | 3373 | 3283 | -2.7% |
| Iron ore | USD per dry_metric_ton_unit | 104.5 | 97.7 | -6.5% |
Copper is the only input that rose by double digits in that period, aluminium fell 2.7 percent and iron ore fell 6.5 percent. A report built this way flags the copper line at once, and the sign of each change tells the buyer which inputs got cheaper.
Add the live copper spot
Monthly series lag the month by definition. For a quote taken now, GET /v1/commodities/spot/XCU/USD returns the live copper price.
curl -s "https://api.narwhalapi.com/v1/commodities/spot/XCU/USD" \
-H "Authorization: Bearer $NARWHAL_API_KEY"{
"symbol": "XCU-USD",
"commodity_key": "copper",
"price": "6.4573",
"currency": "USD",
"unit": "pound",
"unit_key": "pound",
"timestamp": "2026-11-03T02:29:00Z",
"stale": false
}The price is 6.4573 USD per pound, with a timestamp and a stale flag that is false. Multiplied by 2204.62 pounds per tonne it is 14236 USD per tonne, which is +13.6% against the March monthly price of 12529. The copper conversion how-to covers the unit arithmetic.
For daily history, GET /v1/commodities/spot/XCU/USD/bars takes start and end with a UTC offset, such as the trailing Z, and an interval. A request may span at most 3 days of 1m bars, 14 days of 5m bars and 60 days of 15m bars, and the wider intervals have no cap, so a month of daily bars is one request. The bars below use the 1d interval.
curl -s "https://api.narwhalapi.com/v1/commodities/spot/XCU/USD/bars?start=2026-10-26T00:00:00Z&end=2026-10-29T00:00:00Z&interval=1d" \
-H "Authorization: Bearer $NARWHAL_API_KEY"{
"symbol": "XCU-USD",
"commodity_key": "copper",
"currency": "USD",
"unit": "pound",
"unit_key": "pound",
"bars": [
{
"timestamp": "2026-10-26T00:00:00Z",
"open": "6.4662",
"high": "6.5254",
"low": "6.4370",
"close": "6.5160"
},
{
"timestamp": "2026-10-27T00:00:00Z",
"open": "6.5238",
"high": "6.6422",
"low": "6.5090",
"close": "6.6374"
},
{
"timestamp": "2026-10-28T00:00:00Z",
"open": "6.6400",
"high": "6.7032",
"low": "6.5995",
"close": "6.6572"
}
],
"next_cursor": null
}Lay out the report row
One row per input keeps the report readable and traceable. Each row needs the same few fields, all of which come from the responses above.
- The
series_idand theunit, so a reader knows what the price measures. - The base month and the latest month, each as a
period_startmonth. - Both prices and the percentage change, with the sign kept.
- The
revisionof each price, since the monthly rows carry one. - The
attributiontext from the series, which names the publisher and licence. - For the live copper line, the
timestampof the quote and itsstalevalue.
Why does the spot not match the monthly series?
They are different measures. The monthly series carries one value per month with a revision, and the spot is a quote at a timestamp, in pounds. Compare them only after converting the unit, and say in the report which one each figure is. A spot quote also moves during the day, so record its timestamp with the figure. To quote an input in another currency, see get a commodity price in your currency.
What happens if I leave the UTC offset off a bars request?
The route answers 400 with the code invalid_query and the detail that the value must include a UTC offset. Send 2026-10-26T00:00:00Z or an explicit offset such as +07:00.
Can I track steel directly?
Not with a steel price. The physical series list has no steel series, so use an input series such as iron ore and say so in the report. The API does not carry a steel price, so the report should name iron ore as its proxy.
Published .