Historical Palm oil physical API
Get oldest-first Palm oil observations
Endpoint and request
REST endpoint
GET /v1/commodities/palmoil/physical/{series_id}/observationsContract request
curl --request GET \
--url "https://api.narwhalapi.com/v1/commodities/palmoil/physical/id-ri-oil-palm-ffb-smallholder-purchase-agreement/observations?start=2026-08-01&end=2026-09-02&limit=100" \
--header "Authorization: Bearer $NARWHAL_API_KEY"MCP uses commodities_get_palmoil_physical_observations. REST and MCP share the same schema, account quota, rights check, and problem shape.
Parameters
| Name | Required | Type | Meaning |
|---|---|---|---|
series_id | Yes | path · stable Narwhal ID | Series returned by discovery. |
start | No | query · YYYY-MM-DD | Inclusive range start; supply it with end. Indonesian effective periods are included when they overlap the range. Malaysian daily prices are selected by report date. |
end | No | query · YYYY-MM-DD | Exclusive upper bound. It must be later than start. |
limit | No | query · integer, 1–500 | Maximum observations to return. Defaults to 100; age schedules remain complete. |
cursor | No | query · opaque string | Continue with every original parameter unchanged, including series_id, dates, limit and price_month. A changed publication can expire the cursor. |
price_month | No | query · YYYY-MM | MPOB CPO only. Selects one quoted month; defaults to each report's own month. |
Response
Contract example
{
"series_id": "id-ri-oil-palm-ffb-smallholder-purchase-agreement",
"currency": "IDR",
"unit": "kilogram",
"observations": [
{
"period_start": "2026-08-26",
"period_end": "2026-09-01",
"revision": 1,
"prices": [
{
"tree_age_min_years": 3,
"tree_age_max_years": 3,
"price": "2998.31"
},
{
"tree_age_min_years": 4,
"tree_age_max_years": 4,
"price": "3347.20"
},
{
"tree_age_min_years": 5,
"tree_age_max_years": 5,
"price": "3595.38"
},
{
"tree_age_min_years": 6,
"tree_age_max_years": 6,
"price": "3734.94"
},
{
"tree_age_min_years": 7,
"tree_age_max_years": 7,
"price": "3818.86"
},
{
"tree_age_min_years": 8,
"tree_age_max_years": 8,
"price": "3865.46"
},
{
"tree_age_min_years": 9,
"tree_age_max_years": 9,
"price": "3878.72"
},
{
"tree_age_min_years": 10,
"tree_age_max_years": 20,
"price": "3836.93"
},
{
"tree_age_min_years": 21,
"tree_age_max_years": 21,
"price": "3770.94"
},
{
"tree_age_min_years": 22,
"tree_age_max_years": 22,
"price": "3695.31"
},
{
"tree_age_min_years": 23,
"tree_age_max_years": 23,
"price": "3609.33"
},
{
"tree_age_min_years": 24,
"tree_age_max_years": 24,
"price": "3544.29"
},
{
"tree_age_min_years": 25,
"tree_age_max_years": 25,
"price": "3490.84"
},
{
"tree_age_min_years": 26,
"tree_age_max_years": 26,
"price": "3471.62"
},
{
"tree_age_min_years": 27,
"tree_age_max_years": 27,
"price": "3441.82"
},
{
"tree_age_min_years": 28,
"tree_age_max_years": 28,
"price": "3385.41"
},
{
"tree_age_min_years": 29,
"tree_age_max_years": 29,
"price": "3344.02"
},
{
"tree_age_min_years": 30,
"tree_age_max_years": 30,
"price": "3249.41"
}
]
}
],
"next_cursor": null
}| Field | Type | Required | Meaning |
|---|---|---|---|
series_id | string | Yes | Stable physical-series identity. |
currency / unit | strings | Yes | Currency and native unit shared by every observation. |
observations | array | Yes | Period, revision, and scalar value or complete schedule ordered oldest first; MPOB CPO rows retain price_month; Malaysian CPO and FFB rows retain their report-day period_start/period_end. |
next_cursor | string or null | Yes | Opaque continuation token, or null at the end. |
Errors and quota
400 invalid_query— a code, area, date range, limit, or selector is invalid.400 invalid_cursor— the cursor is invalid or does not match the series and range.401 invalid_api_key— the Bearer key is missing or rejected.403 plan_upgrade_required— the requested period or start is before your plan's history window (Free 12 months, Basic 5 years, Pro full).404 palmoil_series_not_found— no complete approved data matches the request.409 cursor_expired, the publication changed. Restart pagination without a cursor.429 rate_limit_exceededorquota_exhausted— the shared allowance is exhausted.503 palmoil_unavailable— a complete publication cannot safely satisfy the Interface.
Rejected requests and server errors do not consume monthly quota.
Coverage
Accepted history varies by series. Malaysian archives have documented report gaps; Indonesian FFB coverage varies by area.
Available with a Narwhal API key.
Docs