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).
Pricing sources
Section titled “Pricing sources”A user’s rates resolve from one of three 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 | Live 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". |
GET /api/v1/rates
Section titled “GET /api/v1/rates”Auth: Required (Bearer JWT)
Example
Section titled “Example”curl https://api.hungrymachines.io/api/v1/rates \ -H "Authorization: Bearer YOUR_TOKEN"Response (200) — zone
Section titled “Response (200) — zone”{ "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, "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 and dynamic responses use the same shape; only source and the source-specific fields change:
- custom —
source: "custom",hourly_rates_cents_per_kwhholds the raw 24-element override. - dynamic —
source: "dynamic",dynamic_zoneis the chosen slug,pricing_adder_cents_per_kwhis the per-user adder.
Response fields
Section titled “Response fields”| 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 cents/kWh values for the resolved source |
source | string | What the resolver actually used this call: "zone", "custom", or "dynamic". May differ from pricing_source when dynamic falls back. |
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, only when source is "custom" |
pricing_source | string | The user’s stored preference: "zone", "custom", or "dynamic". 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 = use the global default. |
available_dynamic_zones | array[{slug, iso, label}] | Catalog for the dynamic source. Use slug when sending dynamic_zone; render label; iso is informational. |
available_pricing_zones | array[{id, slug, utility, plan, region, label, notes}] | Catalog of preset plans. Use id when sending pricing_location; render label. |
unit | string | Always "cents/kWh" |
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 401 | "Not authenticated" | Missing or invalid token |
| 503 | "Database not configured" | DB not configured on the server |
PUT /api/v1/rates
Section titled “PUT /api/v1/rates”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)
Required vs. optional
Section titled “Required vs. optional”All fields are optional. What you send depends on the source you want:
- Custom rates — send
hourly_rates_cents_per_kwh(24 values), ornullto clear. - Dynamic — send
pricing_source: "dynamic"plus adynamic_zone(required here or already stored).pricing_adder_cents_per_kwhis optional. - Preset zone — send
pricing_source: "zone"plus apricing_locationid.
Example — custom hourly override
Section titled “Example — custom hourly override”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}.
Example — opt into dynamic pricing
Section titled “Example — opt into dynamic pricing”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}'Example — pick a preset zone
Section titled “Example — pick a preset zone”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}'Fields
Section titled “Fields”| Field | Type | Notes |
|---|---|---|
hourly_rates_cents_per_kwh | array[float] | null | Exactly 24 floats (00:00 → 23:00), each in [0, 200]. null clears the override. |
pricing_source | string | null | "zone", "custom", or "dynamic". null clears the explicit selection. |
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. Validated against the live catalog. |
Response (200)
Section titled “Response (200)”Same shape as GET /api/v1/rates, reflecting the updated state.
Errors
Section titled “Errors”| 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" | Value non-finite or outside [0, 200] |
| 400 | "pricing_source must be one of ['zone', 'custom', 'dynamic']" | 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 |
| 401 | "Not authenticated" | Missing or invalid token |
| 503 | "Database not configured" | DB not configured or write failed |