Recipes
Copy-paste automation with the HTTP API, from CI pipelines to your SIEM.
All recipes assume:
export THREEAM=https://3am.bank.internal # your console address
export TOKEN=… # the admin token (keep it in your CI's secret store)Pause 3AM during a deployment
Halt before the change, resume after. A halt refuses every action, including fixes that were already approved but not yet run. Diagnosis keeps going.
deploy:
script:
- 'curl -sf -X POST "$THREEAM/v1/halt" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json"
-d "{\"by\": \"ci:$CI_PROJECT_PATH\", \"reason\": \"deploying $CI_COMMIT_SHORT_SHA\"}"'
- ./deploy.sh
after_script:
- 'curl -sf -X POST "$THREEAM/v1/resume" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json"
-d "{\"by\": \"ci:$CI_PROJECT_PATH\"}"'after_script runs even when the deploy fails, so 3AM never stays halted by accident. The halt shows in the console
and the audit log with your reason.
Record changes made outside 3AM
Post deploys and manual fixes to the audit log, so incidents show them in their timeline. This uses the ingest token, not the admin token.
curl -sf -X POST "$THREEAM/v1/events" -H "Authorization: Bearer $INGEST_TOKEN" -H 'Content-Type: application/json' \
-d "{\"type\": \"human.deploy\", \"actor\": \"ci:payments\", \"reason\": \"deployed payments $SHA\",
\"data\": {\"service\": \"payments\", \"version\": \"$SHA\"}}"Export the audit log to your SIEM
Ask for everything after the last event you shipped (after_seq), oldest first, 2000 at a time. Run it on a schedule:
#!/usr/bin/env bash
set -euo pipefail
last=$(cat .last_seq 2>/dev/null || echo 0)
while :; do
page=$(curl -sf -H "Authorization: Bearer $TOKEN" "$THREEAM/v1/ledger?after_seq=$last&order=asc&limit=2000")
[ "$(jq '.events | length' <<<"$page")" -gt 0 ] || break
jq -c '.events[]' <<<"$page" | curl -sf -X POST "$SPLUNK_HEC/services/collector/raw?sourcetype=3am:ledger" \
-H "Authorization: Splunk $HEC_TOKEN" --data-binary @-
last=$(jq '.events[-1].seq' <<<"$page"); echo "$last" > .last_seq
[ "$(jq .more <<<"$page")" = true ] || break
doneBefore handing an export to an auditor, verify the chain on the source:
curl -s -H "Authorization: Bearer $TOKEN" $THREEAM/v1/ledger/verify.
Alert before the licence expires
/v1/status needs no token. With the blackbox exporter's JSON prober, or a small script:
expires=$(curl -sf "$THREEAM/v1/status" | jq -r .licence.expires_at)
days=$(( ( $(date -d "$expires" +%s) - $(date +%s) ) / 86400 ))
[ "$days" -gt 30 ] || echo "3AM licence expires in $days days"On macOS use date -j -f "%Y-%m-%dT%H:%M:%S" "${expires%%+*}" +%s instead of date -d.
List today's incidents and outcomes
curl -sf -H "Authorization: Bearer $TOKEN" "$THREEAM/v1/episodes?limit=200" \
| jq -r '.episodes[] | select(.t_open > (now - 86400 | todate)) | [.t_open, .service, (.alerts|join(",")), .decision, .outcome] | @tsv'2026-10-04T17:54:12Z platform DependencyPortUnreachable abstain escalated
2026-10-04T16:21:03Z core-banking-db MySQLReadOnly act mitigatedPost the daily digest to a channel
text=$(curl -sf -H "Authorization: Bearer $TOKEN" "$THREEAM/v1/digest?days=1" | jq -r .markdown)
jq -n --arg text "$text" '{text: $text}' | curl -sf -X POST "$SLACK_WEBHOOK" -H 'Content-Type: application/json' -d @-