# Renewably public API (v1)

Cheapest and greenest times to use electricity on time-of-use tariffs in Great Britain - Octopus Agile
by default, or any tariff in [`/tariffs`](#get-tariffs) from Octopus, EDF and E.ON Next - as JSON, the
same calculations as the public pages on renewably.uk.

- Base URL: `https://renewably.uk/api/v1`
- Machine-readable spec: [`/api/v1/openapi.json`](https://renewably.uk/api/v1/openapi.json) (OpenAPI 3.1)
- Docs (this page, as Markdown): [`/api/v1/docs`](https://renewably.uk/api/v1/docs); for AI tools, [`/llms.txt`](https://renewably.uk/llms.txt) and an [MCP server](#using-it-with-ai) at `/mcp`

## Contents

- [Using it with AI](#using-it-with-ai)
- [Using it with voice](#using-it-with-voice)
- [Design decisions](#design-decisions)
- [Conventions](#conventions)
- [Caching, rate limits and CORS](#caching-rate-limits-and-cors)
- [Errors](#errors)
- [Data sources and licences](#data-sources-and-licences)
- [Endpoints](#endpoints): [index](#get-) · [regions](#get-regions) · [tariffs](#get-tariffs) · [prices](#get-prices) ·
  [cheapest-window](#get-cheapest-window) · [ev-plan](#get-ev-plan) · [heat-pump](#get-heat-pump) ·
  [home-battery](#get-home-battery) · [tariff-comparison](#get-tariff-comparison) · [openapi.json](#get-openapijson)
- [Terms of use](#terms-of-use)
- [Versioning](#versioning)

## Using it with AI

The API is built to be easy to use from AI assistants and coding agents.

**Let your assistant call it directly (MCP).** `https://renewably.uk/mcp` is a remote
[Model Context Protocol](https://modelcontextprotocol.io) server (Streamable HTTP, no key) with a
tool for each endpoint - prices, cheapest window, EV plan, heat pump and battery times, tariff
comparison. Add it as a custom connector (Claude, ChatGPT) or, in Claude Code:

```sh
claude mcp add --transport http renewably https://renewably.uk/mcp
```

Then ask things like "when should I run my dishwasher tonight in Yorkshire, on Octopus Go?".

**Point a coding assistant at the spec.** Everything it needs is machine-readable:
[`/api/v1/openapi.json`](https://renewably.uk/api/v1/openapi.json) (every parameter and field, with a
worked example per endpoint), [`/llms.txt`](https://renewably.uk/llms.txt) (a short guide) and
[`/llms-full.txt`](https://renewably.uk/llms-full.txt) (these docs in full). Every response carries
`Link` headers to the spec and docs, and every error a `docs` link, so an agent can find its own way.
A prompt to start from:

```text
Use the Renewably API (https://renewably.uk/api/v1, no key, GET only; OpenAPI spec at
https://renewably.uk/api/v1/openapi.json, guide at https://renewably.uk/llms.txt). Times are UK
local ISO 8601, prices pence per kWh including VAT. For "when should I run X", use
/cheapest-window; for an electric car, /ev-plan; list regions with /regions and tariffs with
/tariffs. Credit the CC BY sources in each response's meta.sources if you show the data.
```

**By hand:**

```sh
curl "https://renewably.uk/api/v1/cheapest-window?region=london&duration_hours=2&finish_by=07:00"
```

```python
import requests

window = requests.get("https://renewably.uk/api/v1/cheapest-window",
                      params={"region": "london", "duration_hours": 2, "finish_by": "07:00"}).json()["window"]
print(window["start"], window["average_p_per_kwh"])
```

```js
const r = await fetch("https://renewably.uk/api/v1/cheapest-window?region=london&duration_hours=2");
const { window } = await r.json();
console.log(window.start, window.average_p_per_kwh);
```

## Using it with voice

`GET /say` answers in one sentence, ready for a voice assistant to read aloud - no key, nothing to
parse. Four questions, the day-to-day ones:

| `ask` | Answers | Example |
|---|---|---|
| `price` | Whether now's a good time, and when it's cheaper | "Not really - electricity on Octopus Go in London is 30p a unit now. It drops to 8p from half past midnight tomorrow." |
| `greenest` | The greenest time | "From 1 am tomorrow - the grid in London will be at 50 grams of carbon dioxide per unit then, against 200 now." |
| `appliance` | What to do to run something for `hours` (default 2) at the cheapest time before `ready_by` (default 07:00) | "Set its delay to 7 hours - or, if the delay sets when it finishes, to 9 hours. That runs it from 1 am to 3 am tomorrow - the cheapest 2 hours before 7 am tomorrow on Octopus Agile in London, at about 5p a unit." |
| `car` | The cheapest `hours` (default 4) to charge before `ready_by` | "Charge the car from 1 am to 5 am tomorrow - the cheapest 4 hours before 7 am tomorrow on Octopus Agile in London, at 5p a unit on average." |

All take `region` and `tariff`. Mistakes are spoken too ("Renewably couldn't answer that: region
is required...").

**Siri** (iPhone, iPad, HomePod): the easiest way is a ready-made Shortcut from
[renewably.uk/voice](https://renewably.uk/voice/) - "Is now a good time to use electricity",
"When's the greenest time to use electricity", "What time should I run the appliance" and "What time
should I charge the car". Tap *Add to Siri*, answer its questions (your region and tariff, and for
the appliance and car how many hours), and ask Siri the question. Or make your own:
in the Shortcuts app, a shortcut with two actions - **Get Contents of URL** with your address, e.g.
`https://renewably.uk/api/v1/say?ask=price&region=london&tariff=agile`, then **Speak Text**. Then:

- **Name it as the question you'll ask** - Siri runs a shortcut when you say its name. If Siri
  answers the question itself instead, use a word it won't guess at, like "Renewably price"; and
  never "timer", which can start a countdown in the Clock app instead.
- **Run it once by tapping** and choose *Always Allow* when it asks to connect to renewably.uk.
- **On a HomePod**, turn on *Recognise My Voice* and *Personal Requests* in the Home app, and make
  sure *Find My* uses your iPhone as *My Location* - the HomePod runs your shortcuts through it.

**Anything else that speaks text** - an Alexa skill, Home Assistant's voice assistant, a smart
display - can fetch the same addresses.

## Design decisions

**What's exposed.** Only what the public site already shows, and only where Renewably adds something:

| Endpoint | Why it's worth an API |
|---|---|
| `prices` | Octopus publishes tomorrow's prices around 16:00; beyond that, Renewably's wind-adjusted estimates reach up to four days ahead, each half hour flagged published or estimate. |
| `cheapest-window` | The "best time to run appliances" answer for any length and deadline - the thing most people actually want from Agile prices. |
| `ev-plan` | The full EV charging planner, with the same parameters as `/ev-charging/`, so a page link and an API call describe the same plan. |
| `heat-pump`, `home-battery` | The typical-day plans behind the Heating and Battery pages. |
| `tariff-comparison` | A year's cost on each tariff for a household, as on the tariff comparison page. |
| `regions`, `tariffs` | To pick a region and a tariff. |

**What isn't, deliberately:**

- **Raw carbon intensity.** That's NESO's own free API; re-serving it adds nothing. It's used inside
  `cheapest-window` (`optimise=carbon`) and `ev-plan`, where Renewably adds the planning.
- **Anything derived from Nord Pool.** Its terms forbid republishing its data on a public site without
  written consent and say nothing to clear figures derived from it - the same reason the public pages
  don't use it. The tests fail if the public endpoints touch it.
- **Anything from the signed-in side** (the household's readings, devices, tariffs, analytics).
- **Bulk history.** Past prices are Octopus's to serve (its API has them); a plan's slots cover only
  the window it plans over.

**Same calculations as the site.** Every endpoint calls the function behind the matching page
(`dashboard/ev.py`'s `plan_charge()`, `dashboard/heatpump.py`'s `heat_pump_plan()`, and so on), so the
API and the page can't disagree. The tests compare an `ev-plan` response with the planner's own output.

**Strict where the site is forgiving.** The site quietly clamps an out-of-range battery size so the
page always shows a plan; the API refuses it with a `400` naming the parameter and its range, and
refuses unknown parameters (a typo like `?dya=tomorrow` would otherwise be silently ignored).

**Read-only and anonymous.** GET only, no API keys, no accounts - the data is public and the
endpoints are cheap enough that a per-IP rate limit is the only control needed for now.

## Conventions

- **Times** are ISO 8601 in UK local time with the offset: `2026-10-12T01:00:00+01:00` (BST) or
  `...+00:00` (GMT). Times of day in the typical-day endpoints are `HH:MM`, UK local.
- **Half hours**: each slot has `start` (inclusive) and `end` (exclusive), 30 minutes apart.
- **Units are in field names**: `_p_per_kwh` (pence per kWh, including VAT - 0% on household
  electricity since October 2026), `_pounds`, `_kwh`, `_kw`, `_pct`, `_gco2_per_kwh`, `_c` (degrees
  Celsius), `_minutes`, `_hours`.
- **Estimates are always flagged.** `estimate: true` on a price means Renewably's wind-adjusted
  estimate, not Octopus's published price; plans say `includes_estimates`.
- **`null`** means "not available" (a price neither published nor estimable, a carbon figure past the
  end of NESO's forecast) - never zero.
- **Rounding**: prices 2 decimal places, kWh 3, carbon 1, money 2.
- **Regions** are given as the Agile region letter (`C`) or the slug from the site's addresses
  (`london`), case-insensitive for letters. Responses always echo the region in full.
- **Tariffs**: `prices`, `cheapest-window`, `ev-plan`, `heat-pump` and `home-battery` take an optional
  `tariff`, a key from [`/tariffs`](#get-tariffs) (default `agile`), and echo it as a `tariff` block
  (`key`, `name`, `supplier`, `kind`). Only Agile has estimates beyond the published prices; other
  tariffs show their supplier's prices. Fixed-window and flat tariffs' times are fixed, so days the
  supplier hasn't published yet (EDF and E.ON Next publish a day at a time) repeat the latest
  published day; EDF FreePhase Dynamic goes as far as tomorrow once it's out. On fixed-window and flat tariffs the
  typical-day endpoints use today's rates, which are the same every day.
- Every response has a **`meta`** block: `api_version`, `generated_at`, and `sources` (see below).

## Caching, rate limits and CORS

**Caching.** Every 200 response carries `Cache-Control` sized to how often its data can change:

| Endpoints | `max-age` |
|---|---|
| `prices`, `cheapest-window`, `ev-plan` | 5 minutes |
| `heat-pump`, `home-battery`, `tariff-comparison` | 1 hour |
| index, `regions`, `tariffs`, `openapi.json` | 1 day |

Errors are `no-store`.

**Rate limits.** About 60 requests a minute per IP address; over it, `429` with `Retry-After`
(seconds). The whole site is also throttled to a few requests a second in bursts, answered with
`429` and `{"message": "Too Many Requests"}`. Back off and retry after the time given - the
responses' `Cache-Control` says how long an answer stays good, so caching them helps too.

**CORS.** Once public, any site may call the API from a browser: `Access-Control-Allow-Origin: *`,
GET/HEAD/OPTIONS, no credentials; `Link` and `Retry-After` are readable. The MCP server allows POST.

## Errors

Errors are JSON with the matching HTTP status:

```json
{"error": {"status": 400, "code": "invalid_parameter", "message": "`battery` must be between 5 and 200.", "parameter": "battery",
           "docs": "https://renewably.uk/api/v1/docs"}}
```

| Status | `code` | When |
|---|---|---|
| 400 | `missing_parameter` | A required parameter (`region`) is absent. |
| 400 | `invalid_parameter` | Out of range, not a number, not an allowed value, a malformed time. |
| 400 | `unknown_parameter` | A parameter the endpoint doesn't take. |
| 404 | `not_found` | No such endpoint - and every endpoint while the API isn't public. |
| 405 | `method_not_allowed` | Anything but GET, HEAD or OPTIONS. |
| 429 | `rate_limited` | Over the rate limit; see `Retry-After`. |
| 503 | `data_unavailable` | Upstream data (prices, temperatures) can't be fetched right now. |
| 500 | `internal_error` | A bug - logged. |

`parameter` is present when one parameter is to blame. Plans that can't be made for reasons of the
request itself (already at the target, no time before the deadline) are *not* errors: `ev-plan`
returns `200` with a `status` saying so.

## Data sources and licences

Each response's `meta.sources` lists what it's built from:

| Source | Used for | Licence |
|---|---|---|
| [Octopus Energy API](https://developer.octopus.energy/) | Octopus tariffs' prices | Octopus's terms |
| [EDF's public tariff API](https://api.edfgb-kraken.energy/v1/products/) | EDF tariffs' prices | EDF's terms |
| [E.ON Next's public tariff API](https://api.eonnext-kraken.energy/v1/products/) | E.ON Next tariffs' prices | E.ON Next's terms |
| [Ofgem's price cap levels](https://www.ofgem.gov.uk/energy-regulation/domestic-and-non-domestic/energy-pricing-rules/energy-price-cap/energy-price-cap-default-tariff-levels) | The comparison's baseline | Ofgem's terms |
| [NESO Carbon Intensity API](https://carbonintensity.org.uk/) | Carbon in `cheapest-window` (carbon) and `ev-plan` | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) |
| [Open-Meteo](https://open-meteo.com/) | Wind forecast (estimates), temperatures (heat pump) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) |

Each response lists only the supplier whose prices it uses. Anyone republishing the data must credit
the CC BY sources, as the site does. Renewably isn't affiliated with any of them.

## Endpoints

All are `GET`. Examples below are real responses, trimmed (`"..."` marks cut list items).

### `GET /`

Links to every endpoint and the spec.

### `GET /regions`

The 14 Agile regions (GSP groups), by name.

```json
{
  "meta": {"api_version": "1.0.0", "generated_at": "2026-10-11T22:50:42+01:00", "sources": []},
  "regions": [{"code": "A", "name": "Eastern England", "slug": "eastern-england"}, "..."]
}
```

### `GET /tariffs`

The tariffs `tariff` accepts: Octopus (Agile, Go, Intelligent Go, Cosy, Flux, Flexible), EDF
(FreePhase Dynamic and Static, GoElectric, Heat Pump Tracker, Standard Time of Use, Standard) and
E.ON Next (Drive, Drive Smart, Pumped, Flex). Each has `key`, `name`, `supplier`, `kind` (`dynamic`:
a new price every half hour; `windows`: fixed cheap and dear times; `flat`), `who_for` (null if
anyone can have it) and `note`. Product codes are looked up each day, so re-issued tariffs are
followed automatically (`manage.py check_tariffs` reports any that go missing).

```json
{"key": "go", "name": "Octopus Go", "supplier": "Octopus Energy", "kind": "windows", "who_for": "For electric car owners", "note": null}
```

### `GET /prices`

Half-hourly import prices for a region from midnight today (UK): on Agile, published or estimated;
on other tariffs, as published.

| Parameter | | Default | |
|---|---|---|---|
| `region` | required | | Letter or slug. |
| `tariff` | key | `agile` | From `/tariffs`. |
| `days` | 1-4 | 2 | Days from today: 2 is today and tomorrow. |

```json
{
  "meta": {"api_version": "1.0.0", "generated_at": "...", "sources": ["...Octopus...", "...Open-Meteo..."],
           "unit": "p/kWh, including VAT", "estimate_method": "Beyond Octopus's published prices ..."},
  "region": {"code": "C", "name": "London", "slug": "london"},
  "published_until": "2026-10-12T23:00:00+01:00",
  "slots": [
    {"start": "2026-10-11T00:00:00+01:00", "end": "2026-10-11T00:30:00+01:00", "price_p_per_kwh": 17.78, "estimate": false},
    "..."
  ]
}
```

Before Octopus publishes (around 16:00), all of tomorrow is estimated; after, only its last hour
(Octopus's day runs 23:00-23:00). The estimates are typically within about 5p/kWh of the real price;
tested against a year of prices, they're good at "which night will be cheaper".

### `GET /cheapest-window`

The cheapest (or lowest-carbon) run of `duration_hours`, starting from the next half hour and
finishing by the next `finish_by` at least that long away - what to set a delay timer to.

| Parameter | | Default | |
|---|---|---|---|
| `region` | required | | |
| `duration_hours` | 0.5-12, in halves | 3.5 | How long it runs. |
| `finish_by` | `HH:MM` | `07:00` | Local time it must finish by. |
| `optimise` | `cost`, `carbon` | `cost` | Carbon uses NESO's regional forecast (about two days). |
| `tariff` | key | `agile` | From `/tariffs`. |
| `kwh` | 0-20 | - | Energy the run uses, for `cost` (pounds in the window, and if started now). |

```json
{
  "meta": {"...": "..."},
  "region": {"code": "C", "name": "London", "slug": "london"},
  "request": {"duration_hours": 3.0, "finish_by": "2026-10-12T07:00:00+01:00", "optimise": "cost"},
  "window": {
    "start": "2026-10-12T01:00:00+01:00", "end": "2026-10-12T04:00:00+01:00",
    "starts_in_minutes": 129, "average_p_per_kwh": 25.61, "average_gco2_per_kwh": null,
    "includes_estimates": false
  },
  "if_started_now": {"average": 26.75, "unit": "p/kWh"}
}
```

`starts_in_minutes` is the delay to set (from when the response was generated). `if_started_now`
gives the same average for starting immediately, for comparison.

### `GET /ev-plan`

The cheapest or greenest half hours to charge an electric car by a ready-by time up to three days
ahead - the `/ev-charging/` planner, with **the same parameters** (so a page address and an API call
describe the same plan).

| Parameter | | Default | |
|---|---|---|---|
| `region` | required | | |
| `battery` | 5-200 kWh | 60 | Usable capacity. |
| `now` | 0-100 % | 30 | Charge now. |
| `target` | 0-100 % | 80 | Charge wanted. |
| `charger` | 1-22 kW | 7.4 | Charging power. |
| `day` | `today`, `tomorrow`, `in2`, `in3` | next | Ready-by day; default the next time `time` comes round, at least 4 hours away. |
| `time` | `HH:MM` | `07:00` | Ready-by time. |
| `mode` | `timer`, `smart` | `timer` | One unbroken block (a car's timer), or any half hours (a smart charger). |
| `efficiency` | 60-100 % | 90 | Charging efficiency (wall to battery). |
| `mpk` | 1.5-6 | 3.5 | Miles per kWh, for the per-mile figures. |
| `optimise` | `cost`, `carbon` | `cost` | |
| `tariff` | key | `agile` | From `/tariffs`, as the planner's tariff picker. |

```json
{
  "meta": {"...": "..."},
  "region": {"code": "C", "name": "London", "slug": "london"},
  "request": {"battery_kwh": 60.0, "now_pct": 30, "target_pct": 80, "charger_kw": 7.4,
              "ready_by": "2026-10-12T07:00:00+01:00", "mode": "timer", "efficiency_pct": 90,
              "miles_per_kwh": 3.5, "optimise": "cost"},
  "status": "ok",
  "notes": [],
  "next_publish": "2026-10-12T16:00:00+01:00",
  "plan": {
    "fits": true, "end_pct": 80,
    "windows": [{"start": "2026-10-12T00:00:00+01:00", "end": "2026-10-12T05:00:00+01:00", "hours": 5.0, "includes_estimates": false}],
    "energy": {"into_battery_kwh": 30.0, "from_grid_kwh": 33.333, "miles_added": 105.0},
    "cost": {"pounds": 8.63, "average_p_per_kwh": 25.9, "pence_per_mile": 8.22,
             "if_charged_now_pounds": 8.78, "saving_pounds": 0.15, "extra_cost_pounds": 0.0},
    "carbon": {"kg": 5.063, "average_gco2_per_kwh": 151.9, "if_charged_now_kg": 5.277, "saving_kg": 0.215},
    "includes_estimates": false,
    "published_until": "2026-10-12T07:00:00+01:00",
    "optimised_for": "cost"
  },
  "slots": [
    {"start": "2026-10-11T23:00:00+01:00", "end": "2026-10-11T23:30:00+01:00", "price_p_per_kwh": 28.64,
     "carbon_gco2_per_kwh": 174, "estimate": false, "charging_fraction": 0, "battery_pct": 30.0},
    "..."
  ]
}
```

- `status`: `ok`; `nothing_to_do` (already at the target); `no_prices` (no time, or no prices, before
  the ready-by time); `no_carbon` (optimising for carbon, but the forecast is unavailable). Anything
  but `ok` has `plan: null` and no slots.
- `notes`: adjustments made, e.g. a ready-by time that had passed today moved to the next one.
- `fits: false`: the target can't be reached in time - the plan charges throughout and `end_pct` says
  how far it gets.
- `slots`: every half hour from now to the ready-by time; `charging_fraction` is the share of the half
  hour spent charging (the last one usually partial), `battery_pct` the charge at its end.
- Estimated prices firm up when Octopus publishes (`next_publish`) - call again after it.

### `GET /heat-pump`

For a typical weekday and weekend day at this time of year: the cheapest time to reheat hot water
(and the cheapest ready by 07:00), and the cost of each kWh of heat by hour.

| Parameter | | |
|---|---|---|
| `region` | required | |
| `tariff` | key, default `agile` | From `/tariffs`. |

Response (trimmed): `season` (the weeks averaged), `assumptions` (the heat pump model, flow and hot
water temperatures), and per `day_type`: `hot_water.cheapest` / `hot_water.ready_by` (`start` as
`HH:MM`, `p_per_kwh_heat`), `day_average_p_per_kwh_heat`, and `heating.hours` - 24 entries of
`p_per_kwh_heat`, `price_p_per_kwh`, `cop`, `outdoor_temperature_c` - with `cheapest_hour` and
`dearest_hour`. When the ready-by time costs within 5% of the cheapest, both are the same time.

The model: a generic air-source heat pump's COP from Ruhnau, Hirth & Praktiknjo (2019) with a 0.85
field correction (2.60 at 0C, against the UK Electrification of Heat trial's median 2.57), on the
region's seasonal Agile prices and Open-Meteo temperatures. Full method: `docs/public-heat-pump-method.md`.

### `GET /home-battery`

For a typical weekday and weekend day at this time of year: the cheapest slot to fill a home battery
before the 16:00 peak, an optional afternoon top-up, and what it saves a home of a given size.

| Parameter | | Default | |
|---|---|---|---|
| `region` | required | | |
| `battery` | 2-50 kWh | 10 | Usable capacity. |
| `rate` | 0.5-15 kW | 3 | Grid charge rate. |
| `usage` | 500-30000 kWh | 2700 | The home's yearly use (2700 is average). |
| `tariff` | key | `agile` | From `/tariffs`. |

Per `day_type`: `charge` (`start`, `end`, `average_p_per_kwh`, `fills_battery`), `top_up` (or null),
`evening_average_p_per_kwh` (16:00-23:00), `saving_p_per_kwh` (after 90% round-trip losses),
`worth_it`, `battery_kwh_used_per_day` and `saving_pounds_per_day` (the battery runs the house after
its charge until it's empty, through a typical day's use for `usage`), and 24 `hours` with
`price_p_per_kwh` and `charging`. No solar, no export, and not counting the battery's wear. Full method:
`docs/public-battery-method.md`.

### `GET /tariff-comparison`

What a household would have paid over the past year on each tariff in `/tariffs`, at each tariff's
actual prices for the region. A fixed-rate tariff too new for a year's history uses today's rates
(`todays_rates: true`); a dynamic one without a year (EDF FreePhase Dynamic, since March 2026) is
left out and listed in `too_new`. EDF publishes no export tariff, so Outgoing Octopus stands in for
its export, and `export_prices` says so. The baseline is Ofgem's price cap for the region over the
same year (`price_cap`: its `annual_pounds` and today's capped unit rate and standing charge), and
each tariff's `vs_price_cap_pounds` is against it (negative is cheaper).

| Parameter | | Default | |
|---|---|---|---|
| `region` | required | | |
| `usage` | 500-30000 kWh | 2700 | Electricity a year as on the bill - with solar, what's bought. |
| `shift` | 0-50 % | 0 | Share of everyday use that can move to cheap times. |
| `ev_miles` | 0-40000 | 0 | Electric car miles a year. |
| `heat_pump` | 0 or 1 | 0 | Whether there's a heat pump. |
| `hp_kwh` | 0-20000 kWh | 0 | Its own use a year, if known (0: estimated from the total). |
| `solar` | 0-15 kWp | 0 | Solar panels. |
| `battery` | 0-40 kWh | 0 | Usable battery. |
| `mpk`, `charger` | 1.5-6, 1-22 kW | 3.5, 7 | Car efficiency and charger. |
| `battery_kw`, `round_trip` | 0.5-15 kW, 60-100 % | 3, 90 | Battery power and efficiency. |
| `confirm` | 0 or 1 | 0 | 1 to compare even if the figures look inconsistent. |

Figures that contradict each other (e.g. a car using nearly all of the bill, or a heat pump figure
that looks like a month's) get a `422` with code `inconsistent_inputs`, its message saying what
doesn't fit - the same checks as the page (`docs/public-tariff-comparison-method.md`). With
`confirm=1` the comparison runs anyway and lists them in `problems`.

`home` shows how the year's use was split (with solar, the total is worked back from the bill).
`tariffs`: those this home can have, cheapest first, then the rest. Each has `annual_pounds`
(energy plus standing charge), `vs_price_cap_pounds`, `for_this_home`, `who_for`, `prices`,
`export_prices` and the year's `import_kwh` and `export_kwh`. `cheapest` is the cheapest key this
home can have. Full method: `docs/public-tariff-comparison-method.md`.

### `GET /openapi.json`

The OpenAPI 3.1 spec, with every request parameter and response field.

## Terms of use

- **Free** for personal and non-commercial use, with no key or sign-up. For anything commercial,
  get in touch first ([hello@renewably.uk](mailto:hello@renewably.uk)).
- **Fair use.** Stay within the rate limits, cache responses for as long as their `Cache-Control`
  says, and put your app's name and a contact in your `User-Agent` - so we can reach you before
  having to block anything.
- **Credit the sources.** NESO's and Open-Meteo's data is CC BY 4.0: credit them as each response's
  `meta.sources` lists, wherever you show it. A link to renewably.uk is appreciated. The suppliers'
  prices are theirs - follow Octopus's, EDF's and E.ON Next's own terms for their data.
- **No warranty.** Prices beyond what suppliers have published are estimates; everything is a guide,
  not advice, and may be wrong. The API may change, slow down or stop, without notice.
- **Privacy.** No accounts, and no IP addresses stored. Calls are counted anonymously for 13 months,
  and not at all if the client sends Global Privacy Control or Do Not Track. More at
  [renewably.uk/privacy](https://renewably.uk/privacy/).

Questions, problems or ideas: [hello@renewably.uk](mailto:hello@renewably.uk).

## Versioning

`/api/v1/` changes **additively**: new endpoints, new optional parameters, new response fields
(clients should ignore fields they don't know). Anything that would break a client - removing or
renaming a field, changing a unit or meaning - goes in `/api/v2/`, with v1 kept running for a while.
`meta.api_version` is the spec's version (semver).
