Browse all documentation

Historical Palm oil physical API

Get oldest-first Palm oil observations

Endpoint and request

REST endpoint
GET /v1/commodities/palmoil/physical/{series_id}/observations
Contract 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

NameRequiredTypeMeaning
series_idYespath · stable Narwhal IDSeries returned by discovery.
startNoquery · YYYY-MM-DDInclusive range start; supply it with end. Indonesian effective periods are included when they overlap the range. Malaysian daily prices are selected by report date.
endNoquery · YYYY-MM-DDExclusive upper bound. It must be later than start.
limitNoquery · integer, 1–500Maximum observations to return. Defaults to 100; age schedules remain complete.
cursorNoquery · opaque stringContinue with every original parameter unchanged, including series_id, dates, limit and price_month. A changed publication can expire the cursor.
price_monthNoquery · YYYY-MMMPOB 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
}
FieldTypeRequiredMeaning
series_idstringYesStable physical-series identity.
currency / unitstringsYesCurrency and native unit shared by every observation.
observationsarrayYesPeriod, 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_cursorstring or nullYesOpaque 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_exceeded or quota_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.