Documentation
Incidents
Events up24 detected, and their permalinks.
2 endpoints · all GET · no key · samples in curl, Python, JavaScript and Go
GET /v1/incidents
Incident history
package main
import (
"encoding/json"
"net/http"
"net/url"
)
func main() {
q := url.Values{}
q.Set("venue", "binance")
q.Set("category", "exchange")
q.Set("status", "all")
q.Set("days", "90")
q.Set("limit", "50")
q.Set("format", "json")
res, err := http.Get("https://api.up24.app/v1/incidents?" + q.Encode())
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
}Newest first. Page with before, not with an offset: the list is ordered by startedAt descending and new incidents are inserted at the front, so an offset would skip a row every time one opened mid-walk. Pass the startedAt of the last incident you received to get the next page; a short page means you have reached the end.
The 200-row ceiling is real and one venue having a bad week exceeds it on its own, so a caller that wants the whole history has to walk rather than raise limit.
Parameters
venuestringqueryRestrict to one exchange. Omit for the whole fleet.
"binance""bybit""coinbase""kraken""okx""bitfinex""deribit""bitstamp""gemini""cryptocom""hyperliquid""upbit""binance-futures""bybit-futures""okx-futures"categorystringqueryOnly rows of this category. Absent means every category, which is what an unparameterised request has always meant and still means. A category up24 does not measure is not an error: it answers with an empty list rather than a 404, because "nothing here yet" and "no such thing" are different answers and a caller polling for a category up24 is about to add should not have to tell them apart from a status code. Resolved from each incident’s
venuethrough the registry; the incident row itself carries no category, because it is the same object the alert webhook delivers and a new field there is a change to somebody else’s parser."exchange""rpc""market-data""oracle""stablecoin""bridge""ramp"statusstringqueryOpen incidents, closed ones, or both.
"open""closed""all"Defaults to "all".
daysintegerqueryHow far back to look.
Defaults to 90.
limitintegerqueryRows per page.
Defaults to 50.
beforestring (date-time)queryCursor: only incidents that started strictly before this instant. ISO 8601 UTC.
beforeIdintegerqueryformatstringqueryResponse shape.
jsonis the default and is the whole of it;csvis a flat projection of the same object, withcontent-dispositionset so a browser saves it. Every CSV row carriesgeneratedAt, the window, the measured part of the window andmethodologyUrlas columns rather than as a header comment — this is the output that ends up in a spreadsheet and gets quoted from a slide six months later, and a caveat a parser strips is a caveat that will not be there when it is needed. Here: one row per incident, with the venue’s own notice beside it where there is one — includingsubjectMatched, which isfalseon every pairing."json""csv"Defaults to "json".
Responses
200One page of incidents.400A query parameter is out of range or malformed.429Over the origin rate limit.
200 returns IncidentsResponse
generatedAtstring (date-time)incidentsIncident[]
GET /v1/incidents/{slug}
One incident: timeline, error breakdown, latency around the event
package main
import (
"encoding/json"
"net/http"
)
func main() {
res, err := http.Get("https://api.up24.app/v1/incidents/binance-2026-10-01-ws")
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
}The API behind the permalink. The timeline is built from the incident’s own columns rather than from alert history, which is expired at ninety days — a page that lost half its timeline the night the sweep ran would stop being evidence exactly when the incident became history.
A stream incident charts the venue’s REST latency over the same window, labelled as such: a feed either delivers or it does not, so it has no latency of its own, and the question a reader actually has is whether the rest of the exchange was fine.
Cached 5s while the incident is open and 300s once it has closed — a closed incident is history and stops changing.
Parameters
slugstringpathrequiredThe permalink id —
{venue}-{YYYY-MM-DD}-{rest|ws}-{degraded|outage}, with-2,-3for a second incident of the same shape on the same day.
Responses
200The incident, in full.404No incident has that slug, or its venue is no longer published.429Over the origin rate limit.
200 returns IncidentDetailResponse
generatedAtstring (date-time)incidentIncidentvenueobjectvenue.idstring"binance""bybit""coinbase""kraken""okx""bitfinex""deribit""bitstamp""gemini""cryptocom""hyperliquid""upbit""binance-futures""bybit-futures""okx-futures"venue.namestringvenue.symbolstring | nullThe instrument up24 probes, in this venue's own notation —
XBTUSDon Kraken,tBTCUSDon Bitfinex — not a normalised one, so it matches what you would type into that venue’s own API. Null on any category that trades no instrument, which today is everyrpcrow.venue.quotestring | nullThe currency the symbol is priced in. Venues differ, so do not compare prices across them naively. Null on any category that trades no instrument, for the same reason
symbolis.componentobjectcomponent.idstringOne REST endpoint or one WebSocket stream of one venue —
rest:ticker,ws:book.ws:connis the shared socket itself, which is where a connection-scoped fault is recorded.matches ^(rest|ws):[a-z]+$ · e.g. "rest:ticker"
component.kindstring"rest""ws"component.labelstringcomponent.targetstring | nulleventsIncidentEvent[]errorsErrorBreakdownlatencyIncidentLatency | nulldetectionDetectionofficialobjectofficial.sourcestring | nullofficial.pagestring | nullofficial.observedbooleanrelatedIncident[]