Power cut data, ready to use
Bring UK power cut information into your own apps, dashboards and services,
without building a separate connection to every electricity network operator.
Programmatic access to the same live incident data behind the map — cursor
pagination, ISO 8601 timestamps, and errors as
RFC 9457 problem+json. Base
URL: https://api.outages.uk.
Get an API key
Free while the API is in early access. One active key per email address -- registering again revokes whatever was issued before and replaces it, since there's no recovery for a lost key: only its hash is ever stored.
Authentication
Send your key as a bearer token on every request: Authorization: Bearer
<key>. Keys are rate-limited to 10 requests/minute; a request over
that limit gets a 429 problem+json response.
How much data you get
Standard keys see a rolling 12-hour window: an incident older than that (by when
it started, regardless of whether it's still ongoing) drops out of
/v1/incidents and /v1/postcode-check entirely. A
restored incident stays visible for 60 minutes after resolution — long enough to
see that something was just fixed — then it drops out too.
Full lifecycle history beyond that window is a premium feature
(GET /v1/history), not available to standard keys yet. Get in touch
if continuous history is something you need.
Coverage depends on what each operator publishes, not just what they know about internally — for example Electricity North West's public dataset only includes incidents affecting 5 or more customers, so a handful of genuinely live, smaller incidents on their own website won't appear here. We surface every incident each operator's feed gives us; we can't add what isn't published there.
Endpoints
GET /v1/incidents
Current incidents across every operator, cursor-paginated, plus a freshness envelope showing which operators were stale as of that request.
| Param | Type | Notes |
|---|---|---|
limit | integer, optional | 1–100, default 50 |
cursor | string, optional | from a previous response's nextCursor |
curl -H "Authorization: Bearer <key>" \ "https://api.outages.uk/v1/incidents?limit=50"
Response (trimmed to one incident):
{
"generatedAt": "2026-09-08T12:00:00.000Z",
"operators": [
{ "operator": "ukpn", "name": "UK Power Networks", "stale": false,
"lastSuccessAt": "2026-09-08T11:58:12.000Z", "lastAttemptAt": "2026-09-08T11:58:12.000Z" }
],
"data": [
{
"id": "ukpn:INCD-105479-V",
"operator": { "id": "ukpn", "name": "UK Power Networks",
"attribution": "Contains data from UK Power Networks, licensed under CC BY 4.0" },
"status": "active",
"cause": "unplanned",
"location": {
"postcodeUnits": ["N1 9GU"], "postcodeSectors": ["N1 9"],
"point": { "lon": -0.1, "lat": 51.5 },
"pointMeaning": "affected-area-centroid", "areaName": "Islington Zone"
},
"customersAffected": 42,
"estimatedRestoration": {
"claim": { "kind": "point", "at": "2026-09-08T14:00:00.000Z" },
"displayText": "08 Sept 14:00 - 15:00"
},
"fetchedAt": "2026-09-08T11:58:12.000Z"
}
],
"nextCursor": "ukpn:INCD-105480-V"
}
GET /v1/postcode-check
Whether a postcode or bare sector is currently affected.
| Param | Type | Notes |
|---|---|---|
postcode | string, required | a full unit (N1 9GU) or a sector (N1 9) |
curl -H "Authorization: Bearer <key>" \ "https://api.outages.uk/v1/postcode-check?postcode=N1%209GU"
GET /v1/status
Per-operator freshness on its own, uncached — the source of truth the other two endpoints' envelopes refer to.
curl -H "Authorization: Bearer <key>" \ "https://api.outages.uk/v1/status"
GET /v1/history Premium
Same shape and parameters as GET /v1/incidents, but without the
12-hour window. Requires a premium key; a standard key gets
403 Forbidden.
| Param | Type | Notes |
|---|---|---|
limit | integer, optional | 1–100, default 50 |
cursor | string, optional | from a previous response's nextCursor |
curl -H "Authorization: Bearer <premium key>" \ "https://api.outages.uk/v1/history?limit=50"
Premium: asset monitoring & alerting Premium
Register the sites or meters you care about ("assets"), then get a webhook or email the moment an outage starts affecting one — the same feature that powers console.outages.uk's dashboard, exposed as a full CRUD API for your own integrations (an MCP server, a Zapier-style automation, your own internal tooling). Nothing here is self-serve yet: a workspace API key, not a free-tier key, is required for every route below — generate one from your console.outages.uk dashboard (Settings → API keys). A workspace key belongs to exactly one workspace, so none of these routes take a workspace id — it's implicit in the key. Not a customer yet? See what this looks like and get in touch.
Assets
| Method & path | Notes |
|---|---|
GET /v1/workspace/assets | list, plus your workspace's total allowance |
GET /v1/workspace/assets/:id | |
POST /v1/workspace/assets | 400 once you're at your allowance — buy a block or free up room first |
PATCH /v1/workspace/assets/:id | |
DELETE /v1/workspace/assets/:id | |
POST /v1/workspace/assets/csv | bulk-create; same validation and allowance check, per row |
curl -X POST -H "Authorization: Bearer <workspace key>" -H "Content-Type: application/json" \
-d '{"name":"Head office","postcode":"N1 9GU"}' \
"https://api.outages.uk/v1/workspace/assets"
Response:
{
"id": "3fa2b1c0-...",
"workspaceId": 42,
"name": "Head office",
"externalAssetId": null,
"assetTypeId": null,
"mpan": null,
"postcodeUnit": "N1 9GU",
"postcodeSector": "N1 9",
"dnoOperatorId": null,
"idno": null,
"createdAt": "2026-09-21T09:00:00.000Z",
"updatedAt": "2026-09-21T09:00:00.000Z"
}
postcode (a full unit or a bare sector) is the only required
field beyond name. Set mpan (Meter Point
Administration Number) and dnoOperatorId is derived from it
automatically — it's not independently editable.
Asset types
Optional free-text grouping (e.g. "Retail site", "Warehouse") for your own reporting — not required to create an asset.
| Method & path | Notes |
|---|---|
GET /v1/workspace/asset-types | |
POST /v1/workspace/asset-types | { "name": "..." } |
PATCH /v1/workspace/asset-types/:id | rename |
DELETE /v1/workspace/asset-types/:id |
Alert groups
A named set of assets, so one alert rule can watch many assets at once instead of listing each individually.
| Method & path | Notes |
|---|---|
GET /v1/workspace/alert-groups | |
POST /v1/workspace/alert-groups | { "name": "..." } |
PATCH /v1/workspace/alert-groups/:id | rename |
DELETE /v1/workspace/alert-groups/:id | |
GET /v1/workspace/alert-groups/:id/members | full asset objects, not just ids |
POST /v1/workspace/alert-groups/:id/members | { "assetId": "<uuid>" } |
DELETE /v1/workspace/alert-groups/:id/members/:assetId |
Alerts
A rule: watch one or more assets/groups, notify a webhook and/or a list of email addresses when a watched asset's incident status changes.
| Method & path | Notes |
|---|---|
GET /v1/workspace/alerts | |
GET /v1/workspace/alerts/:id | |
POST /v1/workspace/alerts | |
PATCH /v1/workspace/alerts/:id | regenerates webhookSecret every time, even if the URL didn't change |
DELETE /v1/workspace/alerts/:id | |
GET /v1/workspace/alerts/:id/deliveries | recent delivery attempts, ?limit= up to 200 |
curl -X POST -H "Authorization: Bearer <workspace key>" -H "Content-Type: application/json" \
-d '{
"name": "Head office outages",
"targetAssetIds": ["3fa2b1c0-..."],
"webhookUrl": "https://example.com/hooks/outages",
"events": ["incident.new", "incident.restored"]
}' \
"https://api.outages.uk/v1/workspace/alerts"
At least one of targetAssetGroupIds/targetAssetIds
is required, and at least one of webhookUrl/
emailRecipients. events defaults to all three
(incident.new, incident.updated,
incident.restored) if omitted.
Verifying webhook deliveries
Every delivery is a signed POST with an
x-outages-signature: sha256=<hex> header — an HMAC-SHA256 of
the exact raw request body, keyed with the alert's own
webhookSecret (shown on the alert's own GET/
POST/PATCH response; store it, it's not shown
again after that). Verify against the raw bytes received, not a
re-serialised copy — key order or whitespace differences would break the
comparison.
const crypto = require('node:crypto');
function isValid(secret, rawBody, header) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return header === 'sha256=' + expected;
}
Payload shape:
{
"event": "incident.new",
"sentAt": "2026-09-21T09:05:00.000Z",
"alert": { "id": 7, "name": "Head office outages" },
"asset": { "id": "3fa2b1c0-...", "name": "Head office", "externalAssetId": null },
"incident": {
"id": "ukpn:INCD-105479-V",
"operator": "ukpn",
"status": "active",
"cause": "unplanned",
"postcodeSector": "N1 9",
"startedAt": "2026-09-21T08:50:00.000Z",
"restoredAt": null,
"customersAffected": 42
}
}
Errors
Every error is application/problem+json: type, title, status, detail.
| Status | Meaning |
|---|---|
400 | a request parameter is missing or malformed — detail says which |
401 | missing, invalid, or revoked API key |
403 | valid key, but the endpoint needs a premium one |
404 | no such route |
429 | rate limit exceeded — per-key on /v1 routes, per-address on POST /v1/keys |
Data rights
Most operators publish under CC BY 4.0 — attribute the originating operator
wherever you display their data (each incident carries an
operator.attribution string; null means that operator's
licence isn't cleared for redistribution yet, and its incidents should not be
shown to your own users).
Full machine-readable contract: openapi.yaml.