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": trueand a list oforgs. Send the chosen ID toPOST /api/auth/select-orgas{"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.
| Method | Endpoint | Authentication |
|---|---|---|
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.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/alerts | List 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.
| Method | Endpoint | Description |
|---|---|---|
GET | /sops | List SOPs. Add active_only=true to list only active ones. |
GET | /sops/{sop_id} | Get a summary of one SOP. |
GET | /sops/{sop_id}/content | Get the full SOP definition. |
POST | /api/sops | Create or replace an SOP by name. Admin or editor role. |
POST | /sops/{sop_id}/activate | Activate an SOP. |
POST | /sops/{sop_id}/deactivate | Deactivate an SOP. |
POST | /sops/discover | Discover SOPs from GitHub and Confluence in the background. Query parameters: github_repos and confluence_spaces. |
POST | /sops/discover/github | Discover from GitHub and wait for the result. Query parameter: repos, as owner/repo. |
POST | /sops/discover/confluence | Discover from Confluence and wait for the result. Query parameters: spaces, and optional labels. |
GET | /api/sops/drafts | List SOP drafts waiting for approval. |
POST | /api/sops/{sop_id}/approve | Approve a draft so it can run. Admin role. |
POST | /api/sops/{sop_id}/reject | Reject a draft. It is kept for the record and never runs. Admin role. |
POST | /sops/reload | Reload 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
/mcpwhenMCP_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
| Status | Meaning |
|---|---|
401 | Unknown webhook token, a missing or wrong PagerDuty signature, or an invalid or expired JWT. |
403 | Your role does not allow this action, or your organization requires SSO for password login. |
413 | The webhook body is larger than 2 MB. |
422 | The body does not have the expected shape. For example, a Splunk body without search_name. |
429 | Too many requests. See the limits below. |
Webhook rate limits apply to each client IP address, for each source, per minute:
| Source | Requests per minute | Setting |
|---|---|---|
| Splunk | 200 | SPLUNK_RATE_LIMIT |
| Sensu | 150 | SENSU_RATE_LIMIT |
| Grafana | 150 | GRAFANA_RATE_LIMIT |
| PagerDuty | 100 | PAGERDUTY_RATE_LIMIT |
| Uptime.com | 100 | UPTIME_COM_RATE_LIMIT |
| Railway | 100 | RAILWAY_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.