Price wheat, rice, sugar, coffee and palm oil

A food or agri business can price its inputs from published series. Narwhal returns monthly wheat, rice, sugar, coffee and palm oil prices with unit and currency, and palm oil producer schedules, so the monthly change is a short calculation.

Read the monthly benchmark prices

curl, US hard red winter wheat, July to September
curl -s "https://api.narwhalapi.com/v1/commodities/physical/series/worldbank-cmo-wheat-us-hrw/observations?start=2026-07-01&end=2026-10-01" \
  -H "Authorization: Bearer $NARWHAL_API_KEY"
Response
{
  "series_id": "worldbank-cmo-wheat-us-hrw",
  "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-07-01",
      "period_end": "2026-07-31",
      "price": "310.0",
      "revision": 1
    },
    {
      "period_start": "2026-08-01",
      "period_end": "2026-08-31",
      "price": "330.0",
      "revision": 1
    },
    {
      "period_start": "2026-09-01",
      "period_end": "2026-09-30",
      "price": "347.0",
      "revision": 1
    }
  ],
  "next_cursor": null
}

Each observation is one month. price is a decimal string, period_start and period_end give the month and revision is 1 on these rows. The unit and currency are on the response, USD per metric_ton for wheat. Sugar and coffee are quoted per kilogram, so read the unit on every series before you compare two of them.

The ids follow the pattern worldbank-cmo- plus the commodity: worldbank-cmo-rice-thai-5, worldbank-cmo-sugar-world, worldbank-cmo-coffee-arabica and worldbank-cmo-palm-oil. GET /v1/commodities/physical/series with category=grains, softs or oils-oilseeds lists the others.

Show the monthly change in code

  1. Request three months The script asks for July to September 2026 for five series. The end date is exclusive, so 2026-10-01 stops at September.
  2. Compute each change The change is the month's price divided by the previous month's price, minus one, printed as a percent beside the unit.
  3. Read the output Sugar rose most in August and September. Coffee fell in September while wheat, rice and palm oil rose.
    Python, monthly change for five agricultural series
    import os
    import requests
    
    API = "https://api.narwhalapi.com"
    HEAD = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
    
    series = [
        "worldbank-cmo-wheat-us-hrw",
        "worldbank-cmo-rice-thai-5",
        "worldbank-cmo-sugar-world",
        "worldbank-cmo-coffee-arabica",
        "worldbank-cmo-palm-oil",
    ]
    for sid in series:
        r = requests.get(f"{API}/v1/commodities/physical/series/{sid}/observations",
                         params={"start": "2026-07-01", "end": "2026-10-01"}, headers=HEAD, timeout=15)
        r.raise_for_status()
        body = r.json()
        obs = body["observations"]
        line = [f"{sid} ({body['currency']} per {body['unit']})", f"{obs[0]['period_start'][:7]} {obs[0]['price']}"]
        for prev, cur in zip(obs, obs[1:]):
            change = 100 * (float(cur["price"]) / float(prev["price"]) - 1)
            line.append(f"{cur['period_start'][:7]} {cur['price']} ({change:+.1f}%)")
        print(" | ".join(line))
SeriesUnitJulyAugustSeptember
Wheat, US HRWUSD per metric_ton310.0330.0 (+6.5%)347.0 (+5.2%)
Rice, Thai 5%USD per metric_ton467.0471.0 (+0.9%)482.0 (+2.3%)
Sugar, worldUSD per kilogram0.340.38 (+11.8%)0.41 (+7.9%)
Coffee, arabicaUSD per kilogram7.917.97 (+0.8%)7.26 (-8.9%)
Palm oilUSD per metric_ton11331132 (-0.1%)1180 (+4.2%)

Sugar is quoted to two decimals, so a one cent move on 0.34 is a change of nearly three percent. Compute changes from the published price strings and round only the final percent. For a buyer with a fixed monthly budget, the September column is the one to set against the budget, with July kept as the base.

Read palm oil producer prices

GET /v1/commodities/palmoil/physical lists palm oil price series that are not the world benchmark. Filters combine, and product is ffb for fresh fruit bunches or cpo for crude palm oil. A filter for fresh fruit bunches under purchase agreements returned one series.

curl, palm oil producer series
curl -s "https://api.narwhalapi.com/v1/commodities/palmoil/physical?product=ffb&producer_arrangement=purchase_agreement" \
  -H "Authorization: Bearer $NARWHAL_API_KEY"
Response
{
  "series": [
    {
      "frequency": "weekly",
      "coverage_start": "2026-09-02",
      "coverage_end": "2026-10-27",
      "series_id": "id-ri-oil-palm-ffb-smallholder-purchase-agreement",
      "country": "IDN",
      "area": "ID-RI",
      "currency": "IDR",
      "product": "ffb",
      "observation_type": "age_schedule",
      "price_type": "official_purchase_schedule",
      "period_type": "effective",
      "unit": "kilogram",
      "unit_key": "kilogram",
      "producer_arrangement": "purchase_agreement"
    }
  ],
  "next_cursor": null
}

The series is Indonesian, quoted in IDR per kilogram. Its observation_type is age_schedule and its price_type is official_purchase_schedule, so a week holds a list of prices by tree age instead of one number. Read the observations with the /observations route.

curl, weekly purchase schedule
curl -s "https://api.narwhalapi.com/v1/commodities/palmoil/physical/id-ri-oil-palm-ffb-smallholder-purchase-agreement/observations?start=2026-10-14&end=2026-11-03" \
  -H "Authorization: Bearer $NARWHAL_API_KEY"
Response, trimmed to three age bands per week
{
  "series_id": "id-ri-oil-palm-ffb-smallholder-purchase-agreement",
  "currency": "IDR",
  "unit": "kilogram",
  "unit_key": "kilogram",
  "observations": [
    {
      "period_start": "2026-10-14",
      "period_end": "2026-10-20",
      "revision": 1,
      "prices": [
        {
          "tree_age_min_years": 3,
          "tree_age_max_years": 3,
          "price": "2786.13"
        },
        {
          "tree_age_min_years": 4,
          "tree_age_max_years": 4,
          "price": "3110.23"
        },
        {
          "tree_age_min_years": 10,
          "tree_age_max_years": 20,
          "price": "3565.06"
        }
      ]
    },
    {
      "period_start": "2026-10-21",
      "period_end": "2026-10-27",
      "revision": 1,
      "prices": [
        {
          "tree_age_min_years": 3,
          "tree_age_max_years": 3,
          "price": "2846.36"
        },
        {
          "tree_age_min_years": 4,
          "tree_age_max_years": 4,
          "price": "3176.13"
        },
        {
          "tree_age_min_years": 10,
          "tree_age_max_years": 20,
          "price": "3640.55"
        }
      ]
    }
  ],
  "next_cursor": null
}

Each prices entry has tree_age_min_years, tree_age_max_years and price. The band from 10 to 20 years is one entry, so the lookup matches an age against the range.

Compute the weekly change for one tree age

  1. List and read The script finds the series with the producer filter and reads September 30 to November 3 2026.
  2. Pick the age band For a 12 year old tree it takes the entry whose age range contains 12, which is the 10 to 20 band.
  3. Compare weeks It prints the price for each week with the change from the week before.
    Python, weekly change in the purchase schedule
    import os
    import requests
    
    API = "https://api.narwhalapi.com"
    HEAD = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
    
    listing = requests.get(f"{API}/v1/commodities/palmoil/physical",
                           params={"product": "ffb", "producer_arrangement": "purchase_agreement"},
                           headers=HEAD, timeout=15).json()["series"]
    sid = listing[0]["series_id"]
    
    r = requests.get(f"{API}/v1/commodities/palmoil/physical/{sid}/observations",
                     params={"start": "2026-09-30", "end": "2026-11-03"}, headers=HEAD, timeout=15)
    r.raise_for_status()
    body = r.json()
    
    def price_for_age(row, age):
        return next(float(p["price"]) for p in row["prices"]
                    if p["tree_age_min_years"] <= age <= p["tree_age_max_years"])
    
    previous = None
    for row in body["observations"]:
        price = price_for_age(row, 12)
        change = f" ({100 * (price / previous - 1):+.1f}%)" if previous else ""
        print(row["period_start"], row["period_end"], f"{price:.2f} {body['currency']} per {body['unit']}{change}")
        previous = price
WeekPrice, IDR per kilogramChange
2026-09-30 to 2026-10-063826.84none
2026-10-07 to 2026-10-133694.68-3.5%
2026-10-14 to 2026-10-203565.06-3.5%
2026-10-21 to 2026-10-273640.55+2.1%

Malaysian crude palm oil series use one price per day in MYR per metric_ton, with a price_month field. The latest value of mys-cpo-local-delivered was 4388.00 MYR for the day starting 2026-10-30. The Indonesian reference series idn-cpo-reference-price is monthly in USD per metric_ton. Each series defines its own price, so name the series in the report.

Which palm oil price should I use?

It depends on what you buy. The world benchmark in worldbank-cmo-palm-oil suits a monthly price in USD. The producer schedules fit a buyer who pays growers in Indonesia and wants the IDR price by tree age. Do not mix the two in one change calculation, since they differ in currency, period and definition.

Does a price change need a revision check?

The rows above all carry revision 1. Store the revision with each price so that a later revision to a month can be explained. Compute a change only from two prices of the same series and the same unit. A change between a revised and an unrevised month mixes two vintages, so store the revision beside each price in your own table, and recompute the change whenever a stored revision differs from the one the API now returns.

Can I get rice or coffee in my own currency?

These series are quoted in USD. For a conversion, see get a commodity price in your currency and keep both the original USD price and the rate you used, so the result can be traced.

Published .

Next steps