# Recipes

> Copy-paste automation with the HTTP API, from CI pipelines to your SIEM.

Source: https://docs.3am.si/reference/recipes · Markdown: https://docs.3am.si/reference/recipes.md · All docs: https://docs.3am.si/llms.txt
3AM is an on-prem AI on-call engineer for regulated enterprises (https://3am.si).

All recipes assume:

```bash
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.

```yaml title=".gitlab-ci.yml"
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.

```bash
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:

```bash title="export-ledger.sh"
#!/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
done
```

Before 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:

```bash
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

```bash
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'
```

```text title="Output"
2026-10-04T17:54:12Z	platform	DependencyPortUnreachable	abstain	escalated
2026-10-04T16:21:03Z	core-banking-db	MySQLReadOnly	act	mitigated
```

## Post the daily digest to a channel

```bash
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 @-
```
