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)
versionstring

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

daysintegerquery

Length of the window, in days.

Defaults to 90.

formatstringquery

Response shape. json is the default and is the whole of it; csv is a flat projection of the same object, with content-disposition set so a browser saves it. Every CSV row carries generatedAt, the window, the measured part of the window and methodologyUrl as 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".

categorystringquery

Only 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. fleet describes 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)
daysinteger

min 1

fleetobject
fleet.venuesinteger
fleet.uptimePctnumber | null
fleet.typicalP50Msinteger | null
fleet.typicalP95Msinteger | null
fleet.incidentsinteger

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

daysintegerquery

Length 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)
daysinteger

min 1

venuesobject[]