Track input commodity costs month over month

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, aluminium, March and April
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"
Response
{
  "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.

  1. Choose the series Copper, aluminium and iron ore. The script requests March to September 2026 for each, with end set to 2026-10-01 because end is exclusive.
  2. 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.
  3. 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.
    Python, change since a base month
    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}")
InputUnit2026-032026-09Change
CopperUSD per metric_ton1252914474+15.5%
AluminiumUSD per metric_ton33733283-2.7%
Iron oreUSD per dry_metric_ton_unit104.597.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, live copper spot
curl -s "https://api.narwhalapi.com/v1/commodities/spot/XCU/USD" \
  -H "Authorization: Bearer $NARWHAL_API_KEY"
Response
{
  "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, daily copper bars
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"
Response
{
  "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_id and the unit, so a reader knows what the price measures.
  • The base month and the latest month, each as a period_start month.
  • Both prices and the percentage change, with the sign kept.
  • The revision of each price, since the monthly rows carry one.
  • The attribution text from the series, which names the publisher and licence.
  • For the live copper line, the timestamp of the quote and its stale value.

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 .

Next steps