Skip to main content

Uptime monitoring

Uptime monitors poll a URL from Errora on a fixed interval, assert something about the response, and open an issue when the assertion fails often enough.

Unlike every other product, this one needs no SDK — it is configured entirely in Errora.

Create a monitor​

Uptime → New monitor, or over the API:

curl -X POST "$ERRORA/api/v1/projects/$PROJECT_ID/uptime-monitors" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Checkout API",
"url": "https://api.example.com/healthz",
"method": "GET",
"interval_seconds": 300,
"timeout_ms": 10000,
"environment": "production",
"headers": [["Authorization", "Bearer …"]],
"body": "",
"expected_status_codes": [200, 204],
"body_match": "\"status\":\"ok\"",
"downtime_threshold": 3,
"recovery_threshold": 1,
"trace_sampling": false
}'

Fields​

FieldMeaning
urlWhat to poll. Redirects are followed one SSRF-validated hop at a time.
methodGET, POST, HEAD, …
headersList of [name, value] pairs — auth tokens, custom host headers
bodyRequest body for POST/PUT checks
interval_seconds60, 300, 600, 1200, 1800 or 3600 (1 minute → 1 hour)
timeout_msDefault 10000, max 60000
expected_status_codesEmpty means "any 2xx". Otherwise an explicit allow-list.
body_matchSubstring that must appear in the response body
downtime_thresholdConsecutive failures before the monitor is down (default 3)
recovery_thresholdConsecutive successes before it is up again (default 1)
environmentTags the resulting issue and alerts
trace_samplingAttach a trace to each check (useful when the target is your own instrumented service)
is_mutedKeep checking, stop alerting

What a failure means​

A check fails on a connection error, a timeout, an unexpected status code, or a missing body_match. The failure reason is recorded per check, so the history distinguishes "connection refused" from "returned 500" from "body assertion failed".

After downtime_threshold consecutive failures the monitor goes down: Errora opens a downtime issue for the project and fires the uptime.down alert event. After recovery_threshold successes it goes up, closes the issue and fires uptime.up.

In the UI​

Uptime lists monitors with current status, uptime percentage and recent checks. Open one for the check history (status code, response time, failure reason), statistics, and a Check now button that runs a probe immediately instead of waiting for the schedule.

Alerts​

Route uptime.down / uptime.up to Telegram, Bale, Mattermost, email, SMS or a webhook in Settings → Webhooks — see Alerts.

What Errora can reach​

Checks run from Errora's own servers, not from inside your network, so the URL must be reachable from the public internet. Private/internal addresses are refused, including when a redirect points at one — put a public health endpoint in front of an internal service if you need to watch it.

API​

GET /api/v1/projects/{project_id}/uptime-monitors
POST /api/v1/projects/{project_id}/uptime-monitors
GET /api/v1/projects/{project_id}/uptime-monitors/overview
GET /api/v1/projects/{project_id}/uptime-monitors/{id}
PATCH /api/v1/projects/{project_id}/uptime-monitors/{id}
DELETE /api/v1/projects/{project_id}/uptime-monitors/{id}
GET /api/v1/projects/{project_id}/uptime-monitors/{id}/checks
GET /api/v1/projects/{project_id}/uptime-monitors/{id}/stats
POST /api/v1/projects/{project_id}/uptime-monitors/{id}/check-now

Public status page​

A status page turns a set of uptime monitors into a page your customers can read: /status/<address>. It needs no account, so it keeps working when the rest of the product does not.

Create one under Uptime → Status pages. A page has four parts:

  • Settings — the title, headline and intro, the address it lives at, an optional logo and support link, and a notice banner pinned to the top until you clear it. A page is a draft until you publish it; an unpublished page answers 404.
  • Components — one per uptime monitor, each shown under a name customers recognise (Checkout API, not prod-eu-checkout-lb) and optionally grouped under a heading. The component reads its state from the monitor's probes; there is nothing to keep in sync by hand.
  • Incidents — the write-up around an outage, with a timeline. Posting an update is how an incident's status moves: there is no way to change the status without saying why. An unresolved incident shows the page as degraded even when every probe is green.
  • Maintenance — a planned window with the text customers should read. While a window is in progress its components report "under maintenance" rather than down, so scheduled work never looks like an outage.

Each component shows one bar per day over the page's history window (up to 90 days), computed from the same hourly rollups as the monitor detail page.

GET /api/v1/status/{slug} (public, no auth)
GET /api/v1/organizations/{org_id}/status-pages
POST /api/v1/organizations/{org_id}/status-pages
GET /api/v1/organizations/{org_id}/status-pages/{id}
PATCH /api/v1/organizations/{org_id}/status-pages/{id}
DELETE /api/v1/organizations/{org_id}/status-pages/{id}
GET /api/v1/organizations/{org_id}/status-pages/{id}/components
POST /api/v1/organizations/{org_id}/status-pages/{id}/components
GET /api/v1/organizations/{org_id}/status-pages/{id}/incidents
POST /api/v1/organizations/{org_id}/status-pages/{id}/incidents
POST /api/v1/organizations/{org_id}/status-pages/{id}/incidents/{incident_id}/updates
GET /api/v1/organizations/{org_id}/status-pages/{id}/maintenances
POST /api/v1/organizations/{org_id}/status-pages/{id}/maintenances

Editing a status page needs the organization-manage capability; reading the public page needs nothing at all.

Troubleshooting​

SymptomCause / fix
"Private address" errorThe URL resolves to a private/internal address, which checks may not reach
Flapping up/downRaise downtime_threshold, or increase timeout_ms for a slow endpoint
403/401 on every checkThe target needs auth — add an Authorization header
Passes in a browser, fails hereThe endpoint requires cookies, a specific Host, or blocks the checker's IP
Body assertion always failsbody_match is a plain substring, not a regex — and the body may be gzip-encoded HTML, not the JSON you expect