Skip to content

Instantly share code, notes, and snippets.

@stojg
Created July 22, 2026 04:24
Show Gist options
  • Select an option

  • Save stojg/4fb4c741012da8561d7d1bee1fe35848 to your computer and use it in GitHub Desktop.

Select an option

Save stojg/4fb4c741012da8561d7d1bee1fe35848 to your computer and use it in GitHub Desktop.
rollbar-item
#!/usr/bin/env bash
#
# rollbar-item — download a Rollbar item by its per-project counter.
#
# The counter is the number shown in the Rollbar UI and in item URLs
# (e.g. #13968). The API resolves it to an internal item id and then
# returns the full item.
#
# https://docs.rollbar.com/reference/get-an-item-by-project-counter
#
# By default it also fetches the latest occurrence (which carries the stack
# trace, request, and telemetry) and emits { "item": ..., "occurrence": ... }.
# Use --item-only for just the item metadata.
#
# Usage:
# rollbar-item <counter> [-o output.json] [--item-only]
#
# Token (project access token, 'read' scope) — first found wins:
# ROLLBAR_ACCESS_TOKEN environment variable, or
# ~/.config/rollbar/token (chmod 600; override with ROLLBAR_TOKEN_FILE)
#
# Examples:
# rollbar-item 13968
# rollbar-item 13968 -o err.json
# rollbar-item 13968 | jq '.occurrence.body.trace.frames'
set -euo pipefail
api="https://api.rollbar.com/api/1"
out="-" # stdout by default
item_only=0
die() { printf 'rollbar-item: %s\n' "$1" >&2; exit 1; }
counter=""
while [ $# -gt 0 ]; do
case "$1" in
-o|--output) out="${2:-}"; [ -n "$out" ] || die "-o needs a filename"; shift 2;;
--item-only) item_only=1; shift;;
-h|--help) sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'; exit 0;;
-*) die "unknown option: $1";;
*) counter="$1"; shift;;
esac
done
[ -n "$counter" ] || die "missing item counter (try -h)"
case "$counter" in *[!0-9]*) die "counter must be an integer, got: $counter";; esac
# Token: prefer the environment, else read the per-user config file
# (~/.config/rollbar/token, chmod 600). Keeps the secret out of the shell
# environment and out of any repo.
token_file="${ROLLBAR_TOKEN_FILE:-$HOME/.config/rollbar/token}"
if [ -z "${ROLLBAR_ACCESS_TOKEN:-}" ] && [ -r "$token_file" ]; then
ROLLBAR_ACCESS_TOKEN="$(tr -d '[:space:]' < "$token_file")"
fi
[ -n "${ROLLBAR_ACCESS_TOKEN:-}" ] \
|| die "no token: set ROLLBAR_ACCESS_TOKEN or write it to $token_file (project token, 'read' scope)"
hdr="X-Rollbar-Access-Token: $ROLLBAR_ACCESS_TOKEN"
# 1. Resolve counter -> item id. This endpoint replies with a 301 whose body
# carries result.itemId; we parse it rather than blindly following, so we
# can give a clear error if auth/counter is wrong.
resolve=$(curl -fsS -H "$hdr" "$api/item_by_counter/$counter") \
|| die "lookup failed for counter $counter (bad token, or no such item?)"
item_id=$(printf '%s' "$resolve" \
| grep -o '"itemId"[[:space:]]*:[[:space:]]*[0-9]*' \
| grep -o '[0-9]*$' \
| head -1)
[ -n "$item_id" ] || die "could not find itemId in response: $resolve"
# 2. Fetch the item metadata.
item=$(curl -fsS -H "$hdr" "$api/item/$item_id") \
|| die "failed to fetch item $item_id"
have_jq=0; command -v jq >/dev/null 2>&1 && have_jq=1
# Redaction: strip PII before anything touches disk. Integer ids stay
# (person.id, userId, accountId are useful and not identifying on their own).
# Two layers: (a) delete the known identifying string fields, and (b) mask any
# email or IPv4 wherever it appears — including free-form telemetry/DOM/log
# breadcrumbs, request URLs, and log messages we don't control the shape of.
redact='
def mask:
gsub("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"; "[redacted-email]")
| gsub("\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b"; "[redacted-ip]");
walk(
if type=="object" then del(.email, .username, .user_ip, .query_string)
elif type=="string" then mask
else . end
)
'
# 3. Fetch the latest occurrence (the item metadata has no stack trace; the
# trace/request/telemetry live on an occurrence). Needs jq to locate the id
# and to combine the two documents, so skip gracefully without it.
occ_note=""
if [ "$item_only" -eq 0 ] && [ "$have_jq" -eq 1 ]; then
occ_id=$(printf '%s' "$item" | jq -r '.result.last_occurrence_id // empty')
if [ -n "$occ_id" ]; then
occ=$(curl -fsS -H "$hdr" "$api/instance/$occ_id") \
|| die "failed to fetch occurrence $occ_id"
# { item: <item.result>, occurrence: <instance.result.data> }, then redact
output=$(jq -n --argjson item "$item" --argjson occ "$occ" \
"{item: \$item.result, occurrence: \$occ.result.data} | $redact")
occ_note=" + occurrence $occ_id"
else
output=$(printf '%s' "$item" | jq "{item: .result} | $redact")
occ_note=" (no occurrences)"
fi
elif [ "$have_jq" -eq 1 ]; then
output=$(printf '%s' "$item" | jq "{item: .result} | $redact")
else
output="$item" # raw item, no jq available
printf 'rollbar-item: jq not found; cannot redact — emitting raw item metadata only\n' >&2
fi
if [ "$out" = "-" ]; then
printf '%s\n' "$output"
else
printf '%s\n' "$output" > "$out"
printf 'wrote item %s (counter %s)%s -> %s\n' "$item_id" "$counter" "$occ_note" "$out" >&2
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment