Payloads

Attach the reason a job failed to the ping that reports it, so triage starts with the cause.

Send the reason the job failed along with the failure:

bash
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/failure \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "backup-20260801",
    "payload": "pg_dump: error: connection to server failed: FATAL: too many connections"
  }'
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/failure \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "backup-20260801",
    "payload": "pg_dump: error: connection to server failed: FATAL: too many connections"
  }'

That line renders inline on the run timeline. The on-call engineer sees the cause in the alert instead of opening a shell to find it.

Only POST carries a payload. GET pings are limited to query-string parameters.

What you can send#

FieldWhereLimitWhat it does
payloadPOST body100 KB Free, 1 MB Pro, 2 MB BusinessAny string. Stdout, an error message, or JSON
run_idQuery string or POST body255 charactersTies this ping to the rest of the run
duration_msPOST bodynoneReport duration yourself instead of letting Drumbeats compute it

How Drumbeats decides what to store#

Three rules, in order:

  1. A JSON body with an explicit payload field stores that field's value.
  2. Any other body stores the whole raw body. This is how the plain-text and --data-binary @- forms work.
  3. No body stores no payload. The ping still costs 1 beat.

Rule 2 means you can POST a flat JSON object and read it back as a key-value table:

bash
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/log \
  -H "Content-Type: application/json" \
  -d '{"processed": 1240, "skipped": 3, "duration_ms": 1820}'
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/log \
  -H "Content-Type: application/json" \
  -d '{"processed": 1240, "skipped": 3, "duration_ms": 1820}'

Common shapes#

Capture output, then branch
OUTPUT=$(python job.py 2>&1)
EXIT=$?
EVENT=$([ $EXIT -eq 0 ] && echo "success" || echo "failure")

curl -X POST "https://api.drumbeats.io/v1/ping/<monitor-id>/$EVENT" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg p "$OUTPUT" --arg r "job-$$" '{payload: $p, run_id: $r}')"
OUTPUT=$(python job.py 2>&1)
EXIT=$?
EVENT=$([ $EXIT -eq 0 ] && echo "success" || echo "failure")

curl -X POST "https://api.drumbeats.io/v1/ping/<monitor-id>/$EVENT" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg p "$OUTPUT" --arg r "job-$$" '{payload: $p, run_id: $r}')"
Pipe stdout straight through
python job.py 2>&1 | curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: text/plain" \
  --data-binary @-
python job.py 2>&1 | curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: text/plain" \
  --data-binary @-
Report a measured duration
START=$(date +%s%3N)
/usr/bin/backup.sh
END=$(date +%s%3N)

curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: application/json" \
  -d "{\"run_id\":\"backup-$(date +%Y%m%d)\",\"duration_ms\":$((END-START))}"
START=$(date +%s%3N)
/usr/bin/backup.sh
END=$(date +%s%3N)

curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: application/json" \
  -d "{\"run_id\":\"backup-$(date +%Y%m%d)\",\"duration_ms\":$((END-START))}"

Use jq to build the JSON rather than string interpolation. A stack trace with a quote or a newline in it will produce invalid JSON otherwise, and the ping fails on exactly the runs you needed it most.

Where payloads live#

Payload sizeStoredRead speed
Up to 10 KiBEntirely in the primary databaseRenders inline, instantly
10 KiB up to your plan limitFirst 10 KiB in the database, the full copy in object storageInline preview is instant, the full body loads on demand
Over your plan limitTruncated to the plan limit, then stored as aboveThe bytes past the limit are gone

Record progress on a long job#

The log event stores a payload without touching monitor status. On a multi-phase job it turns "the run hung" into "the run hung during the transform".

Each log costs a beat. Keep them on jobs where the phase actually matters during triage, and drop them once a job is stable.

What lands on the run timeline#

Every ping, including log pings, adds one row: the event name, the time Drumbeats received it, the run_id, the duration when known, the payload or a link to the full copy, and the source IP and user agent.

Each monitor keeps its most recent rows and drops older ones. The per-monitor default is 200, tunable from 10 to 1000, and your plan caps how much history is retained overall.

Run one script against several environments#

The slug URL form keeps the monitor name stable and swaps only the project:

Next#

Ping API for events, methods, and error codes. Scheduled pings and event-driven pings for the patterns that use payloads. Beats and usage for what the bytes cost.