API reference
Alarms
Alarm history and active alarms per asset and per organisation, acknowledgement and muting, and the alarm rule catalogue.
GET/v1/alarm-rules
Get alarm-rule metadata catalog
alarm.read
Required scopes: alarm.read
This endpoint takes no parameters.
curl "https://api.ebx.energy/v1/alarm-rules" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarm-rules",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarm-rules", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"catalog_version": "<string>",
"generated_at": "<string>",
"rule_count": 0,
"rules": "<string>"
}GET/v1/alarms
List alarm history for an organization
alarm.read
Required scopes: alarm.read
Get alarm history for all assets in an organization.
Returns a paginated list of alarm events across all assets belonging to the organization. Results are ordered by timestamp (most recent first).
Query Parameters:
start_time: Start of the time range (required, ISO 8601 format)end_time: End of the time range (required, ISO 8601 format)organization_id: Target organization. Optional for users that map to exactly one organization — required for EBX admins and multi-org users.severity: Filter by severity levels (optional, can specify multiple)event_type: Filter by event types (optional, can specify multiple)limit: Maximum number of alarms to return (default: 100, max: 1000)offset: Offset for pagination (default: 0)
Event Types:
alarm.opened: New alarm openedalarm.resolved: Alarm resolved (condition cleared)alarm.acknowledged: Alarm acknowledged by useralarm.muted: Alarm muted by useralarm.unmuted: Alarm unmuted by user
Performance Notes:
- Historical queries typically take 2-5 seconds
- Queries across many assets may take longer
- Use date ranges efficiently for better performance
Query parameters
- start_timeREQUIRED
- string<date-time>Start time for filtering results (ISO 8601 format)
- end_timeREQUIRED
- string<date-time>End time for filtering results (ISO 8601 format)
- organization_id
- stringTarget organization. Optional for single-org users; required for EBX admins and multi-org users.
- severity
- array[enum]Filter by severity levels (can specify multiple)
- event_type
- array[enum]Filter by event types (e.g., alarm.opened, alarm.resolved)
- limit
- integerdefault 100Maximum number of alarms to return
- offset
- integerdefault 0Offset for pagination
curl "https://api.ebx.energy/v1/alarms?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarms": [
{
"event_id": "<string>",
"event_type": "alarm.opened",
"alarm_id": "<string>",
"alarm_key": "<string>",
"asset_id": "<string>",
"rule_id": "TELEMETRY-001",
"alarm_type": "<string>",
"severity": "INFO",
"message": "<string>",
"evidence": "<string>",
"timestamp": "2026-01-01T00:00:00Z",
"opened_at": "2026-01-01T00:00:00Z",
"resolved_at": "2026-01-01T00:00:00Z",
"date_partition": "<string>",
"muted": false,
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z",
"user_id": "<string>",
"user_email": "<string>",
"notes": "<string>",
"mute_reason": "<string>",
"mute_expires_at": "2026-01-01T00:00:00Z"
}
],
"total": 0,
"limit": 0,
"offset": 0
}PUT/v1/alarms/{alarm_key}/acknowledge
Acknowledge an alarm
alarm.write
Required scopes: alarm.write
Acknowledge an alarm.
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Request Body:
notes: Optional notes about the acknowledgment
Note: The acknowledge action is asynchronous. The state update will be reflected in subsequent queries and WebSocket updates.
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
Request body · required
- notes
- stringOptional notes about the acknowledgment.
curl -X PUT "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/acknowledge" \
-H "Authorization: Bearer $EBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"notes":"<string>"}'import httpx
r = httpx.put(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/acknowledge",
headers={"Authorization": f"Bearer {token}"},
json={
"notes": "<string>",
},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/acknowledge", {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"notes":"<string>"}),
});
const data = await res.json();{
"event_id": "<string>",
"alarm_key": "<string>",
"acknowledged": true,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z"
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 422validation failed
- 429rate limited
POST/v1/alarms/{alarm_key}/declare-asset-fault
Declare an asset/EMS fault
alarm.write
Required scopes: alarm.write
Declare a clear asset/EMS fault for an open alarm.
An operator's notification is sent verbatim. The L0 agent's machine identity writes its own message, under four rules: emergency contacts only (403 not_emergency_contact), in the language each of them reads (422 language_mismatch), no links, IP addresses, domains or e-mail addresses in its subject or body, in any script (422 link_in_message), and the body goes out inside the automated-message frame (signature, operations contact, reference line, Control Center link). The checks run before anything is written.
A fault takes the asset out entirely, or — with power_limit — holds it to the power it can still deliver (a partial fault, e.g. one inverter lost).
The asset is held back with a power limit of reason fault before the response is returned: 0/0 makes it unavailable; otherwise the requested power_limit bounds apply. The window ends at notification.unavailability_end or, without a notification, unavailability_hours from now. The declaration is recorded in the audit history. When a notification is provided, delivery is requested for each selected contact's email and/or Slack channel, following the message rules above. Delivery happens asynchronously; acceptance does not confirm receipt by the contacts.
Callable by an EBX operator with alarm.write or the autonomous L0 agent (an EBX M2M integration granted alarm.write); EBX-internal because it notifies the asset's contact directory.
Notification: null declares the fault without notifying anyone. Otherwise recipient_contact_ids (at least one) must be contacts of the alarm's asset that have an email and/or Slack webhook, and unavailability_end must lie more than 30 min and at most unavailability_hours (+5 min) ahead — else 422 (detail stale_draft: draft the message again). Both are checked before anything is written.
Idempotency: when the asset is already under an active FAULT hold of the extent the request states (taken out entirely when power_limit is null, else the same bounds), already_declared=true is returned with that hold's real window and bounds and no second window is created. Notification delivery is requested again, with duplicate requests deduplicated, so delivery can be retried after a failed request. Any other extent is 409 fault_hold_differs — a full fault on a partially held asset included: a hold is never changed implicitly. A notification whose power_limit (the draft's) differs from the declaration's is 422 stale_draft.
Amend (a further loss): with amend: true the active FAULT hold is narrowed to power_limit from now on — it ends now and continues with the new bounds until its own end — and amended=true is returned with the continuation's window. Only narrowing is an amend (taking the asset out entirely narrows any partial hold): wider bounds in either direction are 409 fault_hold_widens (a recovery — lift or adjust the hold in the Availability tab), no active FAULT hold is 409 no_fault_hold, and the hold's own extent joins it (already_declared=true), so a retry is safe. A notification states the hold's deadline, as on a hand-off. The L0 agent amends like it declares. Another power limit overlapping the new window (maintenance, commercial) is 409 conflicting_limit.
Response notification_status: requested (all notification and audit requests accepted; notified lists the contacts with their channels), not_requested (no notification), or failed (at least one notification or audit request was not accepted — the fault hold still applies; retry, the declaration is idempotent).
Request Body:
alarm_id: optional lifecycle id of the reviewed alarm. If it no longer matches the asset's current alarm for this rule, the request is rejected with409so a declaration is never applied to a different incident than the one reviewed.notes: optional operator notes recorded on the unavailabilityunavailability_hours: the hold's length, 1–720 h (720 = until further notice); default 8, the L0 agent's hold. Ignored when the asset is already under a FAULT unavailability.power_limit:{upper_limit_mw, lower_limit_mw}— declare a partial fault; null takes the asset out entirelyamend: narrow the active FAULT hold topower_limit(a further loss); see Amendnotification:{language, subject, body, recipient_contact_ids, unavailability_end}ornull
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
Request body · required
- alarm_id
- stringOptimistic-concurrency token: the lifecycle id of the alarm whose evidence was reviewed. When set and it no longer matches the asset's current alarm for this rule, the request is rejected (409) so a declaration is never applied to — nor its message sent for — a different incident that re-fired in the meantime. Omitted by callers that act on current state (the L0 agent).
- notes
- stringOptional operator notes recorded on the unavailability.
- unavailability_hours
- integerdefault 8Length of the hold the operator chose (720 = until further notice); omitted, 8 h (the L0 agent's hold). Without a notification the new FAULT window ends this long from now; with one it ends at
notification.unavailability_end, which must lie within it — draft with the same length. Ignored when the asset is already under a FAULT unavailability. - power_limit
- objectDeclare a partial fault: the asset keeps running within these bounds instead of being taken out entirely (null). When the asset is already under a FAULT hold, a declaration of the same extent declares against it; any other extent is rejected with 409
fault_hold_differs— unlessamendis set. - amend
- booleandefault falseExplicitly narrow the asset's active FAULT hold to
power_limitfrom now on — a further loss on an asset already held. The hold keeps its end; the part before now stays as history. An amend only narrows: no active FAULT hold is 409no_fault_hold, bounds wider than the hold's in either direction are 409fault_hold_widens(lift or adjust the hold in the Availability tab instead), and the hold's own extent joins it like a declaration (already_declared, e.g. a retry).unavailability_hoursis ignored. Withoutamend, a hold is never changed by a declaration. - notificationREQUIRED
- objectThe reviewed notification, or null to declare without notifying anyone.
curl -X POST "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/declare-asset-fault" \
-H "Authorization: Bearer $EBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"alarm_id":"<string>","notes":"<string>","unavailability_hours":8,"power_limit":{"upper_limit_mw":0,"lower_limit_mw":0},"amend":false,"notification":{"language":"de","subject":"<string>","body":"<string>","recipient_contact_ids":[0],"unavailability_end":"2026-10-22T20:30:00Z","power_limit":{"upper_limit_mw":0,"lower_limit_mw":0}}}'import httpx
r = httpx.post(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/declare-asset-fault",
headers={"Authorization": f"Bearer {token}"},
json={
"alarm_id": "<string>",
"notes": "<string>",
"unavailability_hours": 8,
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0,
},
"amend": False,
"notification": {
"language": "de",
"subject": "<string>",
"body": "<string>",
"recipient_contact_ids": [
0,
],
"unavailability_end": "2026-10-22T20:30:00Z",
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0,
},
},
},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/declare-asset-fault", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"alarm_id":"<string>","notes":"<string>","unavailability_hours":8,"power_limit":{"upper_limit_mw":0,"lower_limit_mw":0},"amend":false,"notification":{"language":"de","subject":"<string>","body":"<string>","recipient_contact_ids":[0],"unavailability_end":"2026-10-22T20:30:00Z","power_limit":{"upper_limit_mw":0,"lower_limit_mw":0}}}),
});
const data = await res.json();{
"alarm_key": "<string>",
"asset_id": "<string>",
"declared_by": "<string>",
"unavailability_start": "2026-01-01T00:00:00Z",
"unavailability_end": "2026-01-01T00:00:00Z",
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0
},
"already_declared": false,
"amended": false,
"notification_status": "pending",
"notified": [
{
"contact_id": 0,
"name": "<string>",
"channels": [
"email"
]
}
]
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 422validation failed
- 429rate limited
GET/v1/alarms/{alarm_key}/fault-evidence
Preview the evidence for an asset-fault declaration
alarm.write
Required scopes: alarm.write
Assemble the evidence for an open alarm without taking any action (never calls an LLM).
Returns the deterministic, evidence-backed facts (asset, rule and its catalog name, sustained duration, setpoint/return values, concurrent alarms), a Control Center deep link, the FAULT unavailability the asset is already under right now (active_fault_hold, null when none — a declaration then notifies against that hold instead of creating a new window), and the asset's contacts as selectable recipients (notifiable = has an email and/or Slack webhook; is_emergency contacts are pre-selected in the Control Center).
Used by the Control Center "Declare asset fault" modal and by the L0 agent to gather context; the message itself is drafted by POST .../fault-message-draft.
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
curl "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/fault-evidence" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/fault-evidence",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/fault-evidence", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarm_key": "<string>",
"asset_id": "<string>",
"asset_name": "<string>",
"tso": "<string>",
"rule_id": "TELEMETRY-001",
"rule_name": "<string>",
"severity": "INFO",
"facts": [
"<string>"
],
"deep_link": "<string>",
"sustained_seconds": 0,
"concurrent_alarm_rule_ids": [
"<string>"
],
"active_fault_hold": {
"start_time": "2026-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0
}
},
"recipients": [
{
"contact_id": 0,
"name": "<string>",
"role": "asset_owner",
"company": "<string>",
"email": "<string>",
"phone": "<string>",
"has_slack": false,
"is_emergency": false,
"language": "de",
"notifiable": false
}
]
}POST/v1/alarms/{alarm_key}/fault-message-draft
Draft the partner message for an asset-fault declaration
alarm.write
Required scopes: alarm.write
Draft the natural-language message (subject + plain-text body) a partner receives.
The facts are computed in code into a fact sheet; the ops-inbox narrator (an LLM under strict no-invention rules) writes the message from it in the requested language; the signature and reference footer are appended. The draft is a proposal: the operator reviews and edits it in the Control Center, and the declare endpoint sends exactly the reviewed text. The L0 agent does not draft here: it writes its own message and declares with it, inside the automated-message frame (see the declare endpoint).
The draft quotes the deadline of the hold that applies: the asset's active FAULT unavailability when it is already under one (the L0 agent or an earlier declaration), else now + unavailability_hours. A hold of 30 days is "until further notice": the text names no end time. The deadline is returned as unavailability_end and must be sent back as notification.unavailability_end — with the same unavailability_hours — so the declaration is checked against exactly the instant the draft was written for. An open-ended active hold has no deadline to quote: 422 open_ended_hold (adjust it in the Availability tab first). unverified_numbers lists the numbers in the draft that do not appear in the evidence — advisory, for the operator to check before sending; nothing is rejected on that basis.
Takes tens of seconds. 503 when the narrator is not configured, 502 when it fails or returns an unusable draft — there is no fallback text; the operator writes the message.
Request Body:
language:de(default in the Control Center, formal "Sie") orenassessment: an existing hedged hypothesis to build on (the L0 judge's); null from the modalunavailability_hours: the hold's length, 1–720 h (720 = until further notice); default 8power_limit:{upper_limit_mw, lower_limit_mw}for a partial fault, so the draft states the power the asset keeps; null for a full unavailability. Under an active FAULT hold it must state that hold's extent, else409 fault_hold_differs. The draft returns it aspower_limit; send it back asnotification.power_limit.amend: draft for an explicit amend of the active FAULT hold topower_limit(a further loss) — the draft states the narrowed bounds and the hold's deadline; same rules asamendon the declaration.
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
Request body · required
- languageREQUIRED
- enum
deenLanguage of a partner fault notification (German is the default in the Control Center). - assessment
- stringAn existing hedged hypothesis (the L0 judge's) the draft builds on; null from the Control Center.
- unavailability_hours
- integerdefault 8Length of the hold the draft quotes when the asset is not already under a FAULT unavailability (ignored otherwise: the draft quotes the active hold). 720 = until further notice — the message then names no end time.
- power_limit
- objectThe power the asset may still run with under a partial fault, so the draft states it; null drafts a full unavailability. Under an active FAULT hold it must state that hold's extent, else 409
fault_hold_differs— unlessamendis set. - amend
- booleandefault falseDraft for an amend of the active FAULT hold to
power_limit(a further loss): the draft states the new bounds and the hold's deadline. Same rules asamendon the declaration.
curl -X POST "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/fault-message-draft" \
-H "Authorization: Bearer $EBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"language":"de","assessment":"<string>","unavailability_hours":8,"power_limit":{"upper_limit_mw":0,"lower_limit_mw":0},"amend":false}'import httpx
r = httpx.post(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/fault-message-draft",
headers={"Authorization": f"Bearer {token}"},
json={
"language": "de",
"assessment": "<string>",
"unavailability_hours": 8,
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0,
},
"amend": False,
},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/fault-message-draft", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"language":"de","assessment":"<string>","unavailability_hours":8,"power_limit":{"upper_limit_mw":0,"lower_limit_mw":0},"amend":false}),
});
const data = await res.json();{
"language": "de",
"subject": "<string>",
"body": "<string>",
"unavailability_end": "2026-10-22T20:30:00Z",
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0
},
"unverified_numbers": [
"<string>"
]
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 422validation failed
- 429rate limited
GET/v1/alarms/{alarm_key}/incident
Get the BetterStack incident status for an alarm
alarm.read
Required scopes: alarm.read
Return the BetterStack incident status for an alarm's current lifecycle.
Lets the Control Center show whether the on-call page is acknowledged — and whether a human or the L0 agent acknowledged it — reflecting acknowledgements made outside the portal too. Best-effort: returns found=False when the alarm has no live state, no incident was opened (not production-critical), or ops-inbox is unavailable, so the alarm view degrades instead of erroring.
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
curl "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/incident" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/incident",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/incident", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"found": false,
"incident_id": "<string>",
"status": "<string>",
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_by_agent": false,
"acknowledged_at": "<string>",
"started_at": "<string>",
"url": "<string>"
}PUT/v1/alarms/{alarm_key}/mute
Mute an alarm
alarm.write
Required scopes: alarm.write
Mute an alarm.
Muted alarms are still recorded in the event log but are filtered from notification channels (SNS, PagerDuty). WebSocket connections still receive muted alarm events.
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Request Body:
duration_minutes: Duration to mute in minutes (1–10080, defaults to 60)reason: Optional reason for muting
Note: The mute action is asynchronous.
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
Request body · required
- duration_minutes
- integerdefault 60Duration in minutes (1–10080). Defaults to one hour; every mute expires.
- reason
- stringOptional reason for muting the alarm.
curl -X PUT "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/mute" \
-H "Authorization: Bearer $EBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"duration_minutes":60,"reason":"<string>"}'import httpx
r = httpx.put(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/mute",
headers={"Authorization": f"Bearer {token}"},
json={
"duration_minutes": 60,
"reason": "<string>",
},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/mute", {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"duration_minutes":60,"reason":"<string>"}),
});
const data = await res.json();{
"event_id": "<string>",
"alarm_key": "<string>",
"muted": true,
"muted_by": "<string>",
"muted_at": "2026-01-01T00:00:00Z",
"mute_expires_at": "2026-01-01T00:00:00Z"
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 422validation failed
- 429rate limited
DELETE/v1/alarms/{alarm_key}/mute
Unmute an alarm
alarm.write
Required scopes: alarm.write
Unmute an alarm.
Removes the operator mute. Asset-unavailability suppression remains effective.
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Note: The unmute action is asynchronous.
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
curl -X DELETE "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/mute" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.delete(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/mute",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/mute", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"event_id": "<string>",
"alarm_key": "<string>",
"muted": false,
"unmuted_by": "<string>",
"unmuted_at": "2026-01-01T00:00:00Z"
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 429rate limited
GET/v1/alarms/{alarm_key}/triage-context
The structured facts an alarm decision is made from
alarm.write
Required scopes: alarm.write
What the asset can still do, and whether the cause is the asset's — read-only, computed in code (never an LLM).
For any open alarm:
- Physical signature: per direction (discharge, charge; magnitudes in MW) the nameplate, the baseline before the alarm opened, and the lowest/highest/latest available power over the assessment window (the alarm's opening, at most 30 minutes back); the usable energy (MWh) the same way, and where the SoC sits in it. The EMS's own
asset_status. proposed_power_limit: per direction the lowest available power seen in the window, at most the nameplate, floored to the kW — the bounds a partial fault would declare (or amend to). Null with a reason when a direction has no telemetry or nothing is left (a full fault).peers: the other assets of the asset's pool (same TSO and pool id — the same dispatcher, TSO link and data pipeline), each with when its telemetry last arrived and its open alarms. Healthy peers make a shared EBX cause very unlikely. Null outside any pool.asset_path: the asset's own EBX path, which no peer shares — per MRX box the Asset Adapter's link, the Pulse configuration status and when EBX last set its target.isolation: alarms opened on any other asset in the last 30 minutes (a simultaneous burst is a system signature).registry: limits created on this asset in the last 24 hours, and the asset's last registry change.history: FAULT holds and this rule's alarm lifecycles on this asset over 30 days.impact: the blocks of the next 24 hours whose commitments (wholesale + 1.25 × FCR + aFRR) exceed the proposed limit.
Used by the L0 agent before it judges an alarm. Takes seconds (it reads the fleet's alarms and the alarm history); fault-evidence stays the fast preview.
Path Parameters:
alarm_key: The alarm key in formatasset_id:rule_id
Path parameters
- alarm_keyREQUIRED
- stringAlarm key in format 'asset_id:rule_id'
curl "https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/triage-context" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/triage-context",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/DE_BESS_01:CONTROL-003/triage-context", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarm_key": "<string>",
"asset_id": "<string>",
"assessed_at": "2026-10-22T20:30:00Z",
"window_start": "2026-10-22T20:30:00Z",
"baseline_start": "2026-10-22T20:30:00Z",
"baseline_end": "2026-10-22T20:30:00Z",
"discharge": {
"nameplate_mw": 0,
"before_mw": 0,
"window_min_mw": 0,
"window_max_mw": 0,
"latest_mw": 0,
"latest_at": "2026-10-22T20:30:00Z"
},
"charge": {
"nameplate_mw": 0,
"before_mw": 0,
"window_min_mw": 0,
"window_max_mw": 0,
"latest_mw": 0,
"latest_at": "2026-10-22T20:30:00Z"
},
"energy": {
"nameplate_mwh": 0,
"before_mwh": 0,
"window_min_mwh": 0,
"window_max_mwh": 0,
"latest_mwh": 0,
"latest_at": "2026-10-22T20:30:00Z",
"soc_position": 0
},
"asset_status": 0,
"asset_status_at": "2026-10-22T20:30:00Z",
"proposed_power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0
},
"proposed_power_limit_unavailable": "no_telemetry",
"isolation": {
"window_minutes": 0,
"other_assets_alarms": [
{
"asset_id": "<string>",
"rule_id": "<string>",
"severity": "<string>",
"opened_at": "2026-10-22T20:30:00Z"
}
]
},
"peers": {
"tso": "<string>",
"tso_pool_id": 0,
"peers": [
{
"asset_id": "<string>",
"asset_name": "<string>",
"telemetry_at": "2026-10-22T20:30:00Z",
"open_alarms": [
{
"rule_id": "<string>",
"severity": "<string>",
"opened_at": "2026-10-22T20:30:00Z"
}
]
}
]
},
"asset_path": {
"readback_available": false,
"active_slot": 0,
"boxes": [
{
"slot": 0,
"device_id": "<string>",
"adapter_link": "disconnected",
"adapter_link_observed_at": "2026-10-22T20:30:00Z",
"pulse_config_status": "<string>",
"pulse_observed_at": "2026-10-22T20:30:00Z",
"target_revision": 0,
"reported_revision": 0,
"target_set_at": "2026-10-22T20:30:00Z"
}
]
},
"connectivity": {
"tunnels_available": false,
"boxes": [
{
"device_id": "<string>",
"asset_id": "<string>",
"sim_provider": "<string>",
"tunnel": "up",
"since": "2026-10-22T20:30:00Z"
}
],
"sim_providers": [
{
"sim_provider": "<string>",
"boxes": 0,
"up": 0,
"unknown": 0,
"down": [
{
"device_id": "<string>",
"asset_id": "<string>",
"sim_provider": "<string>",
"tunnel": "up",
"since": "2026-10-22T20:30:00Z"
}
]
}
]
},
"registry": {
"window_hours": 0,
"asset_updated_at": "2026-10-22T20:30:00Z",
"asset_updated_by": "<string>",
"limits_created": [
{
"kind": "power",
"start_time": "2026-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"upper_limit": 0,
"lower_limit": 0,
"reason": "maintenance",
"created_at": "2026-10-22T20:30:00Z",
"created_by": "<string>"
}
]
},
"history": {
"window_days": 0,
"fault_holds": [
{
"start_time": "2026-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"power_limit": {
"upper_limit_mw": 0,
"lower_limit_mw": 0
}
}
],
"alarm_lifecycles": [
{
"alarm_id": "<string>",
"severity": "<string>",
"opened_at": "2026-10-22T20:30:00Z",
"resolved_at": "2026-10-22T20:30:00Z"
}
],
"alarm_history_error": "<string>"
},
"impact": {
"horizon_end": "2026-10-22T20:30:00Z",
"shortfalls": [
{
"block_start": "2026-10-22T20:30:00Z",
"direction": "<string>",
"committed_mw": 0,
"limit_mw": 0
}
],
"bids_counted": false
}
}GET/v1/alarms/active
List active alarms for an organization
alarm.read
Required scopes: alarm.read
Get currently active alarms for all assets in an organization.
Returns all non-resolved alarms. This is a fast, real-time view of current alarm state across the organization.
Query Parameters:
organization_id: Target organization. Optional for users that map to exactly one organization — required for EBX admins and multi-org users.severity: Filter by severity levels (optional, can specify multiple)include_muted: Include muted alarms in the response (default: true)
Performance Notes:
- For alarm history, use
GET /alarmswith a time range
Query parameters
- organization_id
- stringTarget organization. Optional for single-org users; required for EBX admins and multi-org users.
- severity
- array[enum]Filter by severity levels (can specify multiple)
- include_muted
- booleandefault trueInclude muted alarms in the response
curl "https://api.ebx.energy/v1/alarms/active" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms/active",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/active", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarms": [
{
"alarm_key": "<string>",
"alarm_id": "<string>",
"asset_id": "<string>",
"rule_id": "TELEMETRY-001",
"alarm_type": "<string>",
"severity": "INFO",
"message": "<string>",
"status": "open",
"opened_at": "2026-01-01T00:00:00Z",
"last_updated": "2026-01-01T00:00:00Z",
"condition_active": true,
"condition_ended_at": "2026-01-01T00:00:00Z",
"muted": false,
"source_muted": false,
"mute_expires_at": "2026-01-01T00:00:00Z",
"mute_reason": "<string>",
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z",
"evidence": "<string>"
}
],
"total": 0
}GET/v1/alarms/active/fleet
List active fleet-level (pool) alarms
alarm.read
Required scopes: alarm.read
Get currently active fleet-level (pool) alarms.
Pool alarms (e.g. POOL-004, POOL-006, POOL-003) describe the aggregated fleet per TSO zone and span all customer organizations, so this endpoint is restricted to EBX admins. Their asset_id is tso-{tso_id}, identifying the TSO zone rather than an individual asset.
Query Parameters:
include_muted: Include muted alarms in the response (default: true)
Query parameters
- include_muted
- booleandefault trueInclude muted alarms in the response
curl "https://api.ebx.energy/v1/alarms/active/fleet" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms/active/fleet",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/active/fleet", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarms": [
{
"alarm_key": "<string>",
"alarm_id": "<string>",
"asset_id": "<string>",
"rule_id": "TELEMETRY-001",
"alarm_type": "<string>",
"severity": "INFO",
"message": "<string>",
"status": "open",
"opened_at": "2026-01-01T00:00:00Z",
"last_updated": "2026-01-01T00:00:00Z",
"condition_active": true,
"condition_ended_at": "2026-01-01T00:00:00Z",
"muted": false,
"source_muted": false,
"mute_expires_at": "2026-01-01T00:00:00Z",
"mute_reason": "<string>",
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z",
"evidence": "<string>"
}
],
"total": 0
}GET/v1/alarms/fleet/{tso_id}/history
List alarm history for one TSO pool
alarm.read
Required scopes: alarm.read
Get the alarm events of one TSO pool within a time range.
The history counterpart of GET /alarms/active/fleet: pool alarms live under the synthetic tso-{tso_id} partition, which has no asset-registry entry, so the asset history route cannot serve them. Restricted to EBX admins like every pool alarm. Results are ordered by timestamp (most recent first).
Path parameters
- tso_idREQUIRED
- enum
50HZTNNTAMPTNBW
Query parameters
- start_timeREQUIRED
- string<date-time>Start time for filtering results (ISO 8601 format)
- end_timeREQUIRED
- string<date-time>End time for filtering results (ISO 8601 format)
- severity
- array[enum]Filter by severity levels (can specify multiple)
- limit
- integerdefault 100Maximum number of alarms to return
- offset
- integerdefault 0Offset for pagination
curl "https://api.ebx.energy/v1/alarms/fleet/50HZ/history?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/alarms/fleet/50HZ/history?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/alarms/fleet/50HZ/history?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarms": [
{
"event_id": "<string>",
"event_type": "alarm.opened",
"alarm_id": "<string>",
"alarm_key": "<string>",
"asset_id": "<string>",
"rule_id": "TELEMETRY-001",
"alarm_type": "<string>",
"severity": "INFO",
"message": "<string>",
"evidence": "<string>",
"timestamp": "2026-01-01T00:00:00Z",
"opened_at": "2026-01-01T00:00:00Z",
"resolved_at": "2026-01-01T00:00:00Z",
"date_partition": "<string>",
"muted": false,
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z",
"user_id": "<string>",
"user_email": "<string>",
"notes": "<string>",
"mute_reason": "<string>",
"mute_expires_at": "2026-01-01T00:00:00Z"
}
],
"total": 0,
"limit": 0,
"offset": 0
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited
GET/v1/assets/{asset_id}/alarms
List alarm history for an asset
alarm.read
Required scopes: alarm.read
Get alarms for a specific asset within a time range.
Returns a paginated list of alarms that have been triggered for the specified asset. Results are ordered by timestamp (most recent first).
Query Parameters:
start_time: Start of the time range (required, ISO 8601 format)end_time: End of the time range (required, ISO 8601 format)severity: Filter by severity levels (optional, can specify multiple)limit: Maximum number of alarms to return (default: 100, max: 1000)offset: Offset for pagination (default: 0)
Alarm Severity Levels:
INFO: Informational alertsWARNING: Potential issues that may require attentionERROR: Errors that need immediate attentionCRITICAL: Critical issues requiring immediate action
Performance Notes:
- Historical queries typically take 2-5 seconds
- Use date ranges efficiently for better performance
- For real-time updates, use the WebSocket connection
Path parameters
- asset_idREQUIRED
- stringThe ID of the asset
Query parameters
- start_timeREQUIRED
- string<date-time>Start time for filtering results (ISO 8601 format)
- end_timeREQUIRED
- string<date-time>End time for filtering results (ISO 8601 format)
- severity
- array[enum]Filter by severity levels (can specify multiple)
- limit
- integerdefault 100Maximum number of alarms to return
- offset
- integerdefault 0Offset for pagination
curl "https://api.ebx.energy/v1/assets/DE_BESS_01/alarms?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/assets/DE_BESS_01/alarms?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/alarms?start_time=2026-02-03T00:00:00Z&end_time=2026-02-04T00:00:00Z", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarms": [
{
"event_id": "<string>",
"event_type": "alarm.opened",
"alarm_id": "<string>",
"alarm_key": "<string>",
"asset_id": "<string>",
"rule_id": "TELEMETRY-001",
"alarm_type": "<string>",
"severity": "INFO",
"message": "<string>",
"evidence": "<string>",
"timestamp": "2026-01-01T00:00:00Z",
"opened_at": "2026-01-01T00:00:00Z",
"resolved_at": "2026-01-01T00:00:00Z",
"date_partition": "<string>",
"muted": false,
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z",
"user_id": "<string>",
"user_email": "<string>",
"notes": "<string>",
"mute_reason": "<string>",
"mute_expires_at": "2026-01-01T00:00:00Z"
}
],
"total": 0,
"limit": 0,
"offset": 0
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited
GET/v1/assets/{asset_id}/alarms/active
List active alarms for an asset
alarm.read
Required scopes: alarm.read
Get currently active alarms for a specific asset.
Returns all non-resolved alarms. This is a fast, real-time view of current alarm state.
Query Parameters:
include_muted: Include muted alarms in the response (default: true)
Performance Notes:
- For alarm history, use
GET /assets/{asset_id}/alarmswith a time range
Path parameters
- asset_idREQUIRED
- stringThe ID of the asset
Query parameters
- include_muted
- booleandefault trueInclude muted alarms in the response
curl "https://api.ebx.energy/v1/assets/DE_BESS_01/alarms/active" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/assets/DE_BESS_01/alarms/active",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/alarms/active", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"alarms": [
{
"alarm_key": "<string>",
"alarm_id": "<string>",
"asset_id": "<string>",
"rule_id": "TELEMETRY-001",
"alarm_type": "<string>",
"severity": "INFO",
"message": "<string>",
"status": "open",
"opened_at": "2026-01-01T00:00:00Z",
"last_updated": "2026-01-01T00:00:00Z",
"condition_active": true,
"condition_ended_at": "2026-01-01T00:00:00Z",
"muted": false,
"source_muted": false,
"mute_expires_at": "2026-01-01T00:00:00Z",
"mute_reason": "<string>",
"acknowledged": false,
"acknowledged_by": "<string>",
"acknowledged_at": "2026-01-01T00:00:00Z",
"evidence": "<string>"
}
],
"total": 0
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited