What the service returns, how to verify it, and what to do when a check fails.
Two commands, no key, no sign-up. This fetches a real signed record for a real GB 400 kV circuit from the same engine and the same signing key a customer gets, and verifies the Ed25519 signature on your machine.
curl -O https://lineclarity.com/static/verify_lineclarity.py python verify_lineclarity.py
It prints the amps you should use, the signature result and the reason. Point it at your own account later with --key and --line; the code is the same either way, and it is about 150 lines you can read in full before you run it.
curl -H "X-API-Key: $LC_KEY" https://lineclarity.com/api/v1/whoami curl -H "X-API-Key: $LC_KEY" https://lineclarity.com/api/v1/lines curl -H "X-API-Key: $LC_KEY" "https://lineclarity.com/api/v1/rating?line=PENW-400-01" curl -O https://lineclarity.com/static/verify_lineclarity.py python verify_lineclarity.py --key $LC_KEY --line PENW-400-01
The last command prints the amps you should use and why. If it says source: static, a check failed and your system should be doing the same thing.
| Endpoint | Purpose |
|---|---|
| GET /api/v1/whoami | Confirm the key, list its scopes and lines |
| GET /api/v1/lines | Your registered circuits |
| GET /api/v1/rating?line=&format=&hours=&detail= | The live rating |
| GET /api/v1/planning?line=&year= | Replay 12 months of recorded weather — a look back, no OT link |
| GET /api/v1/points?line= | Stable point/tag map for commissioning |
| GET /api/v1/audit | Every value we served you |
| GET /api/v1/pubkey | The Ed25519 public key. Fetch once, pin it. |
{
"schema": "lineclarity.rating.v1",
"line": { "line_id": "PENW-400-01", "line_uid": "ln_…", "ext_ref": "NGET.PENW.400.CKT1",
"conductor": "Zebra ACSR (quad)", "kv": 400, "lat": 53.75, "lon": -2.72, "t_max_c": 75, "pf": 1 },
"issued_at": "2026-09-05T18:04:00Z",
"valid_until": "2026-09-05T18:19:00Z",
"ttl_s": 900,
"sequence": 1417, // monotonic per line
"prev_digest": "sha256:…", // chains to the previous record
"status": "OK", // OK | DEGRADED | FAILSAFE
"rating_a": 1264.3, // the number you operate to
"rating_mw": 875.9,
"static_a": 1133.8, // the number you fall back to
"static_basis": "gb_seasonal", // gb_seasonal | worst_case | customer
"sub_conductors": 4, // bundle: rated per sub-conductor, multiplied
"below_static": false, // true when the weather is worse than your static assumes
"route": { "basis": "route", "spans_modelled": 5, "length_km": 32.0, "binding_span": 0 },
"conservatism": { "precip_probability_pct": 40, "rain_expected": false, ... },
"hard_cap_a": 1400,
"cap_applied": false,
"operating_horizon_h": 6, // operate on rows before this; later rows are advisory=true
"horizon": [ { "t": "…", "rating_a": 1264.3, "rating_mw": 875.9, "aar_a": 1180.0, "advisory": false }, … ],
"summary": { "avg_gain_pct": 11.5, "avg_extra_mw": 90.4 },
"failsafe": { "static_a": 1133.8, "max_age_s": 1800, "on_expiry": "revert_to_static" },
"signature": { "alg": "Ed25519", "key_id": "k1_…", "digest": "sha256:…", "value": "base64…" }
}
Four checks on every pull. Any failure → use static_a.
Reference implementations: Python · JavaScript. Both expose one rating_to_use() returning the amps and the reason. Set hard_cap_a per line in the portal — we enforce it server-side, you enforce it again. Alarm on repeated static fallbacks, sequence gaps, or any status other than OK.
Add &format= to any rating URL.
| Format | Kind | What you get |
|---|---|---|
| json | native | The signed record. The signature is defined over this form. |
| csv | native | One row per hour, for historians and studies. |
| pi | native | AVEVA/OSIsoft PI Web API streamset body. |
| cim | native | IEC 61970 RDF/XML: OperationalLimitSet + CurrentLimit. |
| dnp3 | export | Point map + values for your outstation. |
| iccp | export | TASE.2 bilateral point list + values for your gateway. |
| modbus | export | Holding-register map, scaled uint16 big-endian. |
| points | config | Tag/index map, no values. Indices never move for a line. |
Non-JSON responses carry the signature in X-LineClarity-* headers; verification is defined over the JSON form, so verify there and transform locally if you need assurance downstream. Tag names derive from your ext_ref. Quality maps OK → Good / ONLINE / valid, DEGRADED → Questionable / COMM_LOST / suspect, FAILSAFE → Bad / LOCAL_FORCED / held.
X-API-Key: lc_live_…
One key per consuming system so you can revoke one without disturbing the rest; we store only a hash. Scopes: read:ratings, read:planning. Optional per key: a source-IP allowlist, and HMAC request signing with X-LC-Timestamp (±300s), a single-use X-LC-Nonce, and X-LC-Signature = base64 HMAC-SHA256 over method, path, timestamp, nonce and the SHA-256 of the body, newline separated.
The portal has no passwords: we send a one-time enrolment code, your browser generates a non-extractable P-256 key, and signing in means signing a single-use challenge. We store public keys only. Owners invite people and revoke devices; anyone signed in can mint a code for their own second machine, inheriting their role and circuits, and can sign their own device out.
Roles, per-person circuits, access end dates and key scoping are set in the portal and described on the security page. Two things that matter to an integrator:
| Code | Meaning |
|---|---|
| 401 | Missing/invalid key or expired session. Repeated failures back off exponentially. |
| 403 | Missing scope, source IP not allowed, or account suspended. |
| 404 | Unknown line for this account. Line IDs are per-account. |
| 429 | Rate limited — back off. The feed allows 240 requests/minute per key. |
| 502/503 | Engine unavailable. Use static and retry. We never return an unsigned record. |
Poll every 5–15 minutes per line; ttl_s states the intended interval, and polling faster returns the same record. Every feed response is Cache-Control: no-store.
The operational feed is a forecast, so it inherits forecast error. Measured on four GB nodes:
| Input wrong by | Rating moves |
|---|---|
| wind direction ±30° | 5.3% |
| wind speed −30% | 3.2% |
| ambient +3 °C | 1.2% |
Wind direction is the input the rating is most sensitive to, not temperature. Our forecast inputs differ from the ERA5 reanalysis by about 5% on the rating on average and 20% at the 95th percentile. The hard cap is enforced on both sides, the fail-safe is unconditional, and anything past operating_horizon_h is marked advisory.
A planning study is not a forecast. It replays a year of archived, observed weather and asks what you would have had if the circuit had been rated dynamically through that year. Nothing is predicted. The only inductive step is assuming the next year resembles the last, and that is tested separately: across 41 GB site-years a node moved a median of 2.0 points between years and never showed no uplift.
curl -H "X-API-Key: $LC_KEY" \ "https://lineclarity.com/api/v1/planning?line=PENW-400-01&year=2024&congestion_hours=1000&congestion_price_mwh=60"
A year of archived weather: average uplift, p5/p50 ampacity, hours above static, a monthly shape, and a constraint value. No operational connection, so nothing to review on the OT side — the usual starting point.
The money figure is arithmetic on assumptions you supply: extra MW × binding hours × price/MWh, in the currency you name. The "extra MW" is taken from the hours the circuit is actually constrained — the windiest decile for a GB circuit, the hottest decile elsewhere. assumptions.basis in the record says which was used. It is published as congestion_value_upper_bound_yr because it assumes every extra MW relieves the constraint for every binding hour, which no single circuit does — scale it by the share of the constraint your circuit actually carries, or use value_per_extra_mw_yr and supply your own MW. The capacity numbers are physics and stand on their own; the money number does not until someone runs a power flow.
Missing something you need? Ask.