Documentation
Quickstart
Paging incident history, and reading uptime without dropping its denominator. Both are written out in full because both are places a caller gets a plausible wrong answer rather than an error.
Walking the history
limit stops at 200 and one venue having a bad week exceeds that on its own, so the whole history is a walk. The cursor is before — a timestamp, not an offset, because new incidents are inserted at the front of the ordering and an offset would skip a row every time one opened mid-walk.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
)
const base = "https://api.up24.app"
type incident struct {
Slug string `json:"slug"`
Severity string `json:"severity"`
DurationMs int64 `json:"durationMs"`
StartedAt string `json:"startedAt"`
ID int64 `json:"id"`
}
// Walks the cursor rather than raising limit, which stops at 200.
func incidents(venue string, days int) ([]incident, error) {
var all []incident
before, beforeID := "", int64(0)
for {
q := url.Values{}
q.Set("venue", venue)
q.Set("days", strconv.Itoa(days))
q.Set("limit", "200")
if before != "" {
q.Set("before", before)
q.Set("beforeId", strconv.FormatInt(beforeID, 10))
}
res, err := http.Get(base + "/v1/incidents?" + q.Encode())
if err != nil {
return nil, err
}
// The one status worth handling: the API says when to come back.
if res.StatusCode == http.StatusTooManyRequests {
wait, _ := strconv.Atoi(res.Header.Get("retry-after"))
res.Body.Close()
time.Sleep(time.Duration(max(wait, 5)) * time.Second)
continue
}
var body struct {
Incidents []incident `json:"incidents"`
}
err = json.NewDecoder(res.Body).Decode(&body)
res.Body.Close()
if err != nil {
return nil, err
}
all = append(all, body.Incidents...)
// A short page is the end. The cursor is the last row's start and id,
// never an offset: new incidents are inserted at the front, and
// incidents opened in one tick share a start.
if len(body.Incidents) < 200 {
return all, nil
}
last := body.Incidents[len(body.Incidents)-1]
before, beforeID = last.StartedAt, last.ID
}
}
func main() {
all, err := incidents("binance", 90)
if err != nil {
panic(err)
}
for _, i := range all {
fmt.Println(i.Slug, i.Severity, i.DurationMs)
}
}The one status worth handling is 429: the API says when to come back in retry-after, and that header is the whole of the backoff you need. Nothing else here is retryable in a way you can guess at.
Quoting uptime honestly
Every number here is a measurement with a denominator attached. “99.98% uptime” without the check count or the window is not a claim up24 makes anywhere on this site, and anything named typical is a median of daily percentiles rather than the window's own — percentiles do not compose.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
res, err := http.Get("https://api.up24.app/v1/reliability?days=90")
if err != nil {
panic(err)
}
defer res.Body.Close()
var body struct {
Venues []struct {
Name string `json:"name"`
Summary struct {
UptimePct *float64 `json:"uptimePct"`
Checks int64 `json:"checks"`
MeasuredDays int `json:"measuredDays"`
Days int `json:"days"`
TypicalP95Ms *int `json:"typicalP95Ms"`
} `json:"summary"`
} `json:"venues"`
}
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
panic(err)
}
for _, v := range body.Venues {
s := v.Summary
// measuredDays beside days on purpose: a venue watched for eight days
// of ninety has not been 99.99% for a quarter.
fmt.Printf("%s: %.2f%% of %d checks over %d/%d days\n",
v.Name, *s.UptimePct, s.Checks, s.MeasuredDays, s.Days)
}
}measuredDays is printed beside days on purpose: a venue watched for eight days of ninety has not been 99.99% for a quarter, and a dashboard that drops the first number is publishing a claim the data does not support.