Skip to content
RU

API Enterno.io

Programmatic access to all enterno.io tools: HTTP headers, DNS, SSL, ping, IP geolocation, health scores, monitors and status pages. REST API with JSON responses.

API v1 & v2 sunset 2026-10-01 — migrate to v4 before then. See the migration guide for the step-by-step path. v3 is frozen but supported; v4 is the current version.
Three entry paths converge on one core: an open dotted line with no lock, a line through a key, and a line through a protocol node. From the core, connections fan out to the individual tools.

Open API — no key, no signup

One GET, plain text back. Paste it into a terminal, a browser, a CI script or a spreadsheet and it answers — nothing to read first, nothing to sign up for. The keyed API below does more; this is the part you can try in the next five seconds.

GET /api/open/<tool>?q=<target> no authentication
curl "https://enterno.io/api/open/headers?q=https://example.com"
curl "https://enterno.io/api/open/ssl?q=example.com"
curl "https://enterno.io/api/open/dns?q=example.com&types=MX,TXT"

/api/open/ with no tool prints the catalogue, so you can find your way around from the command line without coming back here.

Tools

Tool q = Description Extra
headers url HTTP response headers
dns domain DNS records types
ssl host TLS certificate port
ip host IP geolocation
ping host ICMP reachability
whois host Domain registration
mx domain Mail exchangers
asn query ASN / network owner
dkim domain DKIM public key selector
propagation domain DNS propagation across resolvers
http-status url Status code only
redirects url Redirect chain max
csp url Content-Security-Policy
cors url CORS policy origin
cookie url Cookie flags
robots domain robots.txt rules test_url
schema url Structured data
protocol url HTTP version and TLS
url-expand url Resolve a short link

Formats

&format=text (default) for a shell, &format=json for code, &format=csv for a spreadsheet. Text output ends with a # comment naming the source, so it survives being piped through grep and still says where it came from when someone pastes it into an issue.

Limits, and what is not here

Rate-limited per IP and metered by the same daily allowance the site uses for anonymous visitors. Answers are cached for 60 seconds and readable from any origin.

Two families of tools stay behind a key. Anything with a real bill attached — headless browser, site crawl, paid third-party lookups — because a keyless caller can repeat it without limit and without a name to bill. And anything that probes a host the way its owner would read as a scan: port sweeps, subdomain enumeration, malware and abuse lookups. Keyless, those are not diagnostics; they are a way to scan somebody else from our address. ping is here as reachability only, never as its port-scan mode.

Need a tool that is not on the list, or the same call without the anonymous ceiling? That is what the keyed API below is for.

What to build with it

Each of these is a whole use of the API, not a demo of one endpoint.

1. Fail a build before the certificate does

A certificate that expires on a Saturday is discovered by customers. Put the check in CI and it is discovered by a pipeline instead.

# .gitlab-ci.yml / GitHub Actions — no key needed
DAYS=$(curl -s "https://enterno.io/api/open/ssl?q=$SITE&format=json" | jq -r '.certificate.days_left')
[ "$DAYS" -gt 14 ] || { echo "TLS expires in $DAYS days"; exit 1; }

2. A spreadsheet that checks itself

Google Sheets reads CSV over HTTP natively. One formula per row, refreshed on open — no script, no add-on, no key.

=IMPORTDATA("https://enterno.io/api/open/ssl?q=" & A2 & "&format=csv")

3. Uptime from your own cron

When you would rather own the schedule and the alerting, and only borrow the check.

*/5 * * * * curl -sf "https://enterno.io/api/open/http-status?q=https://example.com" \
  | grep -q "^code: 200" || /usr/local/bin/notify "example.com is not answering 200"

4. Give an AI agent a tool it can actually call

An agent cannot sign up for an API key. A keyless GET that returns plain text is the shape a tool-using model handles without a wrapper — and for a full, typed tool list there is an MCP server.

5. Zapier, Make, n8n

Every one of them has a "GET a URL" step and struggles with anything else. &format=json drops straight into the next node.

6. One-liners worth keeping

# which CDN is in front of it
curl -s "https://enterno.io/api/open/headers?q=https://example.com" | grep -i "server\|cf-ray"

# where does this short link really go
curl -s "https://enterno.io/api/open/url-expand?q=https://bit.ly/xxxx"

# is my SPF/DKIM actually published
curl -s "https://enterno.io/api/open/dkim?q=example.com&selector=default"

7. Embed a live result in someone else's page

The open tier is readable from any origin, so a status widget can be a few lines of fetch() with no proxy of your own. For a picture rather than data there are badges.

8. Auditing a list

For a handful of hosts, a shell loop and the open tier are enough. Past that the anonymous ceiling is the wrong tool — use a key and /api/v4/batch, which runs the list asynchronously instead of making you pace it yourself.

while read h; do
  printf '%s\t' "$h"
  curl -s "https://enterno.io/api/open/ssl?q=$h" | grep '^certificate.days_left:'
done < hosts.txt

Try it

Fire a request against your key and copy a ready-made snippet in one of 5 languages.

Stored in this tab's sessionStorage. Close the tab and the key is gone.

GET /api/v4/check — HTTP headers check


                

GET /api/v4/dns — DNS lookup


                

GET /api/v4/ssl — SSL/TLS certificate


                

GET /api/v4/ping — Ping + ports


                

GET /api/v4/ip — IP geolocation


                

Authentication

All API v4 requests require a key via X-API-Key header. Use X-Idempotency-Key to safely retry write requests.

Saved in the browser. Used by all forms below.

Get an API key by creating an account or in your dashboard.

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/webhooks"

Every response includes an X-Request-Id header for tracing.

Idempotency Key

Add X-Idempotency-Key: unique-id to any write request. Enterno.io returns the cached response for duplicate requests within 60 seconds.

curl -X POST "https://enterno.io/api/v4/webhooks" \
  -H "X-API-Key: YOUR_KEY" \
  -H "X-Idempotency-Key: create-wh-001" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-app.com/hook"}'

Response Format

API v4 uses the same JSON envelope as v3 with "api_version": "4.0" in meta.

{
  "data": { ... },
  "meta": {
    "request_id": "a1b2c3d4e5f6a7b8c9d0e1f2",
    "duration_ms": 8,
    "cached": false,
    "api_version": "4.0"
  }
}

On error:

{
  "error": {
    "code": "not_found",
    "message": "Resource not found"
  },
  "meta": {
    "request_id": "a1b2c3d4e5f6a7b8c9d0e1f2",
    "duration_ms": 2,
    "api_version": "4.0"
  }
}

Rate Limits

Rate limit information is included in response headers:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when rate limit resets
Retry-AfterSeconds to wait (only on 429)
Plan Requests/min Daily Limit Scopes
Free10100check, dns
Pro605 000check, dns, ssl, ip, ping, monitors, webhook
Business12050 000check, dns, ssl, ip, ping, monitors, webhook

Scopes

Each API key has a set of permitted scopes. A request to an inaccessible endpoint will return a 403 error.

Scope Endpoint Description
check/api/v4/check, /api/v4/health, /api/v4/audit-exportHTTP header check + health score
dns/api/v4/dnsDNS lookup + DNSSEC
ssl/api/v4/sslSSL/TLS check + chain details
ip/api/v4/ipIP geolocation
ping/api/v4/pingPing, port check, traceroute
monitors/api/v4/monitorsMonitor CRUD + status pages
webhook/api/v4/webhooks, /api/v4/eventsWebhook subscription and event log CRUD

Webhooks — Manage Subscriptions

Create webhook subscriptions to receive HTTP POST notifications when monitors, SSL certificates, or domains change state.

Supported event types

ScopeDescription
monitor.downMonitor went down
monitor.upMonitor recovered
monitor.degradedMonitor response degraded
ssl.expiringSSL certificate expiring soon
ssl.expiredSSL certificate expired
ssl.chain_changedSSL chain changed (issuer or fingerprint)
domain.expiringDomain expiring soon
domain.expiredDomain registration has lapsed
visual.changedVisual monitor spotted a change
testManual test event

Webhook limits by plan

Plan Max webhooks Event restrictions
Free1monitor.down, monitor.up, test
Starter5All events
Pro20All events
Business50All events

List webhooks

GET /api/v4/webhooks scope: webhook

Returns all webhook subscriptions for the authenticated user.

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/webhooks"

Create webhook

POST /api/v4/webhooks scope: webhook
NameTypeRequiredDescription
urlstringYesDestination URL (HTTPS required)
namestringNoSubscription name (optional)
secretstringNoHMAC-SHA256 secret for payload signature (optional)
eventsarrayNoEvent types to subscribe to (default: all)
curl -X POST "https://enterno.io/api/v4/webhooks" \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/hooks/enterno",
    "name": "Production alerts",
    "events": ["monitor.down", "monitor.up", "ssl.expiring"]
  }'

Response

{
  "data": {
    "id": 12,
    "name": "Production alerts",
    "url": "https://your-app.com/hooks/enterno",
    "secret": "ent_wh_sk_***",
    "events": ["monitor.down", "monitor.up", "ssl.expiring"],
    "is_active": true,
    "created_at": "2026-03-28T06:00:00Z"
  },
  "meta": {"request_id": "...", "duration_ms": 18, "api_version": "4.0"}
}

Update webhook

PUT /api/v4/webhooks?id=N scope: webhook
curl -X PUT "https://enterno.io/api/v4/webhooks?id=12" \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

Delete webhook

DELETE /api/v4/webhooks?id=N scope: webhook
curl -X DELETE "https://enterno.io/api/v4/webhooks?id=12" \
  -H "X-API-Key: YOUR_KEY"

Verifying webhook signatures

Every delivery to your webhook URL is signed with your shared secret. Two headers enable request-integrity + replay-protection checks:

Header Value Purpose
X-Enterno-Timestamp Unix timestamp (seconds) at dispatch Reject if > 300 s skew from receiver clock
X-Enterno-Signature-V2 sha256=<hex> HMAC-SHA256(secret, timestamp + "." + body)
X-Enterno-Signature sha256=<hex> Legacy — HMAC over body only, no replay protection. Kept for existing integrations. Will be removed 2026-07-01.

PHP verifier (recommended)

<?php
$secret  = getenv('ENTERNO_WEBHOOK_SECRET');          // your shared secret
$ts      = $_SERVER['HTTP_X_ENTERNO_TIMESTAMP'] ?? '';
$sig     = $_SERVER['HTTP_X_ENTERNO_SIGNATURE_V2'] ?? '';
$body    = file_get_contents('php://input');

// 1. Replay window — reject timestamps older than 5 minutes
if (abs(time() - (int)$ts) > 300) {
    http_response_code(400);
    exit('stale timestamp');
}

// 2. Compute expected signature
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);

// 3. Timing-safe compare
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('bad signature');
}

$payload = json_decode($body, true);
// …handle the event…

Node.js verifier

const crypto = require('crypto');

function verify(req, secret) {
  const ts   = req.headers['x-enterno-timestamp'];
  const sig  = req.headers['x-enterno-signature-v2'];
  const body = req.rawBody;                             // use a raw-body parser

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300)
    throw new Error('stale timestamp');

  const expected =
    'sha256=' + crypto.createHmac('sha256', secret)
                      .update(ts + '.' + body)
                      .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig)))
    throw new Error('bad signature');
}

Migration window: legacy X-Enterno-Signature (body-only, no timestamp) is still sent alongside V2 until 2026-07-01. If you still depend on it, migrate to V2 before that date.

Send test event

POST /api/v4/webhooks?action=test&id=N scope: webhook
curl -X POST "https://enterno.io/api/v4/webhooks?action=test&id=12" \
  -H "X-API-Key: YOUR_KEY"

Events — Delivery History

View webhook delivery attempts, retry counts, response codes, and failure reasons.

GET /api/v4/events scope: webhook
NameTypeDescription
webhook_idintegerFilter by webhook ID (optional)
event_typestringFilter by event type
statusstringFilter: delivered, retrying, failed, dead, pending
pageintegerPage number (default 1)
per_pageintegerItems per page, max 100 (default 25)
curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/events?webhook_id=12&status=failed"

Response

{
  "data": [
    {
      "id": 501,
      "webhook_id": 12,
      "event_type": "monitor.down",
      "status": "delivered",
      "http_status_code": 200,
      "attempts": 1,
      "created_at": "2026-03-28T06:05:00Z",
      "delivered_at": "2026-03-28T06:05:01Z"
    }
  ],
  "meta": {
    "request_id": "...",
    "duration_ms": 12,
    "api_version": "4.0",
    "pagination": {"total": 1, "page": 1, "per_page": 25, "pages": 1}
  }
}

Check — HTTP Header Check

Get HTTP response headers with detailed timing breakdown (DNS, connect, TLS, TTFB).

GET POST /api/v4/check.php scope: check

Parameters

NameTypeRequiredDescription
urlstringYesURL to check
methodstringNoGET, HEAD, POST (default: GET)
followstringNo0/1 (default: 1)
timeoutintegerNo1-30 (default: 15)
uastringNoCustom User-Agent

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/check.php?url=https://example.com"

Response

{
  "data": {
    "url": "https://example.com",
    "final_url": "https://example.com/",
    "method": "GET",
    "code": 200,
    "http_version": "HTTP/2",
    "ip": "93.184.216.34",
    "headers": [...],
    "timing": {"dns_ms":12,"connect_ms":45,"tls_ms":78,"ttfb_ms":120,"transfer_ms":142,"redirect_ms":0,"redirect_count":0},
    "elapsed_ms": 142
  },
  "meta": {"request_id":"req_01...","duration_ms":150,"cached":false,"api_version":"4.0"}
}

DNS — DNS Lookup

Get DNS records with optional DNSSEC validation.

GET POST /api/v4/dns.php Scope: dns

Parameters

NameTypeRequiredDescription
domainstringYesDomain name
typesstringNoComma-separated: A,AAAA,MX,NS,TXT,CNAME,SOA
dnssecstringNo1 to check DNSSEC status

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/dns.php?domain=example.com&dnssec=1"

SSL — SSL/TLS Check

Check SSL certificate with detailed chain information.

GET POST /api/v4/ssl.php Scope: ssl

Parameters

NameTypeRequiredDescription
hoststringYesHostname
portintegerNoPort (default 443)

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/ssl.php?host=example.com"

IP — Geolocation

Determine location, ISP and organization by IP address or domain.

GET POST /api/v4/ip.php Scope: ip

Parameters

NameTypeRequiredDescription
hoststringYesIP address or domain

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/ip.php?host=8.8.8.8"

Ping — Ping, Ports & Traceroute

Ping a host, check ports, or run a traceroute.

GET POST /api/v4/ping.php Scope: ping

Parameters

NameTypeRequiredDescription
hoststringYesHost or IP
actionstringNoping, ports, traceroute (default: ping)
countintegerNoPing count 1-10 (default 4)
portsstringNoComma-separated ports (for action=ports)
max_hopsintegerNoMax hops 1-30 (for action=traceroute)

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/ping.php?host=example.com&action=traceroute"

Health — Website Health Score

Comprehensive website health analysis: security headers (30pts), SSL/TLS (25pts), performance (25pts), best practices (20pts). Returns a score 0-100 with grade A+ to F.

GET POST /api/v4/health.php scope: check

Parameters

NameTypeRequiredDescription
urlstringYesURL to analyze

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/health.php?url=example.com"

Returns score 0–100, letter grade A+–F, Security Headers / SSL / Performance / Best Practices category breakdown, and prioritised recommendations. Cached 2 minutes per URL.

Monitors — CRUD

Create, read, update and delete uptime monitors via API.

List monitors

GET /api/v4/monitors.php scope: monitors
NameTypeDescription
pageintegerPage number (default 1)
per_pageintegerItems per page, max 100 (default 25)
statusstringFilter: up, down, unknown

Get single monitor

GET /api/v4/monitors.php?id=N

Returns monitor details with 20 most recent checks.

Create monitor

POST /api/v4/monitors.php
curl -X POST "https://enterno.io/api/v4/monitors.php" \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: create-mon-example-001" \
  -d '{
    "url": "https://example.com",
    "check_type": "http",
    "interval_minutes": 5,
    "expected_code": 200,
    "notify_email": true
  }'

Update monitor

PUT /api/v4/monitors.php?id=N
curl -X PUT "https://enterno.io/api/v4/monitors.php?id=5" \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"interval_minutes": 10, "is_active": false}'

Delete monitor

DELETE /api/v4/monitors.php?id=N
curl -X DELETE "https://enterno.io/api/v4/monitors.php?id=5" \
  -H "X-API-Key: YOUR_KEY"

Batch — async URL checker

Submit up to 100 URLs in one call (plan-dependent: free→5, starter→20, pro→50, business→100). Returns a job_id immediately; poll for results.

Submit a batch

POST /api/v4/batch.php scope: check
curl -X POST "https://enterno.io/api/v4/batch.php" \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": ["https://example.com", "https://example.org"],
    "type": "check"
  }'
{
  "data": {
    "job_id": "bq_01ARZ3...",
    "status": "pending",
    "type": "check",
    "total": 2,
    "poll_url": "/api/v4/batch.php?job_id=bq_01ARZ3...",
    "invalid_urls": []
  },
  "meta": {"request_id":"req_...","duration_ms":8,"api_version":"4.0"}
}

Poll status

GET /api/v4/batch.php?job_id=JOB_ID
curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/batch.php?job_id=bq_01ARZ3..."

Status — Public Status Page

Get status page data as JSON. No authentication required when slug is provided.

GET /api/v4/status.php?slug=SLUG public

Example

curl "https://enterno.io/api/v4/status.php?slug=my-company"

Response

{
  "data": {
    "title": "My Company Status",
    "slug": "my-company",
    "overall_status": "operational",
    "monitors_total": 5,
    "monitors_down": 0,
    "monitors": [
      {"id": 1, "name": "example.com", "status": "up", "response_time_ms": 120}
    ],
    "recent_incidents": []
  },
  "meta": {"request_id":"req_...","duration_ms":15,"cached":false,"api_version":"4.0"}
}

Every other tool

The endpoints above are the ones with hand-written handlers. Every other diagnostic tool on the site answers at /api/v4/<tool> under the same auth, the same envelope and the same rate limits — it runs the identical code the site runs, so a result here and a result on the tool page cannot disagree.

GET POST /api/v4/<tool> X-API-Key required
curl -H "X-API-Key: YOUR_KEY" \
  "https://enterno.io/api/v4/cookie?url=https://example.com"

# what this key may call, and what it may not
curl -H "X-API-Key: YOUR_KEY" "https://enterno.io/api/v4/tools"

/api/v4/tools answers for the key that asked: each row carries an allowed flag from that key's scopes, so a client building a menu knows which rows would 403 before it offers them.

Tools and scopes

Endpoint Parameter Scope Description
/api/v4/cookie url check Cookie flags and trackers
/api/v4/cors url + origin check CORS policy
/api/v4/csp url check Content-Security-Policy grade
/api/v4/http-status url check Status code and timing
/api/v4/redirects url + max, ua check Redirect chain
/api/v4/protocol url check HTTP version and TLS negotiation
/api/v4/mixed-content url check Mixed content on an HTTPS page
/api/v4/schema url check Structured data
/api/v4/robots domain + test_url check robots.txt rules
/api/v4/tech-detect url check Technology fingerprint
/api/v4/og-preview url check Open Graph and social preview
/api/v4/cms url check CMS detection
/api/v4/url-expand url check Resolve a short link
/api/v4/resolve-url url check Resolve a URL to its final target
/api/v4/wayback url check Wayback Machine history
/api/v4/security url check Security headers grade
/api/v4/seo-audit url check On-page SEO audit
/api/v4/ai-check domain check AI readiness
/api/v4/carbon url check Carbon footprint estimate
/api/v4/performance url check Performance metrics
/api/v4/pagespeed url + strategy check PageSpeed
/api/v4/mx domain dns Mail exchangers
/api/v4/dkim domain + selector dns DKIM public key
/api/v4/email-check domain dns SPF, DKIM and DMARC
/api/v4/email-header headers dns Parse raw email headers
/api/v4/propagation domain + type dns DNS propagation across resolvers
/api/v4/smtp-test target + port dns SMTP banner and STARTTLS
/api/v4/whois host dns Domain registration
/api/v4/asn query ip ASN and network owner
/api/v4/reverse-ip ip ip Domains sharing an address
/api/v4/traceroute host ping Network route

Scopes are the ones keys already carry, not one per tool: a key issued for check can call the cookie and CSP tools without being reissued. The mapping follows what a tool reads rather than what it reports — the CSP grader fetches a page over HTTP, so it is check, however security-shaped its output looks.

Port sweeps, subdomain enumeration and malware lookups are not here. A key names who is calling; it does not say they own the host being probed, and that is the permission that matters. Those capabilities live in the products that gate on proven ownership.

MCP — for AI agents

The same tools again, this time over the Model Context Protocol, so an agent in Claude Desktop, Cursor or Zed can call them without anyone writing HTTP code. It is the same handler underneath as the REST endpoints above and the tool pages on the site — three ways in, one implementation, so the three cannot disagree about what a check returns.

POST /mcp JSON-RPC 2.0 · Authorization: Bearer
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"check_ssl","arguments":{"host":"example.com"}}}

43 tools are exposed — every REST tool above plus the platform queries. tools/list returns the current set with argument schemas; ready-made config snippets for each agent are on the MCP server page.

An agent with no key at all is not stuck: /api/open/ above needs none, answers plain text, and is listed in our agent card as the skill open-tools.

Audit export (signed)

GET /api/v4/audit-export — returns an integrity-signed (HMAC-SHA256) compliance snapshot of a monitor, a shared check, or your whole tenant. Scope check. Captain-tier.

NameTypeRequiredDescription
typestringyesmonitor | check | tenant
idstringfor monitor/checkmonitor id (int) or shared-check id; omit for tenant
page, per_pageintnopagination, tenant only (per_page ≤ 200)

Response — the signed envelope:

{
  "data": {
    "data": { /* canonical payload */ },
    "sig":  { "v":1, "alg":"HMAC-SHA256", "kid":"e1", "ts":1718000000, "value":"<hex>" }
  },
  "meta": { "request_id":"req_...", "api_version":"4.0" }
}

Verify by re-canonicalising data and re-HMACing ${ts}.${canonical} against the shared secret. Reference verifier: cli/verify-report.php.

Error Codes

HTTP Code Error Code Description
400missing_parameterMissing or invalid parameters
401auth_requiredMissing API key
401invalid_api_keyInvalid or inactive API key
403insufficient_scopeKey does not have the required scope
404not_foundResource not found
405method_not_allowedHTTP method not allowed
409conflictIdempotent request already in progress
429rate_limit_exceededRate limit exceeded
429daily_limit_exceededDaily API request limit exceeded

Migrating from earlier versions

See the full step-by-step guide at /docs/api-v4-migration.md. Quick map below.

FromTo v4Description
/api/v1/{check,dns,ssl,ip,ping}.php/api/v4/{check,dns,ssl,ip,ping}.phpFlat JSON → {data,meta} envelope. Auth header unchanged.
/api/v2/{check,dns,ssl,ip,ping}.php/api/v4/{check,dns,ssl,ip,ping}.php{ok,data,meta}{data,meta}. Error shape changes to {error:{code,message}}.
/api/v3/*/api/v4/*Identical contract — just swap /v3//v4/.
Webhook config (UI only)/api/v4/webhooksProgrammatic webhook CRUD — v4-only.
Delivery log (UI only)/api/v4/eventsWebhook delivery history with pagination and filters.

Timeline: v1 and v2 return 410 Gone after 2026-10-01. v3 remains on a freeze (no new features), no sunset date set — migrate opportunistically. Every v1/v2 response already carries Deprecation + Sunset headers (F2.2 / M-08).

Heartbeat / dead-man's switch

Per-monitor token endpoint for cron jobs and scheduled tasks to ping us regularly. If no ping arrives in the expected interval, the monitor flips to down and alerts fire — same pathway as HTTP/SSL/DNS monitors. No API key required; the token in the URL is the auth.

GET POST HEAD /api/heartbeat/<TOKEN> public

The token is generated when you create a heartbeat monitor in the dashboard. Put the URL into your cron job:

# every 10 minutes
*/10 * * * * curl -fsS https://enterno.io/api/heartbeat/YOUR_TOKEN > /dev/null

Response

{"status":"ok","next_ping_expected":1745197200}

Rate-limited to 30 req/min/IP via getClientIp(). Returns {"status":"ok", ...} for valid + active tokens, same response shape for inactive/unknown tokens (enumeration-safe).

Webhook receivers — verifying our signatures

When enterno.io dispatches a webhook (monitor.down, ssl.expiring, etc.), every POST carries three headers to let you verify authenticity and replay-protect:

HeaderDescription
X-Enterno-EventEvent type: monitor.down, monitor.up, monitor.degraded, ssl.expiring, ssl.expired, ssl.chain_changed, domain.expiring, domain.expired, visual.changed, test.
X-Enterno-TimestampUnix timestamp of dispatch. Reject if abs(now - ts) > 300s (replay guard).
X-Enterno-Signature-V2HMAC-SHA256 over "{timestamp}.{raw_body}". Prefix sha256=.
X-Enterno-Signature (legacy)HMAC-SHA256 body-only — sunset 2026-07-01. Migrate to V2 now.

PHP

$raw = file_get_contents(\'php://input\');
$ts  = (int) ($_SERVER[\'HTTP_X_ENTERNO_TIMESTAMP\'] ?? 0);
$sig = $_SERVER[\'HTTP_X_ENTERNO_SIGNATURE_V2\'] ?? \'\';

// 1. replay guard
if (abs(time() - $ts) > 300) { http_response_code(401); exit(\'stale\'); }

// 2. verify signature (timing-safe)
$expected = \'sha256=\' . hash_hmac(\'sha256\', $ts . \'.\' . $raw, $webhookSecret);
if (!hash_equals($expected, $sig)) { http_response_code(401); exit(\'bad sig\'); }

// 3. process event
$event = json_decode($raw, true);
// ... your logic
http_response_code(200);

Node.js

const crypto = require(\'crypto\');
const secret = process.env.ENTERNO_WEBHOOK_SECRET;
app.post(\'/webhook\', express.raw({ type: \'application/json\' }), (req, res) => {
  const ts  = parseInt(req.get(\'X-Enterno-Timestamp\') || \'0\', 10);
  const sig = req.get(\'X-Enterno-Signature-V2\') || \'\';
  if (Math.abs(Date.now() / 1000 - ts) > 300) return res.status(401).send(\'stale\');

  const expected = \'sha256=\' + crypto.createHmac(\'sha256\', secret)
    .update(ts + \'.\' + req.body.toString())
    .digest(\'hex\');
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    return res.status(401).send(\'bad sig\');
  }

  const event = JSON.parse(req.body.toString());
  // ... your logic
  res.status(200).send();
});