Sending alerts

One POST opens an alert and pages whoever is on call.

Two ways in

Both accept the same thing — a signal aimed at a service. They differ only in where the credential goes.

LaneAuthUse it when
POST /v1/alertsAuthorization: Bearer ak_…You control the sender and can set a header.
POST /v1/ingest/{token}Token in the URL (ik_…)The sender can only be given a URL — most vendor webhook configs.

An ingest URL is a credential: anyone holding it can raise alerts in your organization. Treat it like a password, and rotate it from the integration's page if it leaks.

Your first alert

Create a service in the dashboard. Every new service ships with a default integration and an ingest URL, so there is nothing else to set up.

curl -X POST https://api.acked.dev/v1/alerts \
  -H "Authorization: Bearer ak_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{
    "service_id": "svc_01hexample",
    "event_key": "disk-full:db-1",
    "title": "Disk 91% full on db-1",
    "body": "/var is filling. Growth is ~2%/hour.",
    "priority": "high"
  }'

The response carries the alert id:

{"ok":true,"alert_id":"01KX…","deduped":false,"resolved":false}

Fields

FieldRequiredNotes
service_idalwaysWhich service this is about. Decides who gets paged.
titleto triggerWhat someone reads at 3am. Keep it specific.
event_keyto resolveYour identifier for the condition. See deduplication.
bodynoDetail. Shown on the alert page.
prioritynocritical, high, medium, low, info. Defaults to high.
urgencynohigh or low — see below.
sourcenoFree text naming the system that sent it.
actionnotrigger (default) or resolve.

Priority is not urgency

Priority is a label describing how bad the condition is. It does not decide how loudly anyone is notified.

Urgency decides the notification. high pages as a critical alert, which can break through a phone's silent switch and Focus modes where the device allows it. low arrives as an ordinary notification.

They are separate on purpose: a critical-priority condition during a planned migration may not warrant waking anyone, and a low-priority one at 4am might. If you omit urgency, anything that is not info pages at high urgency.

Deduplication

Send the same event_key for the same service while an alert is still open, and it folds into that alert instead of opening a second one. The dedupe count goes up; nobody is paged again.

This is what makes it safe to post on every check. A monitor firing every 30 seconds produces one alert, not 120 an hour.

Choose an event_key that identifies the condition, not the moment — disk-full:db-1, not disk-full:db-1:2026-08-03T18:04Z. Omit it entirely and every call opens a new alert.

Resolving

When the condition clears, send the same key with action: "resolve". No title needed — you are closing something that already exists.

curl -X POST https://api.acked.dev/v1/alerts \
  -H "Authorization: Bearer ak_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"service_id":"svc_01hexample","event_key":"disk-full:db-1","action":"resolve"}'

Resolving something already closed is a no-op, not an error, so a recovery hook that fires twice is harmless.

What a 202 means

Ingest answers 202 Accepted when the alert has been taken but not yet written — the edge buffers it and delivers it onward. You will see {"ok":true,"alert_id":"…","buffered":true}.

This is deliberate. A page must survive us having a bad moment, so the edge accepts and holds rather than failing your monitor's webhook. The alert id is yours either way, and the alert will open.

The practical consequence: do not read the response to decide whether an alert exists. It does.