API Documentation
Complete reference for the YouMonit REST API v2, with copy-paste examples in seven languages.
Base URL & conventions
All endpoints are served over HTTPS under the version prefix below. Requests and responses are JSON encoded as UTF-8; timestamps are ISO-8601.
https://api.youmonit.com/api/v2
Authentication
Create an API key in your workspace under Integrations & API, then pass it as a Bearer token on every request. Keys start with ymk_live_ and are shown only once β store them safely.
Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx
Create a key in Integrations & API
Scopes
Each key carries one or more scopes. A write scope implies the matching read scope. A request missing a required scope is rejected with HTTP 403.
| Scope | Grants |
|---|---|
read:monitors |
Read monitors, their check history and uptime. |
write:monitors |
Create, update, pause and delete monitors. |
read:incidents |
List and read incidents. |
write:incidents |
Acknowledge incidents. |
read:status-pages |
Read public status pages. |
read:channels |
List notification channels. |
read:billing |
Read the subscription, plan limits and invoices. |
read:members |
Read the account profile and its members. |
read:devices |
List push devices the current user has registered. |
manage:devices |
Register / refresh / remove push devices and fire test notifications. |
read:updates |
Long-poll the live incident + monitor status stream (used by the mobile dashboard). |
read:support |
Read your account's YouMonit Support threads and messages. |
write:support |
Open new support threads and reply to existing ones. |
Quick start β list your monitors
Pick your language above. This example fetches every monitor in the workspace the key belongs to.
curl https://api.youmonit.com/api/v2/monitors \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx"<?php
$ch = curl_init('https://api.youmonit.com/api/v2/monitors');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx'],
]);
$body = curl_exec($ch);
curl_close($ch);
$data = json_decode($body, true);
foreach ($data['monitors'] as $m) {
printf("%s β %s\n", $m['name'], $m['last_status']);
}const res = await fetch('https://api.youmonit.com/api/v2/monitors', {
headers: { Authorization: 'Bearer ymk_live_xxxxxxxxxxxxxxxx' },
});
const { monitors } = await res.json();
monitors.forEach(m => console.log(`${m.name} β ${m.last_status}`));import requests
res = requests.get(
"https://api.youmonit.com/api/v2/monitors",
headers={"Authorization": "Bearer ymk_live_xxxxxxxxxxxxxxxx"},
)
for m in res.json()["monitors"]:
print(m["name"], "β", m["last_status"])use strict;
use warnings;
use LWP::UserAgent;
use JSON::PP qw(decode_json);
my $ua = LWP::UserAgent->new;
my $res = $ua->get(
'https://api.youmonit.com/api/v2/monitors',
'Authorization' => 'Bearer ymk_live_xxxxxxxxxxxxxxxx',
);
die $res->status_line unless $res->is_success;
my $data = decode_json($res->decoded_content);
for my $m (@{ $data->{monitors} }) {
printf "%s β %s\n", $m->{name}, $m->{last_status};
}require "net/http"
require "json"
uri = URI("https://api.youmonit.com/api/v2/monitors")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer ymk_live_xxxxxxxxxxxxxxxx"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
JSON.parse(res.body)["monitors"].each do |m|
puts "#{m['name']} β #{m['last_status']}"
endpackage main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.youmonit.com/api/v2/monitors", nil)
req.Header.Set("Authorization", "Bearer ymk_live_xxxxxxxxxxxxxxxx")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data struct {
Monitors []struct {
Name string `json:"name"`
LastStatus string `json:"last_status"`
} `json:"monitors"`
}
json.NewDecoder(res.Body).Decode(&data)
for _, m := range data.Monitors {
fmt.Printf("%s β %s\n", m.Name, m.LastStatus)
}
}Create a monitor
POST a JSON body. The fields type, name and target are required; everything else falls back to sensible defaults.
curl -X POST https://api.youmonit.com/api/v2/monitors \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"type": "web",
"name": "Marketing site",
"target": "https://example.com",
"interval_seconds": 300
}'<?php
$payload = json_encode([
'type' => 'web',
'name' => 'Marketing site',
'target' => 'https://example.com',
'interval_seconds' => 300,
]);
$ch = curl_init('https://api.youmonit.com/api/v2/monitors');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx',
'Content-Type: application/json',
],
]);
$monitor = json_decode(curl_exec($ch), true)['monitor'];
curl_close($ch);
echo "Created monitor #{$monitor['id']}\n";const res = await fetch('https://api.youmonit.com/api/v2/monitors', {
method: 'POST',
headers: {
Authorization: 'Bearer ymk_live_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'web',
name: 'Marketing site',
target: 'https://example.com',
interval_seconds: 300,
}),
});
const { monitor } = await res.json();
console.log('Created monitor #' + monitor.id);import requests
res = requests.post(
"https://api.youmonit.com/api/v2/monitors",
headers={"Authorization": "Bearer ymk_live_xxxxxxxxxxxxxxxx"},
json={
"type": "web",
"name": "Marketing site",
"target": "https://example.com",
"interval_seconds": 300,
},
)
print("Created monitor #", res.json()["monitor"]["id"])use strict;
use warnings;
use LWP::UserAgent;
use JSON::PP qw(encode_json decode_json);
my $ua = LWP::UserAgent->new;
my $res = $ua->post(
'https://api.youmonit.com/api/v2/monitors',
'Authorization' => 'Bearer ymk_live_xxxxxxxxxxxxxxxx',
'Content-Type' => 'application/json',
Content => encode_json({
type => 'web',
name => 'Marketing site',
target => 'https://example.com',
interval_seconds => 300,
}),
);
my $monitor = decode_json($res->decoded_content)->{monitor};
print "Created monitor #", $monitor->{id}, "\n";require "net/http"
require "json"
uri = URI("https://api.youmonit.com/api/v2/monitors")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer ymk_live_xxxxxxxxxxxxxxxx"
req["Content-Type"] = "application/json"
req.body = {
type: "web",
name: "Marketing site",
target: "https://example.com",
interval_seconds: 300,
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts "Created monitor ##{JSON.parse(res.body)['monitor']['id']}"package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"type": "web",
"name": "Marketing site",
"target": "https://example.com",
"interval_seconds": 300,
})
req, _ := http.NewRequest("POST",
"https://api.youmonit.com/api/v2/monitors", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer ymk_live_xxxxxxxxxxxxxxxx")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
var data struct {
Monitor struct {
ID int `json:"id"`
} `json:"monitor"`
}
json.NewDecoder(res.Body).Decode(&data)
fmt.Printf("Created monitor #%d\n", data.Monitor.ID)
}Monitor object
Every endpoint that returns a monitor emits the same shape. Notable fields:
typeβ the monitor type:http,api,tcp,ssh,smtp,pop,imap,ntp,ping,dns,dns_change,whois,domain_expiration,blacklist,ssl_cert,cloudstatus,pagespeed, or push-basedheartbeat/webhook/rum.tagsβ array of every tag row attached to the monitor, each withid,slugandname. Empty array when none.last_statusβonline,offline,degradedorunknown.configβ type-specific settings block (expected status codes, TCP ports, expected DNS answersβ¦). Structure depends ontype.
{
"monitor": {
"id": 29,
"name": "DE1 Jidlo.cz",
"type": "web",
"target": "https://www.jidlo.cz",
"port": null,
"interval_seconds": 60,
"timeout_seconds": 30,
"sensitivity": 1,
"is_active": true,
"is_paused": false,
"last_status": "online",
"last_checked_at": "2026-07-28 12:34:07",
"last_response_ms": 87,
"config": {
"method": "GET",
"expected_codes": [200, 301],
"follow_redirects": true
},
"tags": [
{ "id": 4, "slug": "production", "name": "Production" },
{ "id": 12, "slug": "cs", "name": "CS" }
],
"created_at": "2026-05-18 15:22:04"
}
}
Endpoint reference
Monitors
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/monitors |
read:monitors |
Paged list of monitors. Default 20 per page, max 200. Each row includes type, target, live status, config JSON and every tag attached. Optional filters: ?type=http, ?status=offline, ?tag=production. See the Pagination section for the page / per_page / links contract. |
| POST | /api/v2/monitors |
write:monitors |
Create a new monitor. |
| GET | /api/v2/monitors/{id} |
read:monitors |
Full monitor object including type, target, tags[], config, sensitivity/retries and the last check summary. |
| PATCH | /api/v2/monitors/{id} |
write:monitors |
Update fields on an existing monitor. |
| DELETE | /api/v2/monitors/{id} |
write:monitors |
Delete a monitor permanently. |
| GET | /api/v2/monitors/{id}/checks |
read:monitors |
Paged list of recent check results for one monitor. Default 20 per page, max 100 β see the Pagination section below. |
| GET | /api/v2/monitors/{id}/uptime |
read:monitors |
Uptime aggregate for 24h / 7d / 30d / 90d. |
Incidents
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/incidents |
read:incidents |
Paged incident feed for the account. Default 20 per page, max 100. Optional filters: status, tag, monitor_id. See the Pagination section below. |
| GET | /api/v2/incidents/{id} |
read:incidents |
Fetch a single incident by ID. |
| POST | /api/v2/incidents/{id}/acknowledge |
write:incidents |
Acknowledge an open incident. |
Status pages & channels
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/status-pages |
read:status-pages |
List the workspace status pages. |
| GET | /api/v2/channels |
read:channels |
List notification channels. |
Account & members
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/me |
read:members |
The account the API key belongs to. |
| GET | /api/v2/accounts/{id} |
read:members |
Fetch account details by ID. |
| GET | /api/v2/accounts/{id}/members |
read:members |
List members of the account. |
Billing
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/billing/plans |
β | Public catalogue of plans β no auth required. |
| GET | /api/v2/billing/subscription |
read:billing |
Current subscription and plan limits. |
| GET | /api/v2/billing/invoices |
read:billing |
Invoice history. |
Mobile push (FCM & APNs)
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| POST | /api/v2/devices |
manage:devices |
Register or refresh a mobile push device (upsert on push_token). Body: platform (ios|android|web), push_token, optional device_name / app_version / os_version / timezone / locale. |
| GET | /api/v2/devices |
read:devices |
List every push device the calling user has registered β includes platform, last_seen_at and any delivery errors. |
| DELETE | /api/v2/devices |
manage:devices |
Deregister a device by push_token (soft-delete β the record is kept for delivery analytics). |
| POST | /api/v2/devices/test-push |
manage:devices |
Send a test push to every active device the calling user owns. Useful during onboarding to verify FCM/APNs credentials are wired up. |
Live dashboard (long-poll)
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/updates |
read:updates |
Long-poll delta stream of incident lifecycle events + monitor status flips since a cursor. Optional wait=<seconds> parks the request server-side until fresh data arrives (up to 25 s). Response includes a new cursor to pass on the next call. |
Support inbox
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/support/threads |
read:support |
List every support thread in this account, newest activity first. Includes an unread count for badge rendering. |
| POST | /api/v2/support/threads |
write:support |
Open a new thread. Body: { subject, body }. Triggers a notification email to YouMonit Support. |
| GET | /api/v2/support/threads/{id} |
read:support |
Full thread payload with every message + any attached action buttons. |
| POST | /api/v2/support/threads/{id}/reply |
write:support |
Post a reply. Body: { body }. |
| POST | /api/v2/support/threads/{id}/read |
write:support |
Mark this thread read from the user side (clears the unread badge in /app and the API list count). |
Pagination
List endpoints that can return more than a page's worth of data are paginated. Pass
page (1-indexed) and per_page as query params. The response
includes a pagination object and a links object with absolute
URLs so clients can walk the pages without rebuilding query strings themselves.
| Endpoint | Default per_page | Max per_page |
|---|---|---|
GET /api/v2/monitors |
20 | 200 |
GET /api/v2/monitors/{id}/checks |
20 | 100 |
GET /api/v2/incidents |
20 | 100 |
Request
GET /api/v2/incidents?page=2&per_page=20&status=open
Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx
The legacy limit parameter is still accepted as an alias for
per_page so pre-pagination clients keep working, but new code should use
per_page.
Response shape
{
"incidents": [ /* 20 items on page 2, newest first */ ],
"pagination": {
"page": 2,
"per_page": 20,
"total": 137,
"total_pages": 7,
"has_more": true
},
"links": {
"first": "https://api.youmonit.com/api/v2/incidents?status=open&page=1&per_page=20",
"prev": "https://api.youmonit.com/api/v2/incidents?status=open&page=1&per_page=20",
"next": "https://api.youmonit.com/api/v2/incidents?status=open&page=3&per_page=20",
"last": "https://api.youmonit.com/api/v2/incidents?status=open&page=7&per_page=20"
}
}
pagination.total_pages=ceil(total / per_page). Zero when there's no data.pagination.has_moreis a convenience βpage < total_pages.links.nextandlinks.prevarenullwhen there is no next / previous page. Walk the list by followingnextuntil it's null.- All filter query params are preserved on the returned links, so paging through a filtered set is trivial.
Follow the next link
# Bash β page through every open incident, 20 at a time
url="https://api.youmonit.com/api/v2/incidents?status=open&per_page=20"
while [ -n "$url" ] && [ "$url" != "null" ]; do
resp=$(curl -s -H "Authorization: Bearer ymk_live_β¦" "$url")
echo "$resp" | jq '.incidents[] | "\(.id) \(.monitor_name) \(.first_error)"'
url=$(echo "$resp" | jq -r '.links.next')
done
// JavaScript β same idea, using fetch()
async function walkIncidents(apiKey) {
let url = 'https://api.youmonit.com/api/v2/incidents?status=open&per_page=20';
while (url) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const data = await res.json();
data.incidents.forEach(i => console.log(i.id, i.monitor_name, i.first_error));
url = data.links.next; // null on the last page β loop exits
}
}
Errors & status codes
Errors return the appropriate HTTP status with a JSON body containing an error field describing what went wrong.
200 / 201 | Success β the response body carries the resource. |
401 | Missing, malformed or revoked Bearer token. |
403 | The token is valid but lacks the required scope. |
404 | The resource does not exist or is not in your account. |
422 | Validation failed β check the error message for the offending field. |
429 | Rate limit exceeded β slow down and retry. |
{ "error": "token lacks required scope: write:monitors" }
Rate limits
API keys are held to a fair-use budget of 120 requests per minute. Batch where you can; exceeding the budget returns HTTP 429.
Outgoing webhooks
Instead of polling, register a webhook in Integrations & API to receive monitor.up and monitor.down events. Each delivery is signed with HMAC-SHA-256 so you can verify it came from YouMonit.
Mobile push notifications
The YouMonit mobile app receives incident alerts through Firebase Cloud Messaging (Android / web builds) and Apple Push Notification service (iOS). Registering a device is a one-call setup β from that moment on the user automatically receives push notifications for every monitor in the account, with no Contact configuration needed.
Each notification payload includes event, monitor_id,
incident_id, target and http_code in its data
block so the app can deep-link straight to the incident detail.
1 Β· Register the device
Call POST /api/v2/devices once, right after the OS grants push permission
and the FCM/APNs SDK hands you a token. The endpoint is idempotent β call it again on
every app launch with the current token and it will upsert / reactivate the record
without creating duplicates.
curl -X POST https://api.youmonit.com/api/v2/devices \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"platform": "ios",
"push_token": "d5f2a7...c9",
"device_name": "Petr's iPhone 15",
"app_version": "1.4.2",
"os_version": "iOS 18.2",
"timezone": "Europe/Prague",
"locale": "en"
}'<?php
$ch = curl_init('https://api.youmonit.com/api/v2/devices');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'platform' => 'android',
'push_token' => $fcmToken,
'device_name' => 'Pixel 8',
'app_version' => '1.4.2',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx',
'Content-Type: application/json',
],
]);
$device = json_decode(curl_exec($ch), true)['device'];
curl_close($ch);
echo "Registered device #{$device['id']} for push\n";// React Native / Expo / Capacitor: call this after the OS grants push
// permission and the SDK hands you a fresh token.
await fetch('https://api.youmonit.com/api/v2/devices', {
method: 'POST',
headers: {
Authorization: 'Bearer ymk_live_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
platform: Platform.OS, // 'ios' | 'android'
push_token: fcmOrApnsToken,
device_name: Device.modelName,
app_version: Application.nativeApplicationVersion,
os_version: Device.osVersion,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
locale: Localization.locale,
}),
});import requests
requests.post(
"https://api.youmonit.com/api/v2/devices",
headers={"Authorization": "Bearer ymk_live_xxxxxxxxxxxxxxxx"},
json={
"platform": "android",
"push_token": fcm_token,
"device_name": "Pixel 8",
},
)use LWP::UserAgent;
use JSON::PP qw(encode_json);
my $ua = LWP::UserAgent->new;
$ua->post(
'https://api.youmonit.com/api/v2/devices',
'Authorization' => 'Bearer ymk_live_xxxxxxxxxxxxxxxx',
'Content-Type' => 'application/json',
Content => encode_json({
platform => 'android',
push_token => $fcm_token,
device_name => 'Pixel 8',
}),
);require "net/http"
require "json"
uri = URI("https://api.youmonit.com/api/v2/devices")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer ymk_live_xxxxxxxxxxxxxxxx"
req["Content-Type"] = "application/json"
req.body = { platform: "android", push_token: fcm_token, device_name: "Pixel 8" }.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }package main
import (
"bytes"
"encoding/json"
"net/http"
)
func RegisterDevice(apiKey, fcmToken string) error {
body, _ := json.Marshal(map[string]any{
"platform": "android",
"push_token": fcmToken,
"device_name": "Pixel 8",
})
req, _ := http.NewRequest("POST",
"https://api.youmonit.com/api/v2/devices", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
res.Body.Close()
return nil
}
That's it. Once a device is registered, its owner automatically receives push
notifications for every monitor in the account β no Contact
wiring required. Add a push method to a Contact only when you want a
different team member to receive alerts for a specific monitor.
Token gone stale? YouMonit auto-detects Unregistered / BadDeviceToken
(APNs) and UNREGISTERED / NOT_FOUND (FCM) responses and
soft-deletes the device row. Re-registering on next launch reactivates it silently.
2 Β· Live dashboard (long-poll)
Push handles background delivery. When the app is in the foreground it should
long-poll GET /api/v2/updates to receive the same events at zero latency
without a WebSocket. Pass the last cursor you received; add
wait=25 to have the server park the request until fresh data arrives
(or 25 s pass, whichever is first).
# `since` is the cursor from the previous response; `wait=25` parks the
# request server-side until fresh data lands (or 25 s elapse).
curl "https://api.youmonit.com/api/v2/updates?since=1721984561000&wait=25" \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx"<?php
$cursor = 0; // start from now; persist between calls
while (true) {
$url = 'https://api.youmonit.com/api/v2/updates?wait=25&since=' . $cursor;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 35,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx'],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($data['incidents'] as $i) {
printf("[%s] %s β %s\n", $i['status'], $i['monitor_name'], $i['first_error']);
}
$cursor = $data['cursor'];
}// Simple recursive long-poll loop for the mobile dashboard.
let cursor = 0;
async function pollUpdates() {
const res = await fetch(`https://api.youmonit.com/api/v2/updates?wait=25&since=${cursor}`, {
headers: { Authorization: 'Bearer ymk_live_xxxxxxxxxxxxxxxx' },
});
const data = await res.json();
cursor = data.cursor;
data.incidents.forEach(i => renderIncident(i));
data.monitor_updates.forEach(m => refreshTile(m));
setTimeout(pollUpdates, 0); // immediate re-poll
}
pollUpdates();import requests, time
cursor = 0
while True:
r = requests.get(
"https://api.youmonit.com/api/v2/updates",
params={"since": cursor, "wait": 25},
headers={"Authorization": "Bearer ymk_live_xxxxxxxxxxxxxxxx"},
timeout=35,
)
data = r.json()
cursor = data["cursor"]
for i in data["incidents"]:
print(i["status"], i["monitor_name"], i.get("first_error", ""))use LWP::UserAgent;
use JSON::PP qw(decode_json);
my $ua = LWP::UserAgent->new(timeout => 35);
my $cursor = 0;
while (1) {
my $res = $ua->get(
"https://api.youmonit.com/api/v2/updates?wait=25&since=$cursor",
'Authorization' => 'Bearer ymk_live_xxxxxxxxxxxxxxxx',
);
my $data = decode_json($res->decoded_content);
$cursor = $data->{cursor};
for my $i (@{ $data->{incidents} }) {
printf "[%s] %s β %s\n", $i->{status}, $i->{monitor_name}, $i->{first_error};
}
}require "net/http"
require "json"
cursor = 0
loop do
uri = URI("https://api.youmonit.com/api/v2/updates?wait=25&since=#{cursor}")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer ymk_live_xxxxxxxxxxxxxxxx"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 35) { |h| h.request(req) }
data = JSON.parse(res.body)
cursor = data["cursor"]
data["incidents"].each { |i| puts "#{i['status']} #{i['monitor_name']}" }
endpackage main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
type update struct {
Cursor int64 `json:"cursor"`
Incidents []struct {
Status string `json:"status"`
MonitorName string `json:"monitor_name"`
FirstError string `json:"first_error"`
} `json:"incidents"`
}
func main() {
client := &http.Client{Timeout: 35 * time.Second}
cursor := int64(0)
for {
req, _ := http.NewRequest("GET",
fmt.Sprintf("https://api.youmonit.com/api/v2/updates?wait=25&since=%d", cursor), nil)
req.Header.Set("Authorization", "Bearer ymk_live_xxxxxxxxxxxxxxxx")
res, err := client.Do(req)
if err != nil { continue }
var u update
json.NewDecoder(res.Body).Decode(&u)
res.Body.Close()
cursor = u.Cursor
for _, i := range u.Incidents {
fmt.Printf("[%s] %s β %s\n", i.Status, i.MonitorName, i.FirstError)
}
}
}3 Β· Test end-to-end
Call POST /api/v2/devices/test-push during onboarding to fire a
notification to every device the user has registered. Useful when validating
your FCM service-account JSON and APNs .p8 key are configured on the platform.
curl -X POST https://api.youmonit.com/api/v2/devices/test-push \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx"
YouMonit Support (in-app messaging)
Every account has a support inbox reachable at /app/support
in the web UI and at /api/v2/support/threads via the API. Threads are
conversations between the account's users and YouMonit Support. Every new message
fires an email notification to the counterparty, and admin replies can attach one
or more action buttons that render as clickable CTAs both in the
email and inside the app (e.g. "Pay subscription" β /app/billing).
Open a thread + reply
# Open a new support thread
curl -X POST https://api.youmonit.com/api/v2/support/threads \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"subject": "Billing question β annual switch",
"body": "Hi, we\'d like to move to annual billing starting next month."
}'
# Reply on an existing thread
curl -X POST https://api.youmonit.com/api/v2/support/threads/17/reply \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "body": "Thanks β please also enable the invoice PDF option." }'
# Mark a thread read from the user side
curl -X POST https://api.youmonit.com/api/v2/support/threads/17/read \
-H "Authorization: Bearer ymk_live_xxxxxxxxxxxxxxxx"
Thread + message shape
{
"thread": {
"id": 17,
"subject": "Billing question β annual switch",
"status": "open",
"opened_by_admin": false,
"unread": false,
"last_message_at": "2026-07-28 14:22:07",
"created_at": "2026-07-28 09:11:02"
},
"messages": [
{
"id": 41,
"thread_id": 17,
"from_admin": false,
"sender_email": "billing@customer.com",
"body": "Hi, we'd like to move to annual billing.",
"actions": [],
"created_at": "2026-07-28 09:11:02",
"read_at": "2026-07-28 09:14:38"
},
{
"id": 42,
"thread_id": 17,
"from_admin": true,
"sender_email": null,
"body": "Sure β jump to Billing to switch cadence anytime.",
"actions": [
{
"label": "API keys",
"url": "/app/integrations",
"full_url": "https://www.youmonit.com/app/integrations",
"style": "primary",
"is_internal": true
},
{
"label": "Pay subscription",
"url": "/app/billing",
"full_url": "https://www.youmonit.com/app/billing",
"style": "primary",
"is_internal": true
}
],
"created_at": "2026-07-28 14:22:07",
"read_at": null
}
]
}
from_admin: true = message written by YouMonit Support.
read_at is stamped when the other side opens the thread β use it to
render "read" ticks in a chat UI.
Action buttons on messages
When an admin attaches action buttons to their reply (e.g. "Pay subscription",
"API keys"), each button surfaces in actions[] on the message. Mobile
clients and third-party integrations can render these as tappable buttons β the API
gives you everything needed to display and dispatch each one:
| Field | Type | Meaning |
|---|---|---|
label | string | Text on the button (e.g. API keys). |
url | string | URL as stored. Usually a relative platform path like /app/integrations. |
full_url | string | Always an absolute URL. Relative paths get prefixed with https://www.youmonit.com. Pass this to WebView / browser openers directly. |
style | string | primary / secondary / danger β drives the button colour to match the web rendering. |
is_internal | bool | true when the URL points at youmonit.com. Mobile apps use this to route the click into an in-app WebView; false means "open in the system browser". |
The web app renders each action as a Bootstrap button using the style
field. Reproduce the same look on mobile / in another client by matching classes
or your platform's equivalent primary / secondary / danger button treatments:
// Web / JS β mirrors the button styling used inside /app/support
function styleClass(style) {
return style === 'danger' ? 'btn btn-sm btn-outline-danger'
: style === 'secondary' ? 'btn btn-sm btn-outline-secondary'
: 'btn btn-sm btn-primary';
}
function renderAction(a) {
return '<a href="' + a.url + '" class="' + styleClass(a.style) + '">'
+ a.label
+ '</a>';
}
// e.g. { label: "API keys", url: "/app/integrations", style: "primary" }
// β <a href="/app/integrations" class="btn btn-sm btn-primary">API keys</a>
// React Native β dispatch to WebView for in-app links, system browser otherwise
import { Linking, TouchableOpacity, Text } from 'react-native';
function ActionButton({ action, onOpenInApp }) {
const bg = action.style === 'danger' ? '#dc2626'
: action.style === 'secondary' ? '#334155'
: '#4CAF87';
return (
<TouchableOpacity
onPress={() => action.is_internal
? onOpenInApp(action.full_url)
: Linking.openURL(action.full_url)}
style={{ backgroundColor: bg, padding: 10, borderRadius: 8 }}
>
<Text style={{ color: '#fff', fontWeight: '600' }}>{action.label}</Text>
</TouchableOpacity>
);
}