# Changelog

Source: https://tenergy.me/docs/changelog
Last updated: 2026-09-26

Changes to the API contract ([`openapi.yaml`](https://tenergy.me/openapi.yaml)), newest first. Everything below is additive unless the entry says otherwise; nothing was renumbered.

## 2026-09-26 — API keys before the first deposit

- `POST /v1/api-keys` works on an `unfunded` account: sign up, create a key, read the deposit address with `GET /v1/deposit-addresses` and top up automatically. Orders still need the balance (`4001 insufficient_funds`).
- `3016 account_unfunded` is no longer returned by any route. The code stays reserved.

## 2026-09-25 — Address resources are public

- `GET /v1/resources/{address}` needs no credentials. An anonymous call runs on the per-IP budget and gets `our_active_orders: []`; a signed call is verified in full and lists that account's orders.
- The `prices.read` scope now guards `POST /v1/estimate/transfer` only.

## 2026-09-25 — The whole day-part schedule on `GET /v1/prices`

- `PriceTable` gains `schedule[]`: `id`, `label`, `start_utc_minute`, `end_utc_minute`, `factor_bps` and the `1h` energy `price_sun_per_unit` (`null` when not on sale) for every period of the day, so a price map renders the whole day from one call.
- `period` comes from the same table. Its ids and bounds changed: `drop`, `off_peak`, `ramp_9`, `ramp_11`, `ramp_12`, `peak`, `peak_late`. Iterate the schedule; never hardcode its ids or count.

## 2026-09-19 — Public price table and estimate

- `GET /v1/prices` and `GET /v1/estimate` need no credentials. Anonymous calls run on a per-IP budget; a signed call is verified in full (a bad signature is still `401`) and runs on the key's budget.
- A signed `GET /v1/estimate` is priced for its own account, so a contract customer sees the contract price; an anonymous one gets the retail price.

## 2026-09-19 — Key format, key limit, dashboard session

- **API key format** (changes what new key ids look like): `ak_live_` + 24 random bytes (base64url) for the id and `sk_live_` + 256 bits for the secret; `ak_test_` / `sk_test_` on the Nile host. The prefix names the environment. A key sent to the other environment's host is refused with the new `1012 key_environment_mismatch` before it is looked up. Keys issued earlier keep their ids and keep working.
- `label` is optional on `POST /v1/api-keys`, defaulting to `Key N`. `scopes` is unchanged: required, with no default.
- **Active-key limit.** `GET /v1/api-keys` returns `limit` and `used`; past the limit, creation answers the new `3017 api_key_limit_reached`.
- `ApiKey.last_used_ip` alongside `last_used_at`, both written at most once a minute per key.
- **Dashboard session:** `POST`, `GET` and `DELETE /v1/session` and `POST /v1/session/refresh`, authenticated by an httpOnly cookie plus `X-CSRF-Token` on writes (`1013 session_expired`, `1014 csrf_token_invalid`). A browser credential only; it carries no API-key scopes.
- `PATCH /v1/account` sets `display_name`, published on `Account` next to `label` with the same value.

## 2026-09-11 — One state machine, one signup model

- **Order states:** `created → paid → allocating → delegated → confirmed → active → expired | reclaimed`, with `failed` and `refunded` as terminal branches — ten names, the same in every enum, example and webhook.
- `Order` gains `partial` and `delivered_amount`: partial delivery is a field on a `confirmed`/`active` order, not a state.
- New webhook event `order.refunded`. `order.confirmed` carries `partial`, `delivered_amount` and `refunded_amount_sun`.
- There is no single-order cancel in v1; `3002 order_not_cancellable` belongs to `POST /v1/batches/{id}/cancel`.
- **Signup** by address challenge: `POST /v1/accounts/challenge`, `POST /v1/accounts/challenge/verify` (returns a 15-minute bootstrap token), `POST /v1/accounts`, `GET /v1/accounts/deposit-address`. `Account.status` is `unfunded | active | suspended | closed`. New codes `1010 challenge_invalid`, `1011 bootstrap_token_expired`, `3016 account_unfunded`.
- **Scopes:** one `area.action` vocabulary (`ApiKeyScope`). `scopes` is required on key creation and has no default.
- **Nile** is documented as not identical to mainnet, with the list of differences ([Environments](https://tenergy.me/docs/environments)).
- Examples use illustrative placeholder prices; live prices come from `GET /v1/prices`.

## Next steps

- [API reference](https://tenergy.me/docs/api/prices) — the contract as it stands today.
- [Environments](https://tenergy.me/docs/environments) — which host takes which key.
