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.

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 resolve from one of four sources, chosen via PUT /api/v1/rates:

SourceWhat it is
zoneA preset time-of-use plan from the API-served available_pricing_zones catalog (real residential utility plans). The default.
customThe user’s own 24-hour hourly_rates_cents_per_kwh array, expanded to 48 half-hour slots.
dynamicLive day-ahead wholesale pricing for the user’s chosen grid region, plus a flat per-kWh adder for delivery/tax riders. The user can additionally select a delivery/distribution plan (adder_grid_ruleset_id, browse options via available_delivery_tariffs) whose per-slot charge is added to the curve — e.g. ComEd Hourly Pricing users pairing it with ComEd’s Delivery Time-of-Day plan — or send their own four period prices (delivery_tod_cents, prefilled from the selected plan’s available_delivery_tariffs[].period_rates and editable to match a real bill), which take precedence over adder_grid_ruleset_id when set. Falls back to the zone curve if data is unavailable — the response then reports source: "zone" while pricing_source still reads "dynamic".
tariffA plan from the tariff library, linked via tariff_ruleset_id (browse with GET /api/v1/rates/tariffs). Rates resolve in the tariff’s own currency (minor units per kWh — USD/CAD cents, GBP pence, EUR cents); the dynamic adder does not apply. Falls back to the zone curve when the linked tariff is missing or no longer in force — the response then reports source: "zone" while pricing_source still reads "tariff". California users can skip browsing and send the Rate Identification Number (RIN) printed on their bill (rate_rin): if the RIN’s tariff is already in the library it links immediately; otherwise it links automatically once the tariff lands in the library (refreshed weekly).

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,
"rate_rin": null,
"rin_status": null,
"available_dynamic_zones": [
{ "slug": "comed", "iso": "PJM", "label": "ComEd — Hourly Pricing (Northern Illinois)" },
{ "slug": "ameren", "iso": "MISO", "label": "Ameren Illinois (Power Smart Pricing)" }
],
"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
}

The custom, dynamic, and tariff responses use the same shape; only source and the source-specific fields change:

  • customsource: "custom", hourly_rates_cents_per_kwh holds the raw 24-element override.
  • dynamicsource: "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 (takes precedence over adder_grid_ruleset_id).
  • tariffsource: "tariff", tariff_ruleset_id is the linked tariff-library id; rates_cents_per_kwh is in the tariff’s currency (minor units per kWh). rate_rin / rin_status report a stored RIN and whether it is "linked" or still "pending".
FieldTypeDescription
pricing_locationintegerCatalog id from available_pricing_zones; defaults to 1 when unset
intervalsarray[int]Indices 047 (30-minute slots of the local day)
rates_cents_per_kwharray[float]Exactly 48 values for the resolved source (cents/kWh, or the tariff currency’s minor units for "tariff")
sourcestringWhat the resolver actually used this call: "zone", "custom", "dynamic", or "tariff". May differ from pricing_source when dynamic or tariff falls back.
seasonstring"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_kwharray[float] | nullThe user’s raw 24-element override, only when source is "custom"
pricing_sourcestringThe user’s stored preference: "zone", "custom", "dynamic", or "tariff". Reflect this in your UI toggle.
dynamic_zonestring | nullThe user’s chosen dynamic-pricing slug. null when none selected.
pricing_adder_cents_per_kwhnumber | nullPer-user flat adder for dynamic pricing. null = use the global default.
tariff_ruleset_idinteger | nullThe user’s linked tariff-library id (browse via GET /api/v1/rates/tariffs). null when no tariff linked.
rate_rinstring | nullThe stored Rate Identification Number (California bills), e.g. USCA-SCSC-TOD4-0000. Deprecated — California retail plans are now selectable as dynamic zones; still accepted for back-compat. null when none stored.
rin_statusstring | null"linked" (a tariff is linked) or "pending" (the RIN is stored but not linked). A "pending" RIN is dormant — select a matching dynamic zone for your plan instead. null when no RIN is stored.
available_dynamic_zonesarray[{slug, iso, label}]Catalog for the dynamic source. Use slug when sending dynamic_zone; render label; iso is informational.
available_pricing_zonesarray[{id, slug, utility, plan, region, label, notes}]Catalog of preset plans. Use id when sending pricing_location; render label.
adder_grid_ruleset_idinteger | nullThe user’s selected delivery/distribution plan (browse via available_delivery_tariffs). Only meaningful alongside pricing_source="dynamic". null = no delivery plan selected (an estimated flat adder is used instead). Ignored by the resolver whenever delivery_tod_cents is a valid four-key map — it still records which plan the values came from.
available_delivery_tariffsarray[{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_centsobject | nullThe user’s own four period prices — {morning, midday_peak, evening, overnight}, cents/kWh. null when unset. Takes precedence over adder_grid_ruleset_id when it is a valid four-key map; otherwise the user falls back to adder_grid_ruleset_id, then no delivery component.
unitstringAlways "cents/kWh"
export_rates_cents_per_kwharray[float] | nullWhat 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.
StatusDetailCause
401"Not authenticated"Missing or invalid token
503"Database not configured"DB not configured on the server

Browse the tariff library to find a tariff_ruleset_id to select on PUT /api/v1/rates. Returns plan metadata only — rate values resolve through 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.

ParamTypeRequiredNotes
countrystringNoExact country code, case-insensitive (US, CA).
regionstringNoCase-insensitive substring match on region.
utilitystringNoExact utility name, case-insensitive.
qstringNoFree-text search over utility, plan_name, and region (case-insensitive substring).
limitintegerNoPage size, 1–500 (default 50).
offsetintegerNoRows 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 ..."
]
}
FieldTypeDescription
totalintegerFull match count before pagination — drive “page X of Y” from this, not tariffs.length
limit / offsetintegerEcho of the applied pagination
country / region / utility / qstring | nullEcho of the applied filters
tariffs[].idintegerSend as tariff_ruleset_id on PUT /api/v1/rates
tariffs[].currencystringThe tariff’s currency (USD, CAD, GBP, EUR). Rates resolve in this currency’s minor units per kWh.
tariffs[].verifiedbooleantrue once the tariff has been checked against the utility’s filed rate sheet
tariffs[].disclaimerstringPresent only on unverified tariffs: "Community-sourced rate — verify against your utility bill". Render it beside the plan; verified tariffs omit the key.
tariffs[].effective_fromstring | nullFirst day the tariff is in force (YYYY-MM-DD); null = open start
StatusDetailCause
401"Not authenticated"Missing or invalid token
422(validation detail)limit / offset out of range
503"Database not configured"DB not configured on the server

Set or clear any subset of the user’s pricing configuration. Only fields you explicitly send are updated — omitted fields are left unchanged.

Auth: Required (Bearer JWT)

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

  • Custom rates — send hourly_rates_cents_per_kwh (24 values), or null to clear.
  • 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.
  • Tariff by RIN (California, deprecated) — California retail plans are now selectable as dynamic zones; pick the one matching your plan instead of entering a RIN. rate_rin is still accepted: if the RIN’s tariff is already in the library it links immediately (pricing_source flips to "tariff"); otherwise the RIN is stored with rin_status: "pending" and stays dormant (no longer auto-linked). An explicitly sent tariff_ruleset_id or pricing_source in the same request wins over the RIN-derived values; {"rate_rin": null} clears the stored RIN without touching an existing tariff 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. Sends alongside pricing_source="dynamic"; null clears the selection (an estimated flat adder is used instead).
  • 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. Takes precedence over adder_grid_ruleset_id; send both to keep the class on record alongside your edited values. 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 '{"hourly_rates_cents_per_kwh": [40.0, 40.0, "... 24 values total ..."]}'

Clear the override 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}}'

delivery_tod_cents takes precedence over adder_grid_ruleset_id; sending both keeps the class on record alongside your edited values. It can also be sent alone; {"delivery_tod_cents": null} clears it.

Example — enter the RIN printed on a California bill

Section titled “Example — enter the RIN printed on a California bill”
Terminal window
curl -X PUT https://api.hungrymachines.io/api/v1/rates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rate_rin": "USCA-SCSC-TOD4-0000"}'

The response’s rin_status reports "linked" or "pending".

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}.

FieldTypeNotes
hourly_rates_cents_per_kwharray[float] | nullExactly 24 floats (00:0023:00), each in [0, 200]. null clears the override.
pricing_sourcestring | null"zone", "custom", "dynamic", or "tariff". null clears the explicit selection.
dynamic_zonestring | nullSlug from available_dynamic_zones (case-insensitive). Required (here or already stored) when pricing_source="dynamic".
pricing_adder_cents_per_kwhnumber | nullFlat per-kWh adder for dynamic pricing, in [0, 50]. null clears the per-user override.
pricing_locationintegerCatalog id from available_pricing_zones. Validated against the live catalog.
tariff_ruleset_idinteger | nullId 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_idinteger | nullId from available_delivery_tariffs. Must reference an available delivery/distribution plan. null clears the selection.
delivery_tod_centsobject | nullExactly the four keys morning/midday_peak/evening/overnight, each a number in [0, 50] cents/kWh. null clears it. Takes precedence over adder_grid_ruleset_id; settable in the same request.
rate_rinstring | nullDeprecated — use a matching dynamic zone instead. The Rate Identification Number printed on California bills (four hyphen-separated groups, e.g. USCA-SCSC-TOD4-0000; lowercase is uppercased). Invalid format → 422. Links only if the RIN’s tariff is already in the library, else stored dormant-pending. null or a blank string clears the stored RIN (an existing tariff link is kept).
export_rates_cents_per_kwharray[float] | nullExactly 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.

StatusDetailCause
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"Value non-finite or outside [0, 200]
400"pricing_source must be one of ['zone', 'custom', 'dynamic', 'tariff']"Unknown source
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 non-numeric or out of range
400"pricing_location must be one of the catalog ids: [...]"pricing_location not in available_pricing_zones
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
422standard validation error bodyrate_rin fails the structural RIN format check
401"Not authenticated"Missing or invalid token
503"Database not configured"DB not configured or write failed