No matching sections.
Host-Tracker API documentation
Two entry points: the endpoint-by-endpoint reference, generated from the API itself, and the guides that explain the parts a reference cannot. Below them, a quick start for each API version.
API v2 - current
The current API: token-authenticated JSON over HTTPS at https://api2.host-tracker.com, covering monitors, incidents, contacts, alerts, maintenance windows, reports, status pages, instant checks and webhooks. New integrations belong here.
Five steps from nothing to a monitor that pushes its state changes to your own endpoint. Every request and response on this page is a real exchange with the API, not an illustration.
1Mint a token
v2 has no OAuth flow and no client registration. A token is a credential you create in the web UI and store: open your profile, go to the API tokens panel, choose the scopes the integration needs, and copy the token. It is shown once and is not stored on our side - if you lose it, mint another.
| Setting | What it does |
|---|---|
| Scopes | What the token may do. Required - a token with no scope is refused. See below. |
| Expiration | A preset window or a specific date. Left alone, a token lasts ten years. |
| IP allow-list | Up to ten exact addresses or from-to ranges. A call from
anywhere else answers 403 ip_not_allowed. Empty means "from anywhere". |
Scopes: leaf and family
There are thirteen leaf scopes, each an action on a domain:
monitor:readmonitor:writecontact:readcontact:writesubs:readwebhook:readwebhook:writestatuspage:readstatuspage:writeaccount:readaccount:writecheck:readcheck:write
and seven family scopes, one per domain, which are the bare domain name:
monitorcontactsubswebhookstatuspageaccountcheck
- A family satisfies every leaf in it: holding
monitorpasses a check formonitor:readand formonitor:write. - The reverse never holds, and one leaf never implies another:
monitor:writedoes not satisfymonitor:read. If your integration both reads and writes monitors, grant both leaves - or the family. - A family scope widens by itself when its domain later gains an action. That is the trade-off for the convenience; prefer leaves when you want the grant pinned to exactly today's surface.
A call whose token lacks the scope answers 403 missing_scope, and the problem names
both sides so you can fix it without guessing:
{
"type": "https://api2.host-tracker.com/problems/missing-scope",
"title": "The token does not carry the scope this operation requires.",
"status": 403,
"code": "missing_scope",
"errors": [
{ "required": "monitor:write", "granted": ["monitor:read"] }
]
}
2First call
List your monitors. Any endpoint would do; this one needs only
monitor:read and proves the token, the host and the header are all right.
curl 'https://api2.host-tracker.com/monitor?limit=1' \
-H 'Authorization: Bearer YOUR_TOKEN'
{
"data": [
{
"id": "4e49d7a2-4ab5-45e2-b9f8-1d59f505ad45",
"type": "http",
"name": "Marketing site",
"url": "https://example.info",
"state": "down",
"since": 1785670783,
"enabled": true,
"tags": [],
"created": 1785670783,
"updated": 1785670783,
"slaTarget": null
}
],
"nextCursor": "eyJrIjoiMDAwMDAwMDAwMTc4NTU2NTY1NCIsImki…",
"hasMore": true
}
That is the collection envelope. To walk the rest, send the nextCursor value back as
cursor and stop when hasMore is
false. Never construct a cursor yourself - it is opaque and
tamper-evident: it carries a checksum over its own payload, so a cursor that was edited,
truncated or handed to a build that no longer understands it is refused with
422 invalid_cursor rather than silently answering the wrong page. That checksum is
not a signature - it uses no secret, and it is there to catch corruption, not a determined forger. It
does not need to be more than that: every v2 query is re-scoped to your own account on the server, so the most
a hand-made cursor can do is reorder results you were already entitled to see.
Every response carries x-request-id
Success or failure alike. Log it; it is the same value a 500 puts in its
traceId, and it is what support needs to find your call.
HTTP/2 200
content-type: application/json; charset=utf-8
x-request-id: req_85499279fbda4d78919a1ea06766013b
Endpoints whose scope carries a countable quota also answer
RateLimit-Limit, RateLimit-Remaining and
RateLimit-Reset, and a 429 adds
Retry-After. GET /account/quota reports the same
numbers on demand, per scope, without spending anything.
3Pick where the check runs from
A monitor runs from one or more agent pools - named groups of monitoring locations. Ask the API which ones your account can use rather than hardcoding names; the list is account-shaped and it also tells you the minimum number of agents a monitor must be given.
curl 'https://api2.host-tracker.com/agent/pool' \
-H 'Authorization: Bearer YOUR_TOKEN'
{
"data": [
{
"id": "allworld",
"name": "All world",
"agents": { "net": 6, "waterfall": 4 },
"children": ["easteurope", "northamerica", "russia", "westeurope"],
"parents": []
},
{
"id": "northamerica",
"name": "North America",
"agents": { "net": 1 },
"children": [],
"parents": ["allworld"]
}
],
"nextCursor": null,
"hasMore": false,
"summary": { "minAgents": { "default": 1 }, "defaults": {}, "presets": [] }
}
limit. Sending
one answers 422 unknown_parameter with an empty
allowed list, which is the API saying "none are accepted here", not "I forgot to
tell you". The whole pool list fits in one response by design.
4First monitor
The smallest useful http monitor. Only type and
url are required; interval and
locations.pools are worth sending explicitly, because the defaults are account
settings you probably do not want to inherit silently.
curl -X POST 'https://api2.host-tracker.com/monitor' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: quickstart-monitor-1' \
-d '{
"type": "http",
"url": "https://example.com",
"name": "Docs quickstart monitor",
"interval": 60,
"locations": { "pools": ["westeurope"] }
}'
{
"id": "79c21af5-0f43-4262-b7b6-841cd3b39b19",
"type": "http",
"name": "Docs quickstart monitor",
"url": "https://example.com",
"state": "up",
"since": 1785712608,
"enabled": true,
"tags": [],
"updated": 1785712608,
"interval": 60,
"slaTarget": null,
"locations": { "pools": ["westeurope"], "excludedAgents": [] },
"settings": { "followRedirect": true },
"attached": { "dnsbl": false, "sslExp": false, "domainExp": false, "webRisk": false },
"created": 1785712608,
"subscription": []
}
A write returns the created resource in full, so there is no verification read to make. Note what the response
tells you that the request did not: the type's default settings, the
attached sub-checks that are available and currently off, and the canonical
id - also given in the Location header.
"interval": 300
answered 422 on the account these excerpts came from, and said exactly what
was allowed:
{
"code": "invalid_interval",
"status": 422,
"errors": [ { "pointer": "/interval", "value": 300, "allowed": [60] } ]
}
errors[0].allowed and retry - never hardcode an interval you have not been
told is permitted.
(url, type) - so running this step a second time is a
409, not a second monitor. That is the answer to "why did my quickstart work
once", and the refusal is useful rather than just a no: it names the row you already have.
{
"code": "duplicate_monitor",
"status": 409,
"errors": [ {
"existingId": "79c21af5-0f43-4262-b7b6-841cd3b39b19",
"key": { "url": "https://example.com", "type": "http" }
} ]
}
existingId to adopt the monitor you already have, or change the url. Note
the difference from a keyed retry: replaying the exact same request with the same
Idempotency-Key replays the original 201 instead, with
Idempotency-Replayed: true - the 409 is what a fresh request for an
address you already monitor gets.
Idempotency-Key above is optional here but always a good idea. On nine
operations it is mandatory. See Jobs & idempotency for the required list
and the replay semantics.
5First webhook
Register an https endpoint of your own and choose which events reach it. The
scope member is required and decides which monitors the subscription
covers - exactly one of {"all": true},
{"monitorIds": [...]} or {"tags": [...]}.
curl -X POST 'https://api2.host-tracker.com/webhook' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://hooks.example.com/host-tracker",
"events": ["monitor.down", "monitor.up", "incident.opened"],
"scope": { "all": true },
"name": "Docs quickstart webhook"
}'
{
"id": "0c3c7b07-cecb-43dd-9b76-8516d3b9c771",
"url": "https://hooks.example.com/host-tracker",
"events": ["monitor.down", "monitor.up", "incident.opened"],
"scope": { "all": true, "monitorCount": 8 },
"name": "Docs quickstart webhook",
"enabled": true,
"consecutiveFailures": 0,
"secret": {
"set": true,
"updatedAt": 1785712680,
"value": "whsec_tPlGftcqGUhzFSMwQgQEMPPiBYChRnIP7ku03iytipE="
},
"created": 1785712681,
"updated": 1785712680
}
secret.value is returned exactly once, here on the 201. Every later read
answers only { "set": true, "updatedAt": … }. Store it now; if you lose it, rotate
it. It is the key your endpoint verifies deliveries with.
What arrives
Every delivery is a POST of the same envelope, with the event's own payload under
data. This is a real delivery captured at a receiving endpoint:
Content-Type: application/json; charset=utf-8
HT-Event: monitor.created
HT-Delivery: d_186bb95f7bf1494ba3d249fae486ce5f
HT-Webhook: a5fc0f43-0541-4d05-8af0-28b46f6ded6a
HT-Attempt: 1
HT-Signature: t=1785672303,v1=f1e2025a9964d762a9df07a3961063e08200c1bc0ce45b54372aa9f66bc7a0c5
{
"id": "d_186bb95f7bf1494ba3d249fae486ce5f",
"event": "monitor.created",
"occurredAt": 1785672303,
"apiVersion": "v2",
"data": {
"id": "fb7d775c-a4ea-4f57-84b9-37ff98de34f8",
"type": "http",
"name": "bat-hooked",
"url": "https://hooked.example.com",
"state": "up",
"since": 1785672301,
"interval": 60,
"locations": { "pools": ["westeurope"], "excludedAgents": [] }
// … the rest of the monitor representation
}
}
Two things to do before you trust a delivery: verify the signature, and
deduplicate on HT-Delivery - that value is stable across every
retry of the same delivery, so it is the idempotency key for your receiver. Both are covered in the
webhooks guide.
Test it without waiting for an outage
curl -X POST 'https://api2.host-tracker.com/webhook/WEBHOOK_ID/test' \
-H 'Authorization: Bearer YOUR_TOKEN'
It sends a synthetic monitor.down through the real delivery path and answers with
what happened - including the signature it sent, so you can check your verifier against it. A failure is
reported plainly rather than hidden:
{
"deliveryId": "d_98ccee49a7314948a00016215842575b",
"event": "monitor.down",
"latencyMs": 37,
"signatureSent": "t=1785712688,v1=fd916924a11419701813d4be56de2fff7e221a0193ecca37876a49af9df6dfca",
"outcome": "failed",
"error": "The SSL connection could not be established, see inner exception."
}
That particular answer is what a self-signed certificate looks like from our side: deliveries require https with a certificate that validates against the public trust chain. See Webhooks for how to test locally without one.
API v1 - legacy
v1 is the older REST API at https://api1.host-tracker.com/ (also reachable through www.host-tracker.com/api/web/v1/). It manages monitoring tasks, contacts, subscriptions, statistics, outages, incidents and agents, and it authenticates by exchanging a login and password for a 48-hour token.
- The v1 reference - the complete v1 documentation, in eleven languages.
- Migrate from v1 - what each v1 endpoint became in v2, endpoint by endpoint, plus the conventions that changed.