Skip to main content

No matching sections.

Migrate from API v1

If you already have a v1 integration, this page is the map: for each thing you do today, what does the same job in v2. v1 is not going away on a date we have announced - this is a guide for moving when it suits you, not a countdown.

Before you begin

The two APIs are separate surfaces, not two dialects of one. v2 is not a wrapper over v1 and does not call it: different paths, different request and response shapes, its own authentication, its own error format. Moving an integration is a port, not a configuration change - budget for it as such.

v1 keeps working, and there is no removal date. Existing integrations are not on a clock. What v1 will not get is new capability: maintenance windows, webhooks, contact groups, status pages, instant checks, delta sync and the bulk job doors exist only in v2, and everything built from here lands there.

Nothing forces a big-bang cutover. The two APIs authenticate independently, so a client can hold a v1 token and a v2 token at once and move endpoint by endpoint - reads first is the usual order, since they are the easy half and prove your token, host and parsing before any write is at stake.

API v1API v2
Base URL https://api1.host-tracker.com/ (or the proxied www.host-tracker.com/api/web/v1/) https://api2.host-tracker.com
Reference The v1 reference, one page, eleven languages The interactive reference generated from the OpenAPI document, plus these guides
Credential A 48-hour ticket exchanged for a login and password, or HTTP Basic A scoped Bearer token you mint once in the web UI

Authentication

This is the one change every v1 client has to make, and it is worth making first.

v1 exchanges credentials for a token at runtime: POST users/token with a login and password returns a ticket valid for 48 hours (HTTP Basic on every call is accepted too). The token is unscoped - it can do everything the account may do - and forLogin mints one that acts on behalf of a super-account.

v2 has no credential-exchange endpoint at all. A token is created in the web interface, on the API page of your account, with the scopes it needs and an optional IP allow-list; left alone it lasts ten years. You send it on every call and never handle a password in code again:

v1
curl -X POST 'https://api1.host-tracker.com/users/token' \
  -H 'Content-Type: application/json' \
  -d '{"login":"[email protected]","password":"…"}'
# then, for the next 48 hours:
curl 'https://api1.host-tracker.com/tasks' \
  -H 'Authorization: bearer TICKET'
v2
curl 'https://api2.host-tracker.com/monitor' \
  -H 'Authorization: Bearer YOUR_TOKEN'
  • A script that logs in at runtime must stop doing that. Mint the token once, keep it where you keep your other secrets, and send it. There is nowhere to POST a password.
  • Tokens are scoped, and a read leaf never implies a write leaf. Grant the integration exactly the actions it performs; a call whose token lacks the scope answers 403 missing_scope naming what was required and what was granted. See Authentication & scopes.
  • There is no forLogin twin. Where a subaccount used impersonation to work inside a super-account, the v2 answer is a token minted by that account with the narrow scopes the integration needs.
  • The reference tier is anonymous in v2. The agent, pool, IP and type catalogues need no token at all - in v1 most of them required one.

Concept map

Most of the port is renaming. The vocabulary changed once, consistently.

v1v2Note
taskmonitorSame thing, throughout: paths, filters, events.
subscriptionalert subscription and report subscription One combined list became two families with their own endpoints.
info= bitmaskexpand= names Comma-separated tokens, per endpoint, with a 422 naming the allowed set.
ticket from users/token pre-minted scoped Bearer tokenSee above.
agent pool (agentPools) pool (locations.pools) Same pool ids; a monitor's pools moved into a locations object.
outages / incidentspan / incident /span is the raw down-window feed, /incident the episode feed with comments and rechecks.
fullLog=trueGET /monitor/incident/{id}/check The failing checks inside one episode, still package-gated - see Statistics and results.
contact type (4)contact type (10) IM is retired; messenger bots, webhooks and web push are contact types now.
ISO strings, or a dual Unix + formatted pairUnix seconds Integers only, in both directions. No formatted twin, no ISO, no milliseconds.
unpaginated listscursor paging {data, nextCursor, hasMore} on every list. See The query surface.
X-HT-Reason + a plain-text body application/problem+json (RFC 9457) Branch on the stable code member. See Errors.
synchronous bulkjob envelopes and delete receipts A bulk write answers 202 with a jobId you poll. See Jobs & idempotency.
-new in v2 Maintenance windows, webhooks, contact groups, status pages, instant checks (/check), delta sync and generated reports have no v1 twin.

Tasks to monitors

v1What it didv2What to know
GET tasks/types 15 PascalCase type names GET /monitor/type 14 lowerCamel tokens (http, sslExp, …), anonymous, and each row carries what the old flat list could not: creatable, attachable, minInterval, requiresPool, entitlement, presets. RusRegBL is gone as a type - it is type: "http" with settings.preset: "bl:ru".
GET tasks/intervals accepted intervals, in minutes GET /accountlimits.intervals Seconds. Per-type floors ride minInterval on /monitor/type. domainExp, sslExp, dnsbl and webRisk run on a fixed cadence and take no interval at all.
GET tasks the whole list, unpaginated, with info= embeds GET /monitor Cursor-paged. info= becomes expand= (see the two sub-tables below).
GET tasks/{id} one task, 200 null when absent GET /monitor/{id} A missing monitor is 404, not a 200 carrying null. Clients that tested for null must test the status.
POST tasks/http · ping · port · rusbl four typed create paths POST /monitor One door for all 14 types; the type is a body member. Request shape differs - notably agentPools[] is locations.pools, and an empty pool list is refused rather than treated as "anywhere" (send ["allworld"]). POST tasks/rusbl was already dead in v1 (TaskTypeDiscontinued).
PUT tasks update one, or every task a filter matched, synchronously PATCH /monitor/{id}; in bulk POST /monitor/bulk-update-validate then POST /monitor/bulk-update The bulk door answers 202 with a job to poll - a mass edit is no longer one blocking call. Tags are deltas (addTags/removeTags), not a whole-list replacement.
DELETE tasks/{id} and by filter delete one, or a filtered set DELETE /monitor/{id}; in bulk POST /monitor/bulk-delete-validate then POST /monitor/bulk-delete The single delete answers a receipt listing what went with it. The bulk delete requires the expectedCount the validate call reported and refuses with 409 selection_mismatch if the selection drifted in between - the anti-wipe guard v1's "empty filter is refused" rule was reaching for.
POST tasks/$batch a heterogeneous batch of creates, updates and deletes no successor Split it into the homogeneous bulk doors above, which are asynchronous and report per-item outcomes.

The info= bitmask becomes expand=

v1 infov2
subscriptions (1) expand=subscription on GET /monitor
stats (2) expand=uptime for the figure on the row, or GET /monitor/result/summary for the full aggregate
results (4) expand=lastResult for the latest one; GET /monitor/result (or GET /monitor/{id}/result) for a window
attachedResults (8) expand=attached for the flags, and GET /monitor/{id}/attached for the attached checks' own results

The full /monitor vocabulary is wider than v1's four bits - settings, attached, subscription, lastIncident, maintenance, lastResult (and its .metrics/.recheck children), uptime, spans, summary, count - and an unknown token is refused with the allowed list rather than ignored.

Filters

v1v2Note
ids / excludeIds id= No exclude form. (includeId= exists but is the opposite: a union that keeps named rows visible whatever the other filters say.)
taskType / taskTypes type=lowerCamel tokens; list-accepting.
url / urls + urlSearchLike url= + like=true like without url is refused, never silently ignored.
name / names + nameSearchLike q= One case-insensitive substring over name and url.
status=Enabled|Disabled enabled=true|false enabled is the configured flag. For "not actually being monitored", which includes an over-limit monitor whose flag is still true, use state=paused.
lastState (bool) state=up,down,paused,maintenance Four states, any combination - a monitor inside a maintenance window is no longer simply "up".
tagstag=Exact match, any-of.
openStatopenStat=Unchanged.
interval / intervals no filter Read interval (seconds) off the rows instead.
-preset=bl:ru, updatedSince=, sort=, fields= New: the Russian-blacklist selector, delta sync, explicit ordering and field projection.

Contacts

v1What it didv2What to know
GET contacts/types ["Email","IM","SMS","VoiceCall"] GET /contact/type Anonymous, and ten types: email, sms, voiceCall, http (a webhook contact), telegram, viber, facebook, googleChat, discord, webPush. IM is retired. Each row also carries its gateways, whether it can carry reports, and its accepted alert delays.
GET contacts/delays the accepted alertDelay values folded into GET /alert/type (summary.alertDelays), GET /contact/type and GET /account (limits.alertDelays) Still minutes - the one duration on the surface that is not seconds, because it is a ladder of allowed values rather than a free number. Same ten values, now sorted: [0,3,5,15,30,60,180,360,720,1440]. An off-ladder value is 422 invalid_alert_delay with allowed[].
GET contacts/sms/gateways, contacts/im/gateways gateway ids the gateways[] member of each /contact/type row IM has no successor.
GET contacts, contacts/{id} list and read, with info=subscriptions GET /contact, GET /contact/{id} Cursor-paged; expand=subscription|template|group|summary|count.
POST contacts/email · sms · voice three typed create paths POST /contact One door, type in the body. A duplicate address binds to the existing contact and answers 200 instead of v1's SimilarExists refusal; an email whose domain has no MX is refused with undeliverable_domain.
PUT contacts update by id or filter PATCH /contact/{id}; in bulk POST /contact/bulk The bulk door is a job and takes mixed creates, updates and deletes. An address collision on update is 409 duplicate_contact. Watch one semantic change: activePeriod.days: [] means paused, not "every day".
DELETE contacts/{id} and by filter delete one or a filtered set DELETE /contact/{id}; in bulk POST /contact/bulk-delete-validate then POST /contact/bulk-delete Same two-phase expectedCount guard as monitors.
PATCH contacts/{id}/code resend the confirmation code POST /contact/{id}/confirmation Rate-limited per contact and per account; a refusal is 429 with Retry-After.
POST contacts/{id}/code confirm with the code POST /contact/{id}/confirmation/verify Same body: the code the contact received.
POST contacts/$batchsynchronous batch POST /contact/bulkAsynchronous job.

Alerts and subscriptions

v1 had one subscription list carrying both alert types and report types. v2 has two families with their own endpoints, because they answer different questions and have different rules - reports go only to email contacts, and their frequency is a package matter.

v1What it didv2What to know
GET subscriptions/alertTypes Up, Down, RepeatedlyDown GET /alert/type lowerCamel: up, down, repeatedlyDown. The alert-delay ladder rides its summary.
GET subscriptions/reportTypes Day … Year GET /report/type The report catalogue: each row's frequencies[] holds daily, weekly, monthly, quarterly, yearly, beside its formats and sections.
GET subscriptions one flat list of alert and report subscriptions GET /alert and GET /report Two calls, merged client-side if you really want one list. Each family also has grouped views (/alert/by-monitor, /alert/by-contact, and the report twins) and nested reads (GET /monitor/{id}/alert, GET /contact/{id}/alert, …). Filters are prefixed by side: monitor.type=, contact.confirmed=.
POST / DELETE subscriptions cross-join add/remove, 204; an omitted side meant "all" POST /alert/bulk and POST /report/bulk One door per family, taking {create[], delete[], allMonitors?, allContacts?} - so a gesture that touched both families is two calls and no longer one transaction. The wildcard is now explicit rather than an omitted member. A selection is capped at 2 000 enumerated rows (a wildcard side does not count); create and delete naming the same pair is refused; an explicitly named non-email contact on a report is 422 unsupported_report_channel; a frequency your package does not include is 403 package_limit.

Statistics and results

v1What it didv2What to know
GET stats uptime aggregates GET /monitor/result/summary One endpoint for the account roll-up (groupBy=account) and the per-monitor and per-bucket breakdowns. Read uptimePercent rather than recomputing it from the seconds beside it - it is the figure every other surface reports.
-- the same endpoint's sla= parameter New in v2: score a window against a target. The three members are slaTarget, slaMet and errorBudgetSecRemaining (seconds of downtime still affordable; negative means overspent). All three are omitted when no target applies - a monitor's own slaTarget, or sla= on the request.
GET outages down windows with event numbers and comments GET /monitor/{id}/span, or GET /monitor/incident for the account-wide feed Spans are the raw down windows; incidents are the episodes, with severity, state, duration and comments (POST /monitor/incident/{id}/comment).
GET incident failure events GET /monitor/incident, GET /monitor/{id}/incident, GET /monitor/incident/{id} Cursor-paged, filterable by state, severity and the usual monitor filters. expand=recheck adds the recheck constellation - which locations confirmed the failure and which disagreed - and needs no entitlement.
GET incident?fullLog=true every failing check of an outage GET /monitor/incident/{id}/check The same capability, its own endpoint, and the same package entitlement: an account without it is refused with 403 package_limit naming apiFullLog, where v1 answered ApiFullLogNotAllowed. Rows are result rows, so each links straight to its result and snapshot; expand=metrics decodes the measurements and the assertions that failed.
GET incident/snapshot the stored response body, addressed by task + check + timestamp GET /monitor/{monitorId}/result/{id}/snapshot Addressed by the result's own id, so resolve the result first - from the incident's check list or GET /monitor/{id}/result.
GET results/http averaged http timings GET /monitor/result/summary?metrics=responseTime,dns,connect,tls,ttfb,transfer Bucketed series of typed points {t, value, p95, samples}. headTime and dataTime are now ttfb and transfer; tls is new.
GET cr/httpResponseTime a raw [unix, ms] series metrics=responseTime on the summary, or GET /monitor/{id}/result for the raw checks There are no two-element tuples on the v2 wire; every point is a named object.
A raw-result read is capped at 30 days per request. A wider window is sliced into requests, not asked for in one - and a cursor never crosses the boundary. Aggregates have their own bounds (groupBy=account covers up to 30 days and 2 000 monitors). See Results, uptime & SLA.

Agents

v1What it didv2What to know
GET agentsthe agent list, authenticated GET /agent Anonymous in v2, and each row carries more: ip, ipv6, visible. The v1 touched parameter has no twin.
GET agents/poolsthe pool list, authenticated GET /agent/pool Anonymous too, but auth-aware: a token adds the account's hidden pools and its limits. This endpoint accepts no query parameters at all - even limit is 422 unknown_parameter.
GET agents/ips the whitelist as text/plain, or JSON on Accept GET /agent/ip JSON only. There is no text/plain mode - Accept: text/plain still answers application/json - and the rows are objects, not bare strings: {ip, family, country, addedAt}. A firewall script that piped the plain list into a rule now needs to pluck .data[].ip.
the v1 one-liner, ported
curl -s 'https://api2.host-tracker.com/agent/ip' | jq -r '.data[].ip'

Conventions that changed

Conventionv1v2
Dates ISO-8601 UTC strings on tasks; a dual pair on statistics - a …Unix integer beside a string formatted to the account's profile Unix seconds, one integer, everywhere, in both directions. Formatting is the client's job.
Errors 4xx with the machine code in the X-HT-Reason header and prose in the body application/problem+json with code, title, detail and an errors[] array pointing at the offending member. Branch on code.
Unknown input ignored refused - 422 unknown_parameter / 422 validation_failed, naming what is accepted. A typo can no longer silently leave a default in place.
Pagingnone - a list was the whole list {data, nextCursor, hasMore}; pass nextCursor back as cursor. Never build a cursor yourself.
Rate limiting 6 requests/second and 120/minute per account and endpoint; 429 RequestThrottled Per-scope quota with RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on the answer, 429 quota_exceeded plus Retry-After when spent, and GET /account/quota to check headroom without spending any.
Request tracing- Every response carries x-request-id. Log it; it is what support needs to find your call.
XMLAccept: application/xml returned an XML body JSON only. An XML Accept header is answered with JSON.
Method override X-HTTP-Method-Override let a POST stand in for PUT/DELETE/PATCH Not supported. Send the real verb.
Retries- Idempotency-Key is honoured on writes and required on the destructive ones. See Jobs & idempotency.

What has no successor

  • POST users/token - v2 mints tokens in the UI, never from credentials, and there is no forLogin impersonation twin.
  • HTTP Basic - the Bearer token is the only scheme.
  • POST tasks/$batch and POST contacts/$batch - use the per-family bulk doors, which are asynchronous jobs with per-item outcomes.
  • IM contacts and their gateways - retired. The messenger channels are their own contact types now.
  • XML responses and X-HTTP-Method-Override.
  • The interval filter on the monitor list, and the exclude* inversions of the id, type, url and name filters.

Where to go next

  • Quickstart - mint the token and make the first call.
  • The query surface - cursors, filters, expand and fields, once for every list.
  • Monitors - the per-type settings a ported create needs.
  • Errors - what to branch on now that X-HT-Reason is gone.
  • The v1 reference - still here, still accurate for the API you are leaving.