NEMStats API

Read-only REST API and outbound webhooks, included with the Business plan. Create API keys and webhook endpoints from your account (owner or team admins).

Limits

Requests per API key60 per minute
Requests per account (all keys)20,000 per day (resets at midnight AEST)
Active API keys per account5
Price history per request7 days
Webhook endpoints per account3
Webhook delivery attempts3 (immediately, then after 5 and 30 minutes)
CSV export range31 days per file

Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time). Over a limit you get 429 with Retry-After in seconds. Requests rejected for a missing or invalid key don't count. Prices update every 5 minutes, so polling faster than once a minute won't return new data.

Authentication

Send your key as a bearer token. Keys belong to the account, not a person, and stop working if the account leaves Business.

curl -H "Authorization: Bearer nem_..." https://nemstats.com.au/api/v1/prices/latest

Errors return JSON: {"error": {"code": "...", "message": "..."}} with status 400, 401, 403 (no Business plan), 404, 405 or 429.

Endpoints

All times are NEM time (AEST, UTC+10:00, no daylight saving). Prices are regional reference prices in AUD/MWh for the 5-minute dispatch interval ending at interval_end.

GET /api/v1/prices/latest

Latest dispatch price for all five regions.

{
  "interval_end": "2026-11-03T14:35:00+10:00",
  "unit": "AUD/MWh",
  "data": [{"region": "NSW", "price": 92.14}, {"region": "QLD", "price": 88.02}, ...]
}

GET /api/v1/prices?region=NSW&from=…&to=…

5-minute prices for one region (NSW, QLD, VIC, SA, TAS). from/to are ISO 8601 (e.g. 2026-11-01T00:00, read as NEM time, or with an offset). Defaults to the 24 hours up to the latest interval; at most 7 days per request.

{
  "region": "NSW", "from": "2026-11-01T00:00:00+10:00", "to": "2026-11-02T00:00:00+10:00",
  "unit": "AUD/MWh", "interval_minutes": 5,
  "data": [{"interval_end": "2026-11-01T00:05:00+10:00", "price": 71.30}, ...]
}

GET /api/v1/alerts

Your team's price alerts and whether each condition is currently active or paused by plan limits.

{"data": [{"id": 12, "region": "SA", "direction": "above", "trigger_price": 300, "notify_email": true,
  "notify_sms": true, "notify_roster": false, "condition_active": false, "paused": false, "paused_reason": null}]}

GET /api/v1/usage

SMS usage for the current allowance period.

{"plan": "business", "billing_interval": "year",
 "period_start": "2026-11-14T09:30:00+10:00", "period_end": "2026-12-14T09:30:00+10:00",
 "sms": {"included": 2000, "included_used": 412, "included_remaining": 1588, "extra_enabled": false,
         "extra_used": 0, "extra_rate_cents": 5, "extra_cost_cents": 0, "extra_cap_cents": 2000,
         "topup_amount_cents": 2000, "topup_balance_cents": 0, "status": "ok"}}

Webhooks

When one of your team's alerts triggers or clears we POST a JSON event to each of your HTTPS endpoints. Respond with any 2xx within 5 seconds. Redirects aren't followed, and endpoints must resolve to a public address on port 443. Events for alerts paused by plan limits aren't sent.

POST /your-endpoint
Content-Type: application/json
X-NEMStats-Event: alert.triggered
X-NEMStats-Delivery: 8812
X-NEMStats-Signature: t=1793780100,v1=5f2c...

{"id": "a12-triggered-1793780100", "type": "alert.triggered", "created": "2026-11-03T04:35:00+00:00",
 "data": {"alert_id": 12, "region": "SA", "matched_region": "SA", "direction": "above",
          "trigger_price": 300, "price": 412.5, "unit": "AUD/MWh"}}

Verify the signature: compute HMAC-SHA256 of "{t}.{raw body}" using your endpoint's signing secret, compare it with v1 in constant time, and reject timestamps more than 5 minutes old. The event id is stable across retries, so use it to ignore duplicates.

$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
$valid = hash_equals($expected, $v1) && abs(time() - $t) < 300;

Not included

The API is read-only and covers the endpoints above. Creating or editing alerts, generator statistics and FCAS prices aren't available through the API yet.