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. 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 (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 ..."
],
"unit": "cents/kWh"
}

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.
  • 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.
unitstringAlways "cents/kWh"
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.
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 — 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".

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".
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).

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