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.
Scope
Allows
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.
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
}
}
Code
HTTP
Meaning
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
Parameter
Type
Notes
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);
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);
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.
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);
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);
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);
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
Parameter
Type
Notes
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);
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
Parameter
Type
Notes
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);
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);