Health checks API: create and query checks via API v2
With the health checks API (part of the IPv64.net API v2) you create, change, pause and delete checks from scripts, query measurements, events and incidents, and manage notification channels, maintenance windows and nodes. The field names are the same as in the Node64 API, so scripts look alike against both.
Overview: base URL, format and errors
All paths on this page start with /hc2 and are appended to the API v2 base URL:
https://ipv64.net/api/v2
- Methods: GET, POST, PATCH and DELETE. If you can only send GET and POST, send POST with the header
X-HTTP-Method-Override: PATCH(orDELETE). - Requests as JSON (
Content-Type: application/json), at most 1 MB.PATCHonly changes the fields you send. - Responses as JSON in the envelope
{"data": …, "meta": {…}};metaonly appears for lists with paging details.DELETEanswers204without content. - Times in ISO 8601 with time zone, in responses always UTC (e.g.
2026-10-26T08:15:00.000Z). - The complete description as OpenAPI 3.1 (section “Healthchecks 2.0”) is available at ipv64.net/api_v2_docs.
Errors
Errors follow RFC 9457 as application/problem+json with a fixed error code in code:
{
"type": "https://ipv64.net/api/v2/errors#limit_reached",
"title": "Forbidden",
"status": 403,
"detail": "Online-Limit erreicht: 10 von 10 Messstellen belegt, benötigt werden 2 weitere. …",
"code": "limit_reached",
"errors": ["Online-Limit erreicht: …"]
}
Validation messages of checks and channels (detail, errors) are in German.
| HTTP | Codes (selection) | Meaning |
|---|---|---|
| 401 | unauthenticated, invalid_token | API key missing, invalid or expired, or the account is not active |
| 403 | limit_reached | measuring point limit of the account class reached |
| 404 | not_found | check, channel, maintenance window, test run or node not found |
| 409 | move_requires_test, unchanged, not_verified, disabled, node_channel | action not possible in the current state (details in detail) |
| 422 | invalid_healthcheck, private_target, invalid_type, move_rejected, invalid_channel, invalid_code … | invalid input; for checks and channels all messages are in errors |
| 429 | rate_limited, sms_limit | too many requests or daily SMS limit reached |
| 502 | delivery_failed | message could not be delivered |
Authentication and limits
The API uses your API keys from Account > API (see Account: API keys). Send the key only in a header – never in the URL:
curl "https://ipv64.net/api/v2/hc2/account" -H "Authorization: Bearer YOUR_API_KEY"
curl "https://ipv64.net/api/v2/hc2/account" -H "X-API-Key: YOUR_API_KEY"
An API key may do everything in the account, not just health checks. A cron job that only reports a push does not need an API key – use the push URL with the check's token instead.
Limits
- General: per account up to 300 requests at once, then 10 per second; per IP address 600 at once, then 20 per second. Failed logins: 20, then 12 per minute per IP. The headers
RateLimit-LimitandRateLimit-Remainingshow the current state; on429,Retry-Aftergives the waiting time in seconds. - Test messages: one per 30 seconds per channel.
- Confirmation codes: 5 codes per hour, 10 attempts per hour – shared with the portal.
- Sending through an IPv64 channel (
/send): 30 at once, then 30 per hour, at most 200 per day; SMS additionally according to the daily limit of your account class. - The health check limits of your account class (online measuring points, online measurements per day, locations per check) also apply via API, see Limits.
Account and settings
GET /hc2/account
Account class, limits and usage:
{"data": {
"plan": "Supporter",
"online_measuring_points": {"used": 6, "limit": 10},
"locations_per_check": 2,
"checks_per_day": {"used": 2817, "limit": 8192},
"sms_per_day": 50,
"locations": [{"code": "fsn", "name": "Falkenstein"}, {"code": "nbg", "name": "Nürnberg"}],
"nodes": {"total": 1, "online": 1},
"checks": 12
}}
limit: null means unlimited; for sms_per_day that is 0.
GET and PATCH /hc2/settings
Quiet hours and grouped alerts of the account (see Notifications). Allowed keys:
| Key | Value |
|---|---|
notify.quiet_enabled | true/false (stored as 1/0) |
notify.quiet_from, notify.quiet_to | time HH:MM |
notify.quiet_days | weekdays 1 (Mon) to 7 (Sun), as a list or "1,2,3" |
notify.quiet_tz | time zone, e.g. Europe/Berlin |
notify.rate_per_hour | 0 to 1000 (0 = unlimited) |
notify.batch_threshold | 0 to 100 |
notify.batch_window_seconds | 0 to 600 |
curl -X PATCH "https://ipv64.net/api/v2/hc2/settings" -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"notify.quiet_enabled": true, "notify.quiet_from": "23:00", "notify.quiet_to": "06:30"}'
Manage checks
| Method and path | Effect |
|---|---|
GET /hc2/checks | all checks; filters: type, state, execution, node_id, tag, group, q (search in name and target) |
POST /hc2/checks | create a check → 201 with the check |
GET /hc2/checks/{token} | one check with measuring points (sources) and availability (availability_percent for 24h, 30d, 1y) |
PATCH /hc2/checks/{token} | change fields (see below about the execution) |
DELETE /hc2/checks/{token} | delete the check with all measurement data → 204; if it was a monitor in DNS failover, the header Warning: 299 names the policies |
POST /hc2/checks/{token}/pause, /resume | pause or resume; response: the check |
POST /hc2/checks/{token}/run | check now: local and hybrid checks → 202 with a command_id per node; online checks → 200 with queued: false (they run at their interval); push checks → 422 not_applicable |
Fields of a check
| Field | Default on create | Note |
|---|---|---|
type | http | http, keyword, json, dns, icmp, tcp, port, cert, push, docker, whois |
name | – | required, up to 200 characters |
target | – | URL, host, IP, domain or container – depending on the type; empty for push |
params | {} | settings of the type (see Parameters per type) |
interval_seconds | 60 | locally from 5, online/hybrid from 20, up to 86400; WHOIS from 3600 |
retry_interval_seconds | 20 | from 5, at most the interval |
retries, stabilize | 1, 3 | 0–10 and 1–20 |
connect_timeout_seconds, timeout_seconds | 5, 10 | 0.5–60 and 1–120 (online at most 30 and 60) |
ip_mode | auto | auto, ipv4, ipv6, both |
execution | online | online, local, both |
nodes or node_id | – | measuring nodes (list of IDs) or a single node; required for local/both |
locations | all locations | e.g. ["nbg","fsn"]; shortened to the locations of your account class |
policy | all | all, any, majority |
notify_down, notify_up, notify_degraded | true, true, false | what you get notified about |
channels | [] | channel IDs; empty = default channels |
repeat_minutes | 0 | reminder, 0–10080 |
critical, invert, paused | false | |
group, tags, description | empty | group up to 100 characters; tags as a list or text |
parent | empty | token of the parent check |
The response additionally contains token, node_id, node_name, nodes (with names), revision, created_at, updated_at and state with state, since, last_check_at, last_status, last_latency_ms, last_reason, last_message, down_since and last_push_at. Push checks also have a push object (see Push URL). The state is one of up, down, degraded, flapping, stabilizing, dependent, pending and paused; “not monitored” and “quota used up” show as pending with last_reason unchecked or quota_exhausted.
Parameters per type (params)
| Type | Keys in params |
|---|---|
http, keyword, json | method, accept_status (e.g. "200-299,301"), follow_redirects, max_redirects, basic_user, basic_password, headers (object), body, insecure_tls, cert_min_days (no default via API, i.e. off); keyword only: keyword, keyword_absent; json only: json_rules, json_any |
dns | record_type, resolver (system, server, authoritative), server, expect (any, contains, equals, regex, nxdomain, public_ip), values (list), min_ttl, dnssec |
icmp | count, max_loss_percent, max_latency_ms |
tcp | port, send, expect, tls |
port | port, expect (open or closed) |
cert | port (default 443), min_days, server_name, skip_chain, skip_hostname |
whois | source (auto, rdap, whois), min_days, contains (list) |
push | period_seconds, grace_seconds |
json_rules is a list of objects {"path": "status", "op": "eq", "value": "ok"} with the operators eq, ne, lt, le, gt, ge, contains, not_contains, regex, exists, not_exists – or the text form as in the form. Limits and meaning of all values are listed under Check types.
Examples
Create an HTTPS check from both locations:
curl -X POST "https://ipv64.net/api/v2/hc2/checks" -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "http", "name": "Website", "target": "https://example.com/",
"interval_seconds": 60, "locations": ["nbg", "fsn"],
"params": {"accept_status": "200-299", "cert_min_days": 14}}'
Push check for a nightly backup:
curl -X POST "https://ipv64.net/api/v2/hc2/checks" -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "push", "name": "Backup NAS", "params": {"period_seconds": 86400, "grace_seconds": 3600}}'
List checks that are down, pause a check:
curl "https://ipv64.net/api/v2/hc2/checks?state=down" -H "Authorization: Bearer YOUR_API_KEY"
curl -X POST "https://ipv64.net/api/v2/hc2/checks/YOUR_CHECK_TOKEN/pause" -H "Authorization: Bearer YOUR_API_KEY"
Errors on create: 422 invalid_healthcheck (invalid input), 422 private_target (private target online), 403 limit_reached (measuring point limit).
Change the execution (moving)
If a PATCH changes execution, the nodes or the locations, the API saves all other fields but leaves the execution unchanged and answers 409 move_requires_test. You switch with a test run – or you send "force_move": true, then the switch happens without a test.
| Method and path | Effect |
|---|---|
POST /hc2/checks/{token}/move | start a test run; body: execution (required), nodes or node_id, locations → 202 with the test run |
GET /hc2/testruns/{id} | state of the test run |
curl -X POST "https://ipv64.net/api/v2/hc2/checks/YOUR_CHECK_TOKEN/move" -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" -d '{"execution": "both", "nodes": ["YOUR_NODE_ID"], "locations": ["nbg", "fsn"]}'
{"data": {"id": 4711, "status": "ok", "target": "both", "applied": true,
"results": [{"source": "ipv64 Nürnberg", "ok": true, "message": "ipv4 up 23.4 ms"}, …]}}
Poll the test run every 2 seconds for at most 60 seconds. status is pending, ok (switch applied, applied: true), failed (stays as before) or expired (no answer within 60 seconds, then with error). Errors on start: 409 unchanged (already runs this way), 422 move_rejected (e.g. node offline, remote management off, private target, limit, Docker check).
Measurements, events, incidents and statistics
| Method and path | Parameters | Content |
|---|---|---|
GET /hc2/checks/{token}/measurements | limit (1–5000, default 200), since, until, source | single measurements, newest first: time, source, source_label, status (up/down), latency_ms, reason, message, family, details |
GET /hc2/checks/{token}/events, GET /hc2/events | limit (1–1000, default 100), before, after, since | events of one check or all checks: id, token, source, kind, time, from_state, to_state, reason, message, duration_seconds, flaps, silent, incident_id |
GET /hc2/checks/{token}/incidents | limit (1–500, default 50) | incidents of one check |
GET /hc2/incidents | limit (1–1000, default 100), open=true, since | incidents of all checks: id, token, name, status (open/closed), started_at, ended_at, duration_seconds, reason, message, sources |
GET /hc2/checks/{token}/stats | range: 24h (default), 7d, 30d, 1y | availability and latency (min, average, max) in total and per measuring point, plus a time series per hour (24h, 7d) or per day (30d, 1y) |
- Measuring points (
source):loc:nbgorloc:fsnfor the IPv64 locations,node:NODE_IDfor a node,pushfor pushes. - Single measurements are only kept for a short time; for longer periods use
/stats. - Paging of events:
metacontainslimit,has_moreandnext_before(ornext_afterwhen you page forward withafter). Pass the value asbeforeorafterin the next request. silent: truemeans no message was sent for this event.
curl "https://ipv64.net/api/v2/hc2/checks/YOUR_CHECK_TOKEN/stats?range=30d" -H "Authorization: Bearer YOUR_API_KEY"
curl "https://ipv64.net/api/v2/hc2/incidents?open=true" -H "Authorization: Bearer YOUR_API_KEY"
Push URL for cron jobs
Pushes do not go through API v2 and need no API key. Every push check has a push object in the response:
| Field | Content |
|---|---|
url | https://ipv64.net/health.php?token=TOKEN – the push URL |
hub_url | an equivalent address directly at the health checks server |
node_path | path on the node (/api/push/TOKEN) for local and hybrid push checks, otherwise null |
# Report OK
curl -fsS -m 10 "https://ipv64.net/health.php?token=YOUR_PUSH_TOKEN"
# Report an outage
curl -fsS -m 10 "https://ipv64.net/health.php?token=YOUR_PUSH_TOKEN&status=down&msg=backup+failed"
Parameters: status=down or fail for an outage, msg or message for a message (up to 500 characters). The response is OK. At most 60 pushes per minute per token; pushes count against the daily quota. More under Push checks.
Notification channels
| Method and path | Effect |
|---|---|
GET /hc2/channel-types | all channel types with their fields (key, label, kind, required, secret, options, default) and needs_verification |
GET /hc2/channels, GET /hc2/channels/{id} | channels; secrets are missing from config, secrets_set lists the ones that are set |
POST /hc2/channels | create a channel: type, name, config, enabled (default true), default (default false), lang (de/en), notify_down, notify_up → 201, for e-mail/SMS with verification_required: true |
PATCH /hc2/channels/{id} | change; omitted secrets stay. For a node's channels only name and switches (config → 409 node_channel) |
DELETE /hc2/channels/{id} | delete and remove from all checks → 204 |
POST /hc2/channels/{id}/test | test message → {"sent": true, "queued_at_node": false}; confirmed channels only |
POST /hc2/channels/{id}/verify/send | send a confirmation code to the e-mail address or mobile number (valid for 15 minutes) |
POST /hc2/channels/{id}/verify | confirm with {"code": "123456"}; wrong or expired: 422 invalid_code |
POST /hc2/channels/{id}/send | your own message through a confirmed, enabled channel ipv64_mail, ipv64_sms or app: subject, text (required), optional kind, event_id, source |
Channel types and their config keys: ipv64_mail (email), ipv64_sms (number), app (device_key, critical), telegram (bot_token, chat_id, silent), discord (webhook_url, username), slack and teams (webhook_url), matrix (server, token, room), ntfy (server, topic, token, user, password, priority_down, priority_up), gotify (server, app_token, priority), pushover (user_key, app_token, device, priority_down, priority_up), webhook (url, method, headers, body, insecure_tls), smtp (host, port, security, user, password, from, to). Meaning and defaults: Channel types. URLs and the SMTP host must be public, SMTP only on port 25, 465, 587 or 2525; otherwise 422 invalid_channel with the messages in errors. Internal targets only work through a channel on the node.
curl -X POST "https://ipv64.net/api/v2/hc2/channels" -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "ntfy", "name": "ntfy phone", "default": true, "config": {"server": "https://ntfy.sh", "topic": "my-secret-topic"}}'
With /send, an event_id that was already sent successfully in the last 24 hours is not sent again (response duplicate: true). For SMS, sms_left in the response tells how many SMS are left today (null = unlimited).
Maintenance windows
| Method and path | Effect |
|---|---|
GET /hc2/maintenance, GET /hc2/maintenance/{id} | maintenance windows |
POST /hc2/maintenance | create → 201 |
PATCH /hc2/maintenance/{id} | change |
DELETE /hc2/maintenance/{id} | delete → 204 |
Fields: name, start_at and end_at (ISO 8601, required, end after start), repeat (none, daily, weekly, monthly), repeat_until, tokens (list of check tokens, empty = all checks) and active (default true).
curl -X POST "https://ipv64.net/api/v2/hc2/maintenance" -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Proxmox updates", "start_at": "2026-11-01T03:00:00+01:00", "end_at": "2026-11-01T04:00:00+01:00", "repeat": "weekly"}'
Nodes
| Method and path | Effect |
|---|---|
GET /hc2/nodes | nodes with health checks sync: node_id, name, location, version, timezone, modules, remote_mgmt, online, last_seen, last_sync, outbox_size, conflicts, linked_at |
GET /hc2/nodes/{node_id} | one node with its local and hybrid checks |
PATCH /hc2/nodes/{node_id} | name, location, remote_mgmt (off, read, full) |
POST /hc2/nodes/link | link a node with a code: code (8 characters), optional location, remote_mgmt (default full) → 202; the node fetches its credentials itself within about 10 seconds. Unknown or expired code: 404 code_unknown |
DELETE /hc2/nodes/{node_id} | disconnect the health checks sync; keep_checks in the JSON body (default true) continues checks of only this node online where possible → {"unlinked": true, "checks_kept_online": 2, "checks_deleted": 1} |
POST /hc2/nodes/{node_id}/commands | command to the node: name = run_check, pause, resume (each with args.token), repair (full sync) or resolve_conflict (args.id, args.keep = server/local) → 202 |
GET /hc2/nodes/{node_id}/commands | last commands (limit 1–200, default 20) with done_at, ok, output |
GET /node64/nodes | the node overview: all Node64 containers of the account with modules, features (health checks sync, honeypot sensor, CDN64 tunnel, DynDNS), sync state and removed |
The node only accepts commands if its remote management allows it (see Remote management). Nodes register themselves with POST /node64/hello; you do not need that in your own scripts.
Found a mistake or missing something? Tell us in the v64.tech forum. · Updated: 2026-11-04