API and webhook reference

This page covers the endpoints you need to send alerts and manage SOPs. It shows how each one authenticates, with curl examples for sending a test alert.

The examples use http://localhost:8000, the address of a self-hosted server. On hosted adoe, use the host shown on the Webhooks tab of the Integrations page.

Authentication

adoe uses two kinds of authentication:

  • Webhooks carry a token in the URL path. The token identifies and authenticates your organization. PagerDuty can also sign each request. See Webhook endpoints.
  • The REST API needs a JWT (JSON Web Token, a signed login token). Send it in the Authorization: Bearer <token> header.

To get a JWT, log in with your email and password:

curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}'

# The response includes "access_token". Save it:
JWT=<access_token>
  • A token is valid for 24 hours.
  • Login allows 10 requests per minute from each IP address.
  • If your account belongs to more than one organization, the response has "needs_org_selection": true and a list of orgs. Send the chosen ID to POST /api/auth/select-org as {"org_id": "…"}, with that first token, to get a token for that organization.
  • If your organization requires single sign-on (SSO), password login returns 403. TODO(owner): how SSO-only organizations get an API token

Webhook endpoints

Monitoring tools send alerts here. Create each token on the Webhooks tab of the Integrations page.

MethodEndpointAuthentication
POST/webhooks/sensu/{token}Token in the URL
POST/webhooks/splunk/{token}Token in the URL
POST/webhooks/pagerduty/{token}Token in the URL. If you set a signing secret, also an HMAC-SHA256 signature in X-PagerDuty-Signature.
POST/webhooks/grafana/{token}Token in the URL
POST/webhooks/uptime-com/{token}Token in the URL
POST/webhooks/railway/{token}Token in the URL

A webhook replies with "status": "accepted" and processes the alert in the background. The reply comes before processing ends, so it does not include an alert ID.

One more webhook, POST /webhooks/github, is not an alert source. It receives GitHub pull request events, so that merging an SOP pull request approves the matching SOP draft. Set GITHUB_WEBHOOK_SECRET and use the same secret in GitHub. adoe then checks the X-Hub-Signature-256 header. Without a secret, adoe accepts unsigned deliveries.

Alert endpoints

These need a JWT.

MethodEndpointDescription
GET/api/alertsList alerts, newest first. Filters: status (comma-separated), service, severity, incident_id (a PagerDuty incident ID). Paging: limit (1 to 100, default 50) and offset.
GET/api/alerts/{alert_id}Get one alert.
curl -H "Authorization: Bearer $JWT" \
  "http://localhost:8000/api/alerts?status=escalated,recommended&limit=20"

SOP endpoints

These need a JWT. Some need a specific role, as shown.

MethodEndpointDescription
GET/sopsList SOPs. Add active_only=true to list only active ones.
GET/sops/{sop_id}Get a summary of one SOP.
GET/sops/{sop_id}/contentGet the full SOP definition.
POST/api/sopsCreate or replace an SOP by name. Admin or editor role.
POST/sops/{sop_id}/activateActivate an SOP.
POST/sops/{sop_id}/deactivateDeactivate an SOP.
POST/sops/discoverDiscover SOPs from GitHub and Confluence in the background. Query parameters: github_repos and confluence_spaces.
POST/sops/discover/githubDiscover from GitHub and wait for the result. Query parameter: repos, as owner/repo.
POST/sops/discover/confluenceDiscover from Confluence and wait for the result. Query parameters: spaces, and optional labels.
GET/api/sops/draftsList SOP drafts waiting for approval.
POST/api/sops/{sop_id}/approveApprove a draft so it can run. Admin role.
POST/api/sops/{sop_id}/rejectReject a draft. It is kept for the record and never runs. Admin role.
POST/sops/reloadReload SOPs from disk and the database. Admin role.

To pass several values, repeat the query parameter, for example ?repos=org/a&repos=org/b. The SOP fields are described in SOP authoring.

Create a recommend-only SOP:

curl -X POST http://localhost:8000/api/sops \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "queue_backlog",
    "description": "Guide the on-call person through a queue backlog",
    "alert_types": ["queue_backlog"],
    "actions": [
      {"type": "manual", "command": "Check the consumer logs for {{ service }}"}
    ],
    "auto_execute": false
  }'

The health check, GET /health, needs no authentication.

Other endpoint groups

adoe has other endpoint groups that this page does not cover:

  • The dashboard API, under /api/.
  • SCIM 2.0 user and group provisioning, under /scim/v2.
  • Single sign-on with SAML 2.0 or OIDC, under /sso.
  • The MCP server (Model Context Protocol, which lets AI clients such as Claude read adoe data), at /mcp when MCP_ENABLED=true.

Test payloads

Use these to check a connection. Set TOKEN to the webhook token for that source first. adoe drops a repeat of the same alert within 30 minutes as a duplicate, so change a field to send it again.

Sensu

curl -X POST "http://localhost:8000/webhooks/sensu/$TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "entity": {
      "name": "api-backend-1",
      "entity_class": "agent",
      "namespace": "prod",
      "labels": {"service": "api-backend", "environment": "prod"}
    },
    "check": {
      "name": "cpu_high",
      "status": 2,
      "output": "CPU usage is 95%"
    },
    "timestamp": 1704067200
  }'

"status": 2 gives severity critical.

Splunk

curl -X POST "http://localhost:8000/webhooks/splunk/$TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "search_name": "Alert - api-backend - CPU high - prod",
    "app": "search",
    "owner": "admin",
    "results_link": "https://splunk.example.com/results/123",
    "result": {
      "host": "api-backend-1",
      "_raw": "CPU usage exceeded threshold",
      "_time": "2024-01-01T12:00:00Z"
    },
    "service": "api-backend",
    "environment": "prod",
    "severity": "critical",
    "signal": "cpu_high"
  }'

PagerDuty

Without a signing secret set on the token:

curl -X POST "http://localhost:8000/webhooks/pagerduty/$TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event": {
      "event_type": "incident.triggered",
      "data": {
        "id": "P123ABC",
        "incident_number": 123,
        "title": "High CPU on api-backend in prod",
        "status": "triggered",
        "urgency": "high",
        "html_url": "https://example.pagerduty.com/incidents/P123ABC",
        "service": {"id": "PSVC123", "summary": "api-backend"}
      }
    }
  }'

With a signing secret, sign the exact body you send:

SECRET="your-pagerduty-signing-secret"
BODY='{"event":{"event_type":"incident.triggered","data":{"id":"P124","incident_number":124,"title":"Test alert","status":"triggered","urgency":"high","html_url":"https://example.pagerduty.com/incidents/P124","service":{"id":"S1","summary":"test"}}}}'
SIGNATURE="v1=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')"

curl -X POST "http://localhost:8000/webhooks/pagerduty/$TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-PagerDuty-Signature: $SIGNATURE" \
  -d "$BODY"

To resolve the test alert, send the same body with "event_type": "incident.resolved". You can also use the Send Test Alert button on the PagerDuty card in the dashboard.

After a few seconds, check that the alert arrived:

curl -H "Authorization: Bearer $JWT" "http://localhost:8000/api/alerts?limit=5"

Errors and rate limits

StatusMeaning
401Unknown webhook token, a missing or wrong PagerDuty signature, or an invalid or expired JWT.
403Your role does not allow this action, or your organization requires SSO for password login.
413The webhook body is larger than 2 MB.
422The body does not have the expected shape. For example, a Splunk body without search_name.
429Too many requests. See the limits below.

Webhook rate limits apply to each client IP address, for each source, per minute:

SourceRequests per minuteSetting
Splunk200SPLUNK_RATE_LIMIT
Sensu150SENSU_RATE_LIMIT
Grafana150GRAFANA_RATE_LIMIT
PagerDuty100PAGERDUTY_RATE_LIMIT
Uptime.com100UPTIME_COM_RATE_LIMIT
Railway100RAILWAY_RATE_LIMIT

A 429 response has no Retry-After header. Wait, then retry with a growing delay between attempts. On a self-hosted server you can change the limits with the settings above.