API Reference

Opsentry's HTTP API lets you read status, create incidents, and schedule maintenance from your own systems — CI pipelines, on-call runbooks, internal dashboards. Every request is scoped to a single tenant via an API token.

Overview

Authentication

The API is authenticated with tenant-scoped API tokens. Tokens are bound to a single tenant and a set of scopes that limit what they can do. Sessions (the browser cookie used by the admin UI) cannot be used for API calls — token and session auth are deliberately separate.

Creating tokens

  1. Sign in and go to API tokens in the admin sidebar.
  2. Click Create token, give it a name (e.g. CI pipeline), pick the scopes it needs, and optionally an expiry.
  3. Copy the token immediately. It looks like ops_3f8b… and is shown only once — we store a hash, not the value.
  4. Treat it like a password: never commit it to source control, never paste it into shared chat. If a token leaks, revoke it from the same page.
You only see the token once. If you lose it, revoke the lost one and mint a new one — there's no way to recover a token after the page reloads.

Include the token in the Authorization header on every request:

Authorization: Bearer ops_3f8b1c2d…

Example with curl:

curl https://opsentry.io/api/v1/incidents/active \
  -H "Authorization: Bearer ops_3f8b1c2d…"

JavaScript (fetch):

const res = await fetch("https://opsentry.io/api/v1/incidents/active", {
  headers: { "Authorization": `Bearer ${token}` }
});
const data = await res.json();

Python (requests):

import requests
r = requests.get(
    "https://opsentry.io/api/v1/incidents/active",
    headers={"Authorization": f"Bearer {token}"},
)
data = r.json()

Go:

req, _ := http.NewRequest("GET", "https://opsentry.io/api/v1/incidents/active", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)

Scopes

Each token carries one or more scopes. The API rejects requests with 403 Forbidden if the token lacks the scope required by the endpoint.

ScopeWhat it allows
read:statusRead services, incidents, maintenance, uptime statistics.
write:incidentsCreate, update, and resolve incidents.
write:maintenanceSchedule and update maintenance windows.

Pick the minimum set the integration needs. A CI pipeline that only declares failed deploys as incidents should hold write:incidents — never read or admin scopes.

Errors

The API uses standard HTTP status codes and returns a JSON body with a short error message:

{
  "error": "missing required scope: write:incidents"
}
StatusMeaning
200 / 201Success.
400Malformed request — bad JSON, missing required field.
401Missing Authorization header or unknown/expired token.
403Token is valid but lacks the required scope.
404Resource doesn't exist or doesn't belong to the token's tenant. We don't distinguish — leaks no information about other tenants.
429Rate-limited. See Rate limits.
5xxServer problem. Includes a request_id — quote it when reporting.

Rate limits

The API is rate-limited per token's source IP. Limits are conservative by default; contact support if your integration needs more.

When you hit the limit you get 429 Too Many Requests. Back off and retry — don't tight-loop on 429s.

Endpoints

All paths are relative to https://opsentry.io/api/v1. Responses are JSON. Errors follow the standard shape. Pagination uses limit + offset query parameters where applicable.

Health

Four health endpoints exist, each tuned for a different consumer:

EndpointAuthUse case
GET /healthnoneLoad balancer probe. Cheapest possible response — plain text OK, no DB hit, no JSON parsing. Use this from AWS Target Groups, HAProxy, Cloudflare, Nginx upstream checks, etc.
GET /health/livenoneKubernetes-style liveness probe. Returns JSON; equivalent semantics to /health but in the shape K8s expects.
GET /health/readynoneReadiness probe — 200 only when DB, Redis, and the notification outbox are healthy. Returns 503 with per-check details otherwise. Use from orchestrators so traffic drains during dependency outages.
GET /api/v1/healthtokenToken-verifying ping. Returns the tenant ID + token prefix + scopes so integrations can confirm "is my key valid and what does it carry?" in one round trip.
Picking the right one for a load balancer: use /health as the primary check (it's cheap and always 200 if the process can serve traffic). If you also want the LB to drain a replica when its DB connection is failing, use /health/ready as a secondary deep check on a longer interval.
GET/health

Load-balancer probe. Plain text OK, no JSON, no dependency check. Sub-millisecond.

Unauthenticated.

curl -i https://opsentry.io/health

HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: text/plain; charset=utf-8

OK
GET/health/live

Liveness — 200 with JSON {"status":"ok"} while the process is running.

Unauthenticated.

curl https://opsentry.io/health/live
GET/health/ready

Readiness — 200 when all dependency checks pass, 503 with per-check status when any fail.

Unauthenticated.

{
  "status": "ready",
  "checks": {
    "postgres": { "status": "ok", "duration_ms": 2400000 },
    "redis":    { "status": "ok", "duration_ms": 810000 },
    "outbox":   { "status": "ok", "duration_ms": 5100000 }
  }
}

The outbox check fails when the notification backlog exceeds 1000 pending rows or the oldest pending row is older than 5 minutes — at that point your orchestrator should drain the replica.

GET/api/v1/health

Authenticated ping. Returns 200 with the token's metadata. Handy for CI/CD pipelines: a single call confirms the API is reachable and the token still works.

Requires a valid token; no specific scope required.

curl https://opsentry.io/api/v1/health \
  -H "Authorization: Bearer ops_3f8b1c2d…"
{
  "ok": true,
  "tenant_id": 17,
  "token_prefix": "3f8b1c2d",
  "scopes": ["read:status", "write:incidents"]
}

A 401 response means the token is invalid, expired, or revoked — even before you bother making real requests.

Services

GET/services

List services for the tenant. Query params: status, visibility, search, limit (default 100, max 200), offset.

Requires scope read:status

curl https://opsentry.io/api/v1/services?status=degraded_performance \
  -H "Authorization: Bearer ops_…"
{
  "services": [{
    "id": 42,
    "slug": "main-api",
    "name": "Main API",
    "current_status": "degraded_performance",
    "visibility": "public",
    "tags": ["prod", "customer-facing"]
  }],
  "total": 1, "limit": 100, "offset": 0
}
GET/services/:id

Fetch one service by ID. 404 if the ID doesn't belong to your tenant.

Requires scope read:status

GET/services/:id/uptime

Returns 24h / 7d / 30d uptime percentages for the service.

Requires scope read:status

{
  "service_id": 42,
  "uptime_24h": 99.97,
  "uptime_7d": 99.94,
  "uptime_30d": 99.91
}

Incidents

GET/incidents

Paginated incident list. Query params: status, severity, limit (default 50, max 200), offset.

Requires scope read:status

GET/incidents/active

Returns only non-resolved incidents — shortcut for the dashboards/alerters that don't want to paginate.

Requires scope read:status

GET/incidents/:id

Fetch one incident including update history and affected services.

Requires scope read:status

POST/incidents

Create a new incident. The tenant is taken from the token — any tenant_id in the body is ignored.

Requires scope write:incidents

curl -X POST https://opsentry.io/api/v1/incidents \
  -H "Authorization: Bearer ops_…" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Deploy failed — rolling back",
    "description": "Health checks failing post-deploy",
    "severity": "degraded",
    "visibility": "public",
    "service_ids": [42]
  }'
FieldTypeNotes
titlestringRequired. Public headline.
descriptionstringOptional context for the timeline.
severitystringOne of informational, degraded, partial_outage, major_outage, critical.
visibilitystringpublic (default) or internal.
service_idsinteger[]Services to link as affected.

Returns 201 Created with the new incident.

PATCH/incidents/:id

Mutate an incident. All fields optional — only the ones present in the body are changed.

Requires scope write:incidents

curl -X PATCH https://opsentry.io/api/v1/incidents/2041 \
  -H "Authorization: Bearer ops_…" \
  -H "Content-Type: application/json" \
  -d '{"status": "monitoring", "severity": "informational"}'

Status transitions are validated server-side; resolvedinvestigating is allowed (re-open). See the admin UI for the full state machine.

POST/incidents/:id/updates

Append a status-update message. The message also transitions the incident's status.

Requires scope write:incidents

curl -X POST https://opsentry.io/api/v1/incidents/2041/updates \
  -H "Authorization: Bearer ops_…" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "monitoring",
    "message": "Failover complete. Watching for 15 minutes.",
    "is_public": true
  }'
POST/incidents/:id/resolve

Mark the incident resolved with optional root cause + postmortem.

Requires scope write:incidents

curl -X POST https://opsentry.io/api/v1/incidents/2041/resolve \
  -H "Authorization: Bearer ops_…" \
  -H "Content-Type: application/json" \
  -d '{
    "root_cause": "Stale DNS cache after failover.",
    "postmortem": "https://internal.notes/inc-2041"
  }'

Maintenance

GET/maintenance

Paginated list of maintenance windows. Query params: status, limit, offset.

Requires scope read:status

GET/maintenance/upcoming

Future scheduled windows only. No pagination — typical tenant has few.

Requires scope read:status

GET/maintenance/:id

Fetch one maintenance window including linked services.

Requires scope read:status

POST/maintenance

Schedule a maintenance window. Times are RFC 3339 timestamps in UTC.

Requires scope write:maintenance

curl -X POST https://opsentry.io/api/v1/maintenance \
  -H "Authorization: Bearer ops_…" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Database failover drill",
    "description": "Customer Portal will be read-only for ~6 minutes.",
    "scheduled_start": "2026-06-01T02:00:00Z",
    "scheduled_end":   "2026-06-01T04:00:00Z",
    "visibility": "public",
    "service_ids": [17],
    "exclude_from_sla": true,
    "suppress_alerts": true
  }'
POST/maintenance/:id/:action

:action is one of start, complete, cancel. Transition rules are validated server-side — e.g. complete only works on active windows.

Requires scope write:maintenance

curl -X POST https://opsentry.io/api/v1/maintenance/812/start \
  -H "Authorization: Bearer ops_…"

Uptime

Per-service uptime statistics are exposed under /services/:id/uptime (see above) to keep all service-scoped data under a single resource. Tenant-wide rollups are coming.

Changelog