Skip to content

Rates

The rates endpoints expose the electricity rate curve the optimizer saves against, plus the metadata to render and edit the user’s pricing selection. Use them to color a schedule timeline by peak / off-peak pricing, or to let the user pick how their electricity is priced.

All values are in cents per kWh. Clients displaying dollars divide by 100; clients submitting dollars multiply by 100 first. For a tariff or a dynamic zone outside the US, read the same numbers as that plan’s own currency in minor units per kWh (pence, euro cents, and so on); nothing is converted. The response’s currency field names which ISO 4217 currency that is.

The rate curve has the same 48 × 30-minute shape as schedules — each interval is a half-hour slot of the local day, indexed 0 (00:00–00:30) through 47 (23:30–00:00).

A user’s rates come from one of four sources, chosen via PUT /api/v1/rates:

Source What it is
zone A preset time-of-use plan from the API-served available_pricing_zones catalog (real residential utility plans). The default.
custom The user’s own 24-hour hourly_rates_cents_per_kwh array, expanded to 48 half-hour slots.
dynamic Day-ahead pricing that changes daily, for the user’s chosen dynamic_zone. Zones priced from a wholesale market add a flat per-kWh amount (pricing_adder_cents_per_kwh) for delivery and tax riders. The user can also select a delivery/distribution plan (adder_grid_ruleset_id, options in available_delivery_tariffs) — e.g. ComEd Hourly Pricing users on ComEd’s Delivery Time-of-Day plan — and can enter their own four period prices (delivery_tod_cents) to match a real bill. When delivery_tod_cents is set it is used; otherwise the selected plan’s published rates are used. When day-ahead prices are not available, the response carries the zone curve and reports source: "zone" while pricing_source still reads "dynamic".
tariff A plan from the tariff library, linked via tariff_ruleset_id (browse with GET /api/v1/rates/tariffs). Rates are in the tariff’s own currency (minor units per kWh — USD/CAD cents, GBP pence, EUR cents); the dynamic adder does not apply. When the linked tariff is missing or no longer in force, the response carries the zone curve and reports source: "zone" while pricing_source still reads "tariff".

Auth: Required (Bearer JWT)

Terminal window
curl https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN"
{
"pricing_location": 1,
"intervals": [0, 1, 2, "...", 47],
"rates_cents_per_kwh": [24.7, 24.7, "... 48 values total ..."],
"source": "zone",
"season": "summer",
"hourly_rates_cents_per_kwh": null,
"pricing_source": "zone",
"dynamic_zone": null,
"pricing_adder_cents_per_kwh": null,
"tariff_ruleset_id": null,
"available_dynamic_zones": [
{ "slug": "comed", "label": "ComEd — Hourly Pricing (Northern Illinois)" },
{ "slug": "ameren", "label": "Ameren Illinois (Power Smart Pricing)" },
"... more entries (UK, EU/Nordic, Spain, Australia and California plans) ..."
],
"available_pricing_zones": [
{ "id": 1, "slug": "sdge-tou-dr1", "utility": "SDG&E", "plan": "TOU-DR1", "region": "San Diego, CA", "label": "SDG&E TOU-DR1 — San Diego", "notes": "peak 4-9pm" },
"... more entries ..."
],
"adder_grid_ruleset_id": null,
"available_delivery_tariffs": [
{ "id": 12, "external_id": "comed-dtod-sf", "plan_name": "Delivery Time-of-Day — Single-Family", "utility": "ComEd", "region": "Northern Illinois", "period_rates": { "morning": 4.009, "midday_peak": 10.712, "evening": 3.747, "overnight": 2.984 } },
"... more entries ..."
],
"delivery_tod_cents": null,
"unit": "cents/kWh",
"export_rates_cents_per_kwh": null,
"currency": "USD"
}

The custom, dynamic, and tariff responses use the same shape; only source and the source-specific fields change. A response can carry keys beyond those documented here; ignore them.

  • custom — source: "custom", hourly_rates_cents_per_kwh holds the raw 24-element override.
  • dynamic — source: "dynamic", dynamic_zone is the chosen slug, pricing_adder_cents_per_kwh is the per-user adder, adder_grid_ruleset_id is the selected delivery/distribution plan (if any), delivery_tod_cents is the user’s own four period prices if set (used in place of the selected plan’s published rates).
  • tariff — source: "tariff", tariff_ruleset_id is the linked tariff-library id; rates_cents_per_kwh is in the tariff’s currency (minor units per kWh).
Field Type Description
pricing_location integer Catalog id from available_pricing_zones; defaults to 1 when unset
intervals array[int] Indices 0–47 (30-minute slots of the local day)
rates_cents_per_kwh array[float] Exactly 48 values for the source reported in source (cents/kWh, or the plan currency’s minor units for a tariff or a non-US dynamic zone). Display only.
source string Which pricing source this response’s curve came from: "zone", "custom", "dynamic", or "tariff". Differs from pricing_source when dynamic or tariff pricing was unavailable and the zone curve was returned instead.
season string "summer" or "winter", from the user’s local date. Only some plans differ by season; others report the season but return the same curve.
hourly_rates_cents_per_kwh array[float] | null The user’s raw 24-element override, whenever one is stored — regardless of the active source. null when none is stored.
pricing_source string The user’s stored preference: "zone", "custom", "dynamic", or "tariff". Reflect this in your UI toggle.
dynamic_zone string | null The user’s chosen dynamic-pricing slug. null when none selected.
pricing_adder_cents_per_kwh number | null Per-user flat adder for dynamic pricing. null = the server default for the zone.
tariff_ruleset_id integer | null The user’s linked tariff-library id (browse via GET /api/v1/rates/tariffs). null when no tariff linked.
available_dynamic_zones array[{slug, label}] Catalog for the dynamic source. Use slug when sending dynamic_zone; render label — it names the plan or market (e.g. "SCE TOU-D-4-9PM (California)", "Netherlands day-ahead").
available_pricing_zones array[{id, slug, utility, plan, region, label, notes}] Catalog of preset plans. Use id when sending pricing_location; render label.
adder_grid_ruleset_id integer | null The user’s selected delivery/distribution plan (browse via available_delivery_tariffs). Only meaningful alongside pricing_source="dynamic". null = no delivery plan selected. When delivery_tod_cents is set, those prices are used and this field records which plan they started from.
available_delivery_tariffs array[{id, external_id, plan_name, utility, region, period_rates}] Delivery/distribution plans available for adder_grid_ruleset_id. period_rates is that plan’s published {morning, midday_peak, evening, overnight} rates — use it to prefill delivery_tod_cents when the user selects this plan; null if it can’t be derived. Empty when none apply to your utility.
delivery_tod_cents object | null The user’s own four period prices — {morning, midday_peak, evening, overnight}, cents/kWh. null when unset. When set, these prices are used in place of the selected plan’s published rates.
unit string Always "cents/kWh". For a tariff or a non-US dynamic zone, read it as that plan’s currency minor units per kWh — see currency below for which one.
export_rates_cents_per_kwh array[float] | null What your utility pays for exported solar — 48 half-hour slots (slot 0 = local midnight), same units as rates_cents_per_kwh. null = no export credit configured. Settable via PUT.
currency string ISO 4217 code of the curve actually served: "USD" for zone/custom sources and a USD dynamic zone, the plan’s own code for a tariff, the zone’s own code for a non-US dynamic zone (e.g. "GBP", "EUR", "AUD").
Status Detail Cause
401 "Not authenticated" Missing or invalid token

Browse the tariff library to find a tariff_ruleset_id to select on PUT /api/v1/rates. Returns plan metadata only — the rate values appear in GET /api/v1/rates once a tariff is linked.

Auth: Required (Bearer JWT)

All parameters are optional — with none, the full library is returned page by page.

Param Type Required Notes
country string No Exact country code, case-insensitive (US, CA).
region string No Case-insensitive substring match on region.
utility string No Exact utility name, case-insensitive.
q string No Free-text search over utility, plan_name, and region (case-insensitive substring).
limit integer No Page size, 1–500 (default 50).
offset integer No Rows to skip (default 0).
Terminal window
curl "https://api.hungrymachines.io/api/v1/rates/tariffs?country=US&q=tou" \
-H "Authorization: Bearer YOUR_TOKEN"
{
"total": 2,
"limit": 50,
"offset": 0,
"country": "US",
"region": null,
"utility": null,
"q": "tou",
"tariffs": [
{
"id": 7,
"utility": "SCE",
"plan_name": "TOU-D-4-9PM",
"region": "Southern California",
"country": "US",
"currency": "USD",
"verified": false,
"effective_from": "2026-01-01",
"disclaimer": "Community-sourced rate — verify against your utility bill"
},
"... more entries ..."
]
}
Field Type Description
total integer Full match count before pagination — drive “page X of Y” from this, not tariffs.length
limit / offset integer Echo of the applied pagination
country / region / utility / q string | null Echo of the applied filters
tariffs[].id integer Send as tariff_ruleset_id on PUT /api/v1/rates
tariffs[].currency string The tariff’s currency (USD, CAD, GBP, EUR). Its rates are in this currency’s minor units per kWh.
tariffs[].verified boolean true once the tariff has been checked against the utility’s filed rate sheet
tariffs[].disclaimer string Present only on unverified tariffs: "Community-sourced rate — verify against your utility bill". Render it beside the plan; verified tariffs omit the key.
tariffs[].effective_from string | null First day the tariff is in force (YYYY-MM-DD); null = open start
Status Detail Cause
401 "Not authenticated" Missing or invalid token
422 array detail limit / offset out of range or not an integer
503 "Database unavailable" or "Failed to load tariff store" The tariff library could not be read

Set or clear any subset of the user’s pricing configuration. Only fields you explicitly send are updated — omitted fields are left unchanged. A request with none of the fields below changes nothing and returns the current state.

Auth: Required (Bearer JWT)

All fields are optional. What you send depends on the source you want:

  • Custom rates — send pricing_source: "custom" together with hourly_rates_cents_per_kwh (24 values), unless one is already stored. Send {"hourly_rates_cents_per_kwh": null} to clear the stored array.
  • Dynamic — send pricing_source: "dynamic" plus a dynamic_zone (required here or already stored). pricing_adder_cents_per_kwh is optional.
  • Preset zone — send pricing_source: "zone" plus a pricing_location id.
  • Tariff — send pricing_source: "tariff" plus a tariff_ruleset_id (required here or already stored) from GET /api/v1/rates/tariffs. Sending tariff_ruleset_id alone links the tariff without changing the source; null clears the link.
  • Delivery/distribution plan (dynamic only) — send adder_grid_ruleset_id with an id from available_delivery_tariffs, e.g. a ComEd Delivery Time-of-Day class. Applies alongside pricing_source="dynamic"; null clears the selection.
  • Your own DTOD period prices (dynamic only) — send delivery_tod_cents with all four keys morning/midday_peak/evening/overnight, prefilled from available_delivery_tariffs[].period_rates and edited to match your bill. When set, these prices are used; send adder_grid_ruleset_id with them to record which plan the values started from. null clears it.
  • Export rates (solar) — send export_rates_cents_per_kwh (48 values) with what your utility pays per half-hour slot for power you send back. Independent of every other pricing field. null clears it (no export credit).
Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pricing_source": "custom", "hourly_rates_cents_per_kwh": [22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 28.0, 28.0, 28.0, 28.0, 28.0, 28.0, 28.0, 28.0, 28.0, 28.0, 45.0, 45.0, 45.0, 45.0, 45.0, 28.0, 28.0, 22.0]}'

Send the source and the array together. Clear the stored array with {"hourly_rates_cents_per_kwh": null}.

Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pricing_source": "dynamic", "dynamic_zone": "comed", "pricing_adder_cents_per_kwh": 6.5}'
Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pricing_source": "zone", "pricing_location": 7}'

Example — select a tariff from the library

Section titled “Example — select a tariff from the library”
Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pricing_source": "tariff", "tariff_ruleset_id": 7}'

Example — select a delivery/distribution plan

Section titled “Example — select a delivery/distribution plan”
Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"adder_grid_ruleset_id": 12}'

Send {"adder_grid_ruleset_id": null} to clear the selection.

Example — edit the four DTOD period prices

Section titled “Example — edit the four DTOD period prices”
Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"adder_grid_ruleset_id": 12, "delivery_tod_cents": {"morning": 4.5, "midday_peak": 11.0, "evening": 3.8, "overnight": 3.0}}'

When delivery_tod_cents is set, those prices are used; sending adder_grid_ruleset_id with it records which plan the values started from. It can also be sent alone; {"delivery_tod_cents": null} clears it.

Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"export_rates_cents_per_kwh": [3.0, 3.0, "... 48 values total ..."]}'

Clear it with {"export_rates_cents_per_kwh": null}.

Field Type Notes
hourly_rates_cents_per_kwh array[float] | null Exactly 24 numbers (00:00 → 23:00), each in [0, 200]. Send it with pricing_source: "custom". null clears the stored array.
pricing_source string | null "zone", "custom", "dynamic", or "tariff" (case-insensitive). null clears the explicit selection. Required (here or already stored) when "custom": send hourly_rates_cents_per_kwh too unless one is already stored.
dynamic_zone string | null Slug from available_dynamic_zones (case-insensitive). Required (here or already stored) when pricing_source="dynamic".
pricing_adder_cents_per_kwh number | null Flat per-kWh adder for dynamic pricing, in [0, 50]. null clears the per-user override.
pricing_location integer Catalog id from available_pricing_zones. Has no cleared state: null is rejected.
tariff_ruleset_id integer | null Id from GET /api/v1/rates/tariffs. Must reference a tariff that exists and is currently in force. null clears the link. Required (here or already stored) when pricing_source="tariff".
adder_grid_ruleset_id integer | null Id from available_delivery_tariffs. Must reference an available delivery/distribution plan. null clears the selection.
delivery_tod_cents object | null Exactly the four keys morning/midday_peak/evening/overnight, each a number in [0, 50] cents/kWh. null clears it. Settable in the same request as adder_grid_ruleset_id.
export_rates_cents_per_kwh array[float] | null Exactly 48 floats (slot 0 = local midnight), each in [0, 200]. null clears it (no export credit).

Same shape as GET /api/v1/rates, reflecting the updated state.

Status Detail Cause
400 "hourly_rates_cents_per_kwh must have exactly 24 values" Array length is not 24
400 "rate at hour <i> must be between 0 and 200 cents/kWh" A value outside [0, 200]
400 "pricing_source must be one of ['zone', 'custom', 'dynamic', 'tariff']" Unknown source
400 "pricing_source custom requires hourly_rates_cents_per_kwh" custom selected with no override sent (or explicitly nulled) and none already stored
400 "pricing_source='dynamic' requires dynamic_zone (one of: [...])" Dynamic selected without a resolvable zone
400 "dynamic_zone must be one of: [...]" Unknown dynamic_zone slug
400 "pricing_adder_cents_per_kwh must be a number between 0 and 50 cents/kWh" Adder outside [0, 50]
400 "pricing_location must be one of the catalog ids: [...]" pricing_location not in available_pricing_zones, or sent as null
400 "tariff_ruleset_id must be a positive integer id" Non-positive id sent
400 "tariff_ruleset_id <id> does not exist (browse GET /api/v1/rates/tariffs)" Id matches no tariff
400 "tariff_ruleset_id <id> is not currently in force" Tariff’s effective window excludes today
400 "pricing_source='tariff' requires tariff_ruleset_id (browse GET /api/v1/rates/tariffs)" Tariff selected with no id sent or stored
400 "adder_grid_ruleset_id <id> does not reference an available delivery tariff (see available_delivery_tariffs on GET /api/v1/rates)" Id doesn’t match a delivery/distribution plan
400 "delivery_tod_cents must be an object with exactly the keys [...]" Payload isn’t an object
400 "delivery_tod_cents must have exactly the keys [...] (missing [...]; unexpected [...])" A key is missing or an unexpected key is present
400 "delivery_tod_cents.<key> must be a number between 0 and 50 cents/kWh" A period value is a bool, non-numeric, non-finite, or out of range
400 "export_rates_cents_per_kwh must have exactly 48 values" Array length is not 48
400 "export rate at slot <i> must be between 0 and 200 cents/kWh" Value is a bool, non-numeric, non-finite, or out of range
422 array detail A field of the wrong JSON type (hourly_rates_cents_per_kwh not an array of numbers, a non-numeric adder, or a non-integer id)
401 "Not authenticated" Missing or invalid token
503 "Database unavailable", "Failed to update rates configuration", "Failed to validate tariff_ruleset_id" or "Failed to validate adder_grid_ruleset_id" The database could not be reached or the write failed