API

REST API for certificate monitoring: add, delete and re-check domains, read health scores and history. Token auth, JSON in and out, from Starter up.

The REST API lets you script what you would otherwise do in the browser: add and remove domains, trigger checks, and read health scores. It is available on Starter and Pro at /api/v1/.... Everything is JSON, in and out.

Getting a token

  1. Go to Settings → API token.
  2. Copy the token and send it in the Authentication-Token header on every request:
curl https://certwatchr.com/api/v1/domains \
  -H 'Authentication-Token: <YOUR-TOKEN>'

Treat the token like a password: anyone holding it can add, remove and inspect your domains. If it appears in a shared log, a CI job output or a screenshot, rotate it.

Rotate token on the same page issues a new token and invalidates the old one immediately, so update your scripts straight after. Rotating does not sign you out of the web app, and changing your password does not change your token.

Domains

GET    /api/v1/domains                  # list your domains
POST   /api/v1/domains                  # add a domain
DELETE /api/v1/domains/<id>             # remove a domain
POST   /api/v1/domains/<id>/check       # run a certificate check now

Adding a domain

curl -X POST https://certwatchr.com/api/v1/domains \
  -H 'Authentication-Token: <YOUR-TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"hostname":"example.com","port":443,"alert_at_days":30,"tags":["prod","web"]}'
Field Required Notes
hostname yes Lowercased and trimmed for you. Up to 255 characters.
port no 1–65535. Defaults to 443.
label no Up to 128 characters.
alert_at_days no 1–365 (Free accounts: up to 30). Defaults to 30.
tags no A list (["prod","web"]) or a comma-separated string ("prod,web"). Stored the same way the web form stores them.

Adding a hostname and port you already monitor returns 409 Conflict and leaves the existing domain untouched, so re-running a script is safe. Your plan's domain limit applies exactly as it does in the browser.

Domain objects

Every domain is returned in this shape:

{
  "id": 42,
  "hostname": "example.com",
  "port": 443,
  "label": "Main site",
  "tags": ["prod", "web"],
  "alert_at_days": 30,
  "status": "ok",
  "days_remaining": 67,
  "cert_expiry": "2026-11-28T09:14:00",
  "cert_issuer": "Let's Encrypt",
  "cert_subject": "example.com",
  "last_checked_at": "2026-09-22T06:00:12",
  "alerts_snoozed_until": null
}

status is untested, ok, warning, critical or error. POST .../check answers 202 {"queued": true}; the result appears on the domain a few seconds later.

Health score

GET  /api/v1/domains/<id>/health                     # latest score, breakdown, suggestions
POST /api/v1/domains/<id>/health/check               # run a health check now
GET  /api/v1/domains/<id>/health/history?limit=90    # score over time, oldest first

GET /health returns the stored result without running a new check:

{
  "health_score": 90,
  "breakdown": {
    "chain_trusted": 20,
    "hostname_match": 15,
    "not_yet_valid": 5,
    "expiry_proximity": 20,
    "revocation": 10,
    "chain_complete": 0,
    "tls_version": 10,
    "weak_algorithms": 10
  },
  "certificate": {
    "subject": "example.com",
    "issuer": "Let's Encrypt",
    "days_remaining": 67
  },
  "errors": ["Incomplete chain: server did not send its intermediate certificate"],
  "suggestions": [
    {
      "category": "chain_complete",
      "points": 10,
      "severity": "critical",
      "title": "Send the intermediate certificates too",
      "why": "..."
    }
  ],
  "last_health_checked_at": "2026-09-22T06:30:00"
}

The categories and points are the ones on the Health score page. suggestions is the same "what to fix" list the domain page shows, most urgent first.

history returns one entry per past check with health_score, breakdown, tls_version, signature_algorithm, errors and checked_at. limit defaults to 90 and goes up to 365.

Errors

Every error is JSON with a matching HTTP status, including authentication failures — you never get an HTML page or a redirect to the login form:

{"error": "Conflict", "message": "example.com:443 is already being monitored."}
Status Meaning
400 The body is not valid JSON, or a field failed validation. The message says which.
401 Missing or invalid Authentication-Token.
403 Your plan does not include this endpoint, the domain is not yours, or you are at your domain limit.
404 No such domain.
409 That hostname and port are already monitored on your account.

5xx responses are safe to retry with a short delay. 4xx responses will not change until you change the request.