Documentation
Reliability
What has happened over a window.
3 endpoints · all GET · no key · samples in curl, Python, JavaScript and Go
GET /v1/thresholds
Every rule the incident engine grades by, and why each number is that number
package main
import (
"encoding/json"
"net/http"
)
func main() {
res, err := http.Get("https://api.up24.app/v1/thresholds")
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 rules, as a document. A board that grades incidents by numbers nobody can see is asking to be trusted rather than checked, so they are published: the error rates, the latency multiple and its floor, the sample floors under unknown, the recovery hold, the flap damping, and the per-feed stall threshold for every stream in the registry.
Built from the engine’s own constants and the exchange registry rather than written out here, so a threshold cannot be published that the engine is not applying. Reads no database and takes no window: it describes the product, not a measurement, and it changes when core is deployed and not otherwise. version is that build.
Stall thresholds are registry data rather than engine constants because three seconds of silence means something different on a 100ms order book than on a trade tape that is quiet between trades. Each is read off the measured distribution of that feed’s own quiet periods.
Every change to any of these is dated at https://up24.app/changelog with its commit and what it moved.
Responses
200The rules in force on this build.429Over the origin rate limit.
200 returns ThresholdsResponse
generatedAtstring (date-time)versionstringrulesPublishedRule[]stallsStallThreshold[]
GET /v1/reliability
Every venue’s window, in one response
package main
import (
"encoding/json"
"net/http"
"net/url"
)
func main() {
q := url.Values{}
q.Set("days", "90")
q.Set("format", "json")
q.Set("category", "exchange")
res, err := http.Get("https://api.up24.app/v1/reliability?" + 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)
}
}What the leaderboard and the comparison pages read. Exists so they do not fetch twelve detail responses — each of which reads raw samples — to render one table of numbers the daily rollup already holds.
Read measuredDays beside days on every venue. A venue watched for eight days of a ninety-day window has not been 99.99% for a quarter, and every sentence up24 templates over this prints both.
Parameters
daysintegerqueryLength of the window, in days.
Defaults to 90.
formatstringqueryResponse 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 venue, the leaderboard as a file."json""csv"Defaults to "json".
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.
fleetdescribes the rows this request returned, not the whole board: a filtered leaderboard whose totals still described every venue would be a table disagreeing with its own denominator."exchange""rpc""market-data""oracle""stablecoin""bridge""ramp"
Responses
200The fleet’s window.400A query parameter is out of range or malformed.429Over the origin rate limit.
200 returns ReliabilityResponse
generatedAtstring (date-time)daysintegermin 1
fleetobjectfleet.venuesintegerfleet.uptimePctnumber | nullfleet.typicalP50Msinteger | nullfleet.typicalP95Msinteger | nullfleet.incidentsintegervenuesReliabilityVenue[]
GET /v1/detection
up24’s incidents against each venue’s own announcements
package main
import (
"encoding/json"
"net/http"
"net/url"
)
func main() {
q := url.Values{}
q.Set("days", "90")
res, err := http.Get("https://api.up24.app/v1/detection?" + 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)
}
}Time order between up24’s incidents and the venues’ own notices, per venue and pooled. Its denominators are part of the number: an exchange up24 caught nine times and that acknowledged twice is described by that ratio, not by a median over the two it admitted to. The fleet row carries no median, because medians do not pool.
Read aheadUncorrelated for what its name says and nothing more. Pairing is a symmetric ±6h window on the same venue; nothing tests that the notice describes the fault up24 measured, and every pairing carries subjectMatched: false for that reason. The field was called ahead until 28 Aug 2026, when all seventeen of the fleet’s cases were read by hand against the notice each was paired with and every one turned out to be a different subject — a REST latency spike beside a token deposit announcement, a socket drop beside a notice about delayed email. The matcher was behaving as documented; the name was making a claim the window cannot support. Component-level correlation, which is what would earn the word back, is not built.
Parameters
daysintegerqueryLength of the window, in days.
Defaults to 90.
Responses
200Lead time over the window.400A query parameter is out of range or malformed.429Over the origin rate limit.
200 returns DetectionResponse
generatedAtstring (date-time)daysintegermin 1
fleetDetectionvenuesobject[]