API documentation

Manage monitors and read their status over HTTP. Everything the dashboard does to a monitor, an API key can do too — subject to the same plan limits.

Base URL https://dev-api.uptimecraft.com

Authentication

Every request carries an API key as a bearer token. Create one under Settings → API keys.

Authorization: Bearer uc_live_xxxxxxxxxxxxxxxxxxxxxxxx
  • The key is shown once, when you create it. We store only a hash, so it cannot be recovered — if you lose it, revoke it and make another.
  • Keys always expire, up to 365 days.
  • A missing or invalid key answers 401 with a JSON body. It never redirects to a sign-in page.

Permissions

Each key holds a set of permissions. You can change them later without issuing a new key.

ScopeAllows
monitors:read Read monitors and their status
monitors:write Create, edit, pause and delete monitors
incidents:read Read incidents and their timelines
status-pages:read Read status pages

Rate limits

Two limits apply, and they do different jobs. The per-minute limit stops a runaway loop. The daily cap is the volume budget, and it is usually the one that binds. Both are counted per team, so extra keys do not buy extra requests.

Plan Access Keys Per minute Per day
Free Read only 1 60 1,000
Starter Read and write 3 120 15,000
Pro Read and write 10 600 75,000
Business Read and write 25 1,800 300,000
On Free, polling each monitor separately every five minutes would need about 5,760 requests a day, which is more than the 1,000 allowed. Use list monitors instead: one request returns them all.

Headers on every response

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1789045260
X-RateLimit-Daily-Limit: 75000
X-RateLimit-Daily-Remaining: 74904
X-RateLimit-Daily-Reset: 1789084800

Over a limit, the answer is 429 with a Retry-After header in seconds. A rejected request does not count against your budget, so honouring Retry-After always gets you back in. Every example below handles this. Daily counters reset at 00:00 UTC. Being over the API limit never affects your monitoring: checks, alerts and status pages carry on as normal.

Errors

Every error uses the same shape. Branch on code.

{
  "error": {
    "code": "missing_scope",
    "message": "This key does not have the monitors:write permission.",
    "field": null
  }
}
CodeHTTPMeaning
unauthorized 401 No key was sent, or it is unknown, revoked or expired.
missing_scope 403 The key is valid but lacks the permission this call needs.
plan_read_only 403 Your plan's API access is read-only.
plan_forbidden 403 Your plan does not include API access.
not_found 404 No such resource, or it belongs to another team.
invalid_request 400 The request was rejected; the message says why.
invalid_field 400 A field failed validation; 'field' names it.
invalid_body 400 The body could not be read as JSON.
invalid_parameter 400 A query parameter was not a valid value.
rate_limit_exceeded 429 Too many requests this minute. Honour Retry-After.
daily_limit_exceeded 429 The day's allowance is spent. Resets at 00:00 UTC.
cooldown 429 This monitor was checked moments ago.
internal_error 500 Something went wrong on our side.

A resource belonging to another team answers 404, not 403 — we do not confirm that an id exists to someone who cannot see it.

Versioning

/api/v1 only ever gains things. New fields may appear in responses and new optional parameters may be accepted, so parse leniently and ignore what you do not recognise. Anything that would break an existing integration goes to /api/v2 instead.

Endpoints

GET /api/v1/monitors

List monitors

A page of your team's monitors. This is the cheapest way to watch everything you run: one request covers every monitor, where asking for each one separately costs a request each.

Requires monitors:read

ParameterTypeNotes
page integer Zero-based page number. Defaults to 0.
per_page integer Monitors per page, up to 200. Defaults to 50.
type string Filter by monitor type, e.g. HTTP.
status string Filter by current status, e.g. UP or DOWN.
curl -X GET \
  https://dev-api.uptimecraft.com/api/v1/monitors \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.get(f"{BASE}/api/v1/monitors", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("GET", "https://dev-api.uptimecraft.com/api/v1/monitors", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("GET", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{
  "data": [
    {
      "id": "3f1a...",
      "name": "Homepage",
      "type": "HTTP",
      "url": "https://example.com",
      "intervalSeconds": 300,
      "paused": false,
      "status": "UP",
      "lastCheckedAt": "2026-09-06T09:31:04Z"
    }
  ],
  "page": { "number": 0, "size": 50, "totalElements": 1, "totalPages": 1, "hasMore": false }
}
POST /api/v1/monitors

Create a monitor

Creates a monitor and starts checking it. Your plan's monitor allowance and minimum check interval apply here exactly as they do in the dashboard.

Requires monitors:write · paid plans only

curl -X POST \
  https://dev-api.uptimecraft.com/api/v1/monitors \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Homepage", "type": "HTTP", "url": "https://example.com", "intervalSeconds": 300, "timeoutSeconds": 30 }'
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.post(f"{BASE}/api/v1/monitors", headers=HEADERS, json={ "name": "Homepage", "type": "HTTP", "url": "https://example.com", "intervalSeconds": 300, "timeoutSeconds": 30 })
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "strings"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        body := strings.NewReader(`{ "name": "Homepage", "type": "HTTP", "url": "https://example.com", "intervalSeconds": 300, "timeoutSeconds": 30 }`)
        req, _ := http.NewRequest("POST", "https://dev-api.uptimecraft.com/api/v1/monitors", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("POST", HttpRequest.BodyPublishers.ofString("""
                    { "name": "Homepage", "type": "HTTP", "url": "https://example.com", "intervalSeconds": 300, "timeoutSeconds": 30 }"""))
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ "name": "Homepage", "type": "HTTP", "url": "https://example.com", "intervalSeconds": 300, "timeoutSeconds": 30 })
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Request body

{
  "name": "Homepage",
  "type": "HTTP",
  "url": "https://example.com",
  "intervalSeconds": 300,
  "timeoutSeconds": 30
}

Response

{
  "id": "3f1a...",
  "name": "Homepage",
  "type": "HTTP",
  "url": "https://example.com",
  "intervalSeconds": 300,
  "timeoutSeconds": 30,
  "paused": false,
  "status": "UNKNOWN"
}
GET /api/v1/monitors/{id}

Get one monitor

Everything configured on a single monitor.

Requires monitors:read

curl -X GET \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.get(f"{BASE}/api/v1/monitors/MONITOR_ID", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("GET", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("GET", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{
  "id": "3f1a...",
  "name": "Homepage",
  "type": "HTTP",
  "url": "https://example.com",
  "intervalSeconds": 300,
  "paused": false,
  "status": "UP"
}
PATCH /api/v1/monitors/{id}

Update a monitor

Changes only the fields you send. Anything you leave out keeps its current value, so an older integration cannot blank a setting it does not know about.

Requires monitors:write · paid plans only

curl -X PATCH \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Homepage (production)", "intervalSeconds": 60 }'
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.patch(f"{BASE}/api/v1/monitors/MONITOR_ID", headers=HEADERS, json={ "name": "Homepage (production)", "intervalSeconds": 60 })
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "strings"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        body := strings.NewReader(`{ "name": "Homepage (production)", "intervalSeconds": 60 }`)
        req, _ := http.NewRequest("PATCH", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("PATCH", HttpRequest.BodyPublishers.ofString("""
                    { "name": "Homepage (production)", "intervalSeconds": 60 }"""))
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID`, {
      method: "PATCH",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ "name": "Homepage (production)", "intervalSeconds": 60 })
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Request body

{
  "name": "Homepage (production)",
  "intervalSeconds": 60
}

Response

{
  "id": "3f1a...",
  "name": "Homepage (production)",
  "intervalSeconds": 60,
  "url": "https://example.com",
  "status": "UP"
}
DELETE /api/v1/monitors/{id}

Delete a monitor

Removes the monitor and stops checking it. Answers 204 with no body.

Requires monitors:write · paid plans only

curl -X DELETE \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.delete(f"{BASE}/api/v1/monitors/MONITOR_ID", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("DELETE", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("DELETE", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID`, {
      method: "DELETE",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);
POST /api/v1/monitors/{id}/pause

Pause a monitor

Stops checking without deleting anything. Paused monitors never alert.

Requires monitors:write · paid plans only

curl -X POST \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/pause \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.post(f"{BASE}/api/v1/monitors/MONITOR_ID/pause", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("POST", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/pause", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/pause"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("POST", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID/pause`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{ "id": "3f1a...", "name": "Homepage", "paused": true, "status": "PAUSED" }
POST /api/v1/monitors/{id}/resume

Resume a monitor

Starts checking a paused monitor again.

Requires monitors:write · paid plans only

curl -X POST \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/resume \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.post(f"{BASE}/api/v1/monitors/MONITOR_ID/resume", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("POST", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/resume", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/resume"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("POST", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID/resume`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{ "id": "3f1a...", "name": "Homepage", "paused": false, "status": "UNKNOWN" }
GET /api/v1/monitors/{id}/status

Get current status

What we last recorded for this monitor. This reads stored state and does not run a check — use the check endpoint for that.

Requires monitors:read

curl -X GET \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/status \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.get(f"{BASE}/api/v1/monitors/MONITOR_ID/status", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("GET", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/status", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/status"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("GET", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID/status`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{
  "id": "3f1a...",
  "name": "Homepage",
  "status": "UP",
  "paused": false,
  "lastCheckedAt": "2026-09-06T09:31:04Z",
  "consecutiveFailures": 0,
  "consecutiveSuccesses": 412
}
POST /api/v1/monitors/{id}/check

Check now

Asks an agent to check this monitor immediately. Answers 202: the check runs on an agent in its region and the result arrives shortly after, so read the outcome from the status endpoint. Refused while the monitor is paused or inside a maintenance window, and limited to one request per monitor per minute.

Requires monitors:write · paid plans only

curl -X POST \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/check \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.post(f"{BASE}/api/v1/monitors/MONITOR_ID/check", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("POST", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/check", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/check"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("POST", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID/check`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{
  "monitorId": "3f1a...",
  "accepted": true,
  "message": "Check requested. Read the result from /status or /results."
}
GET /api/v1/monitors/{id}/results

List check results

Individual checks, most recent first.

Requires monitors:read

ParameterTypeNotes
hours integer How far back to look. Capped by your plan's history retention, and by 31 days per request.
curl -X GET \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/results \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.get(f"{BASE}/api/v1/monitors/MONITOR_ID/results", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("GET", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/results", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/results"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("GET", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID/results`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

[
  {
    "checkedAt": "2026-09-06T09:31:04Z",
    "region": "nyc",
    "up": true,
    "statusCode": 200,
    "responseTimeMs": 143,
    "duringMaintenance": false
  }
]
GET /api/v1/monitors/{id}/uptime

Get uptime

Uptime over a window, as a percentage. Checks recorded during a maintenance window are excluded, matching what your dashboard and status pages show. If the window you ask for is longer than your plan keeps history, the answer covers what we have and windowTruncated is true.

Requires monitors:read

ParameterTypeNotes
hours integer How far back to look. Capped by your plan's history retention, and by 31 days per request.
curl -X GET \
  https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/uptime \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.get(f"{BASE}/api/v1/monitors/MONITOR_ID/uptime", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("GET", "https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/uptime", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/monitors/MONITOR_ID/uptime"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("GET", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/monitors/MONITOR_ID/uptime`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

{
  "monitorId": "3f1a...",
  "uptimePercent": 99.94,
  "periodHours": 24,
  "from": "2026-09-05T09:31:04Z",
  "to": "2026-09-06T09:31:04Z",
  "windowTruncated": false
}
GET /api/v1/status-pages

List status pages

Your team's status pages. Read-only for now.

Requires status-pages:read

curl -X GET \
  https://dev-api.uptimecraft.com/api/v1/status-pages \
  -H "Authorization: Bearer $UPTIMECRAFT_API_KEY"
import os, time, requests

BASE = "https://dev-api.uptimecraft.com"
HEADERS = {"Authorization": f"Bearer {os.environ['UPTIMECRAFT_API_KEY']}"}

def call():
    for attempt in range(5):
        r = requests.get(f"{BASE}/api/v1/status-pages", headers=HEADERS)
        # 429 means a rate limit; Retry-After says how long to wait.
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")) * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json() if r.content else None
    raise RuntimeError("still rate limited after 5 attempts")

print(call())
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
    "io"
)

func main() {
    for attempt := 0; attempt < 5; attempt++ {
        var body io.Reader
        req, _ := http.NewRequest("GET", "https://dev-api.uptimecraft.com/api/v1/status-pages", body)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("UPTIMECRAFT_API_KEY"))
        req.Header.Set("Content-Type", "application/json")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        // 429 means a rate limit; Retry-After says how long to wait.
        if resp.StatusCode == http.StatusTooManyRequests {
            wait, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
            time.Sleep(time.Duration(wait*(attempt+1)) * time.Second)
            continue
        }
        fmt.Println("status", resp.Status)
        return
    }
    panic("still rate limited after 5 attempts")
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        for (int attempt = 0; attempt < 5; attempt++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://dev-api.uptimecraft.com/api/v1/status-pages"))
                    .header("Authorization", "Bearer " + System.getenv("UPTIMECRAFT_API_KEY"))
                    .header("Content-Type", "application/json")
                    .method("GET", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            // 429 means a rate limit; Retry-After says how long to wait.
            if (response.statusCode() == 429) {
                long wait = Long.parseLong(
                        response.headers().firstValue("Retry-After").orElse("1"));
                Thread.sleep(Duration.ofSeconds(wait * (attempt + 1)).toMillis());
                continue;
            }
            System.out.println(response.statusCode() + " " + response.body());
            return;
        }
        throw new IllegalStateException("still rate limited after 5 attempts");
    }
}
const BASE = "https://dev-api.uptimecraft.com";

async function call() {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE}/api/v1/status-pages`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${process.env.UPTIMECRAFT_API_KEY}`,
        "Content-Type": "application/json",
      }
    });

    // 429 means a rate limit; Retry-After says how long to wait.
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * (attempt + 1) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
    return response.status === 204 ? null : await response.json();
  }
  throw new Error("still rate limited after 5 attempts");
}

call().then(console.log);

Response

[ { "id": "9c2b...", "name": "Acme Status", "slug": "acme" } ]