Quickstart.
Amanoki answers three questions about a wholesale electricity market
area, over plain HTTP. What state is it in right now — is price
moving unusually little, normally, unusually much, or is it short of
supply? How often has price gone on to cross a given level within the
next two or four hours, starting from a moment like this one? And how
likely is each of those four states one, two, or four hours from now?
JEPX (Japan) is live, with every regional area plus the system price;
the other catalogued markets — ERCOT, PJM, CAISO, AEMO, NYISO at the
time of writing — are routed and answer that they are in warmup.
GET /v1/markets is the catalog itself, and
the table on the front page renders it.
Every endpoint listed here is callable without an API key today — the service is in public preview while the billing stack is wired. Paid tiers will change the rate limit, not the shape.
The first call
This is the whole of it. No key, no signup:
curl https://api.amanoki.com/v1/power/jepx/tokyo
{"market":"jepx","area":"tokyo","currency":"JPY","price_unit":"kWh",
"regime":"low","price":20.77,"z_vol":-1.17,
"realized_vol":…,"baseline_vol":…,
"last_update_ms":1785335400000}
Read it as: for the 30-minute settlement interval starting at
last_update_ms, JEPX cleared the Tokyo area at
¥20.77/kWh, and that interval is labelled low.
z_vol is how far this interval's volatility sits from
its own recent baseline, in standard deviations — negative means
quieter than usual — and the label is derived from it. Two things
about the timestamp: it is UTC milliseconds, and it names the start
of the interval being described rather than the moment we fetched it,
so for a day-ahead market it usually sits several hours in the
future. Swap tokyo for any other JEPX area, or for
system — GET /v1/markets/jepx/areas lists
the names its URLs accept. realized_vol and
baseline_vol are the two volatility figures
z_vol is built from, carried so you can recompute the
z-score yourself.
Before an area has enough history to compute a baseline, the same
endpoint answers {"regime":"normal","warmup":true,"message":…}
— check warmup before trusting a label. That is also
what every catalogued-but-not-yet-connected market returns.
HTTP (primary surface)
Base URL is https://api.amanoki.com; everything below is
a GET under /v1. In the order they are most
useful: what exists, whether the service is healthy, the current
label, the forecast, the spike table, the recent state changes, and
which neighbouring areas tend to move first.
curl https://api.amanoki.com/v1/markets
curl https://api.amanoki.com/v1/status
curl https://api.amanoki.com/v1/power/jepx/tokyo
curl "https://api.amanoki.com/v1/power/jepx/tokyo/regime-forecast?horizon_min=120"
curl https://api.amanoki.com/v1/power/jepx/tokyo/spike-probability
curl "https://api.amanoki.com/v1/power/jepx/tokyo/transitions?limit=10"
curl "https://api.amanoki.com/v1/power/jepx/tokyo/spatial-influence?target_regime=high"
horizon_min is one of 60 / 120 / 240.
spatial-influence ranks the other JEPX areas by how
often their entry into target_regime has preceded this
area's, which is why normal is rejected there — it
carries no information to precede anything with. The fuel-cost
adjustment lives on its own endpoints under
/v1/nencho/, described on
fuel adjustment.
Python SDK
The amanoki package wraps the same endpoints, one method
per URL. Most methods return a typed dataclass you read by attribute;
regime_forecast, transition_matrix,
regime_durations, history,
spatial_influence and health return the
decoded JSON, which you read by key. Both appear in the block below
on purpose, and the SDK page marks every method.
Raw HTTP callers see the same JSON content either way.
pip install amanoki
from amanoki import Client
c = Client()
# SDK style (typed dataclass)
r = c.get_regime("jepx", "tokyo")
print(r.regime, r.price, r.price_unit, r.z_vol)
# Equivalent raw-HTTP shape (dict access)
# r = requests.get("https://api.amanoki.com/v1/power/jepx/tokyo").json()
# print(r["regime"], r["price"], r["price_unit"], r["z_vol"])
s = c.spike_probability("jepx", "tokyo")
for cell in s.cells:
print(cell.threshold, cell.horizon_min, cell.p, cell.base_rate)
# regime_forecast returns the raw dict, so read it by key
f = c.regime_forecast("jepx", "tokyo", horizon_min=120)
print(f["method"], f["probabilities"], f.get("test_brier"))
ts = c.transitions("jepx", "tokyo", limit=10)
for t in ts:
print(t.ts_ms, t.from_regime, "->", t.to_regime, "@", t.price)
Async flavour via amanoki.AsyncClient. Full reference at
/v1/reference.
Response shape (regime-forecast)
{
"market": "jepx",
"area": "tokyo",
"horizon_min": 120,
"asof_ms": 1776695400000,
"method": "weather_conditioned_mlr_v1_5",
"sample_size_bars": 36000,
"probabilities": {
"low": 0.006, "normal": 0.994, "high": 0.0, "scarcity": 0.0
},
"base_rates": {
"low": 0.075, "normal": 0.718, "high": 0.207, "scarcity": 0.0
},
"test_brier": 0.0321,
"baseline_test_brier": 0.4319,
"weather_fetched_ms": 1776604192971
}
sample_size_bars counts the 30-minute bars behind the
table for this one (market, area) — Tokyo alone in the response
above. It grows as the daily archive refresh extends the history;
a JEPX area currently carries a little over 40,000 bars, a bit
more than two years.
probabilities is the model's prediction at
t + horizon_min; base_rates is the
unconditional class frequencies from training (context for the
model's confidence). test_brier against
baseline_test_brier is the held-out calibration
comparison: 0.0321 against a 0.4319 baseline in the response
above. Both are the scores the model earned at training time on
its held-out split, not a rolling score on live outcomes. A Brier
score is the mean squared distance between the four predicted
probabilities and the outcome that occurred, so 0 is perfect and
lower is better; /stats carries the current
figures per horizon.
The two timestamps mean different things and are meant to look far
apart. asof_ms is the start of the settlement interval
the forecast is anchored on, and JEPX is a day-ahead market, so it
normally sits hours in the future. weather_fetched_ms is
wall-clock: when the weather inputs were last pulled, on an hourly
refresh. So roughly a day between them is the day-ahead offset rather
than a stale cache. When the weather cache does fall behind — more
than two hours — the response carries
weather_staleness_warning and serving continues.
Response shape (spike-probability)
{
"market": "jepx",
"area": "tokyo",
"hour_of_day_local": 23,
"is_weekend": false,
"bucket_size": 1072,
"method": "empirical_by_hour_and_weekend",
"sample_size_bars": 36000,
"cells": [
{"threshold": 30.0, "horizon_min": 120, "p": 0.0036, "base_rate": 0.014},
{"threshold": 80.0, "horizon_min": 240, "p": 0.0, "base_rate": 0.0002}
]
}
p is the empirical probability that the maximum price
over the next horizon_min minutes crosses
threshold, conditioned on the current hour-of-day
and weekend flag. bucket_size is how many historical
bars fed into that conditional estimate — cells with small
buckets (< 100) are noisy and should be discounted.
Error handling
The SDK raises rather than returns on any non-2xx response, so a caller only has to catch the cases it can act on.
import time
from amanoki import Client, NotFoundError, RateLimitError
try:
c.get_regime("jepx", "okinawa") # area not tracked
except NotFoundError:
...
except RateLimitError as e:
time.sleep(e.retry_after or 30)
All error responses ship as application/problem+json
(RFC 7807) with {type, title, status, detail, instance}.
The type URI points at the relevant section of the
methodology so you can read past the symptom to the cause.