Axiom Monitoring Skill
Query and manage Axiom datasets, monitors, and dashboards using the Axiom API.
API Conventions
Authentication
Axiom API uses Bearer token or API token — injected by connection. Never hardcode tokens.
Base URL
- API:
https://api.axiom.co/v2/ - Cloud US:
https://cloud.axiom.co/api/v1/ - Use connection-injected
AXIOM_BASE_URL.
Output Rules
- TOKEN EFFICIENCY: Target <=50 lines per output
- Use
jqto extract query results and dataset metadata - NEVER dump full query results — summarize and limit output
Core Helper Function
#!/bin/bash
axiom_api() {
local method="$1"
local endpoint="$2"
local data="${3:-}"
if [ -n "$data" ]; then
curl -s -X "$method" \
-H "Authorization: Bearer ${AXIOM_API_TOKEN}" \
-H "Content-Type: application/json" \
"${AXIOM_BASE_URL}/v2${endpoint}" \
-d "$data"
else
curl -s -X "$method" \
-H "Authorization: Bearer ${AXIOM_API_TOKEN}" \
"${AXIOM_BASE_URL}/v2${endpoint}"
fi
}
axiom_query() {
local apl="$1"
local start="${2:-1h}"
local end="${3:-}"
axiom_api POST "/datasets/_apl" \
"{\"apl\":\"${apl}\",\"startTime\":\"$(date -u -d "-${start}" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-${start} +%Y-%m-%dT%H:%M:%SZ)\"${end:+,\"endTime\":\"${end}\"}}"
}
Parallel Execution
{
axiom_api GET "/datasets" &
axiom_api GET "/monitors" &
axiom_api GET "/dashboards" &
}
wait
Anti-Hallucination Rules
NEVER assume dataset names, field names, or monitor IDs. ALWAYS discover first.
Phase 1: Discovery
#!/bin/bash
echo "=== Datasets ==="
axiom_api GET "/datasets" \
| jq -r '.[] | "\(.name)\t\(.description // "no description")"' | head -20
echo ""
echo "=== Dataset Fields ==="
DATASET="${1:-}"
if [ -n "$DATASET" ]; then
axiom_api GET "/datasets/${DATASET}/info" \
| jq -r '.fields[] | "\(.name)\t\(.type)"' | head -20
fi
echo ""
echo "=== Monitors ==="
axiom_api GET "/monitors" \
| jq -r '.[] | "\(.id)\t\(.name)\t\(.disabled)"' | head -15
echo ""
echo "=== Dashboards ==="
axiom_api GET "/dashboards" \
| jq -r '.[] | "\(.id)\t\(.name)"' | head -15
Common Operations
APL Queries
#!/bin/bash
DATASET="${1:?Dataset name required}"
echo "=== Recent Events ==="
axiom_query "['${DATASET}'] | take 10" "1h" \
| jq -r '.matches[:10][] | "\(._time)\t\(.data | to_entries[:3] | map("\(.key)=\(.value)") | join(", "))"'
echo ""
echo "=== Event Count by Field ==="
axiom_query "['${DATASET}'] | summarize count() by bin(_time, 5m)" "1h" \
| jq -r '.buckets.totals[]? // .matches[]? | "\(._time // "")\t\(.["count_"] // "")"' | head -15
echo ""
echo "=== Error Events ==="
axiom_query "['${DATASET}'] | where level == 'error' or severity == 'error' | take 20" "1h" \
| jq -r '.matches[:20][] | "\(._time)\t\(.data | to_entries[:3] | map("\(.key)=\(.value)") | join(", "))"'
Dataset Management
#!/bin/bash
echo "=== Dataset Statistics ==="
for ds in $(axiom_api GET "/datasets" | jq -r '.[].name' | head -10); do
{
info=$(axiom_api GET "/datasets/${ds}/info")
events=$(echo "$info" | jq '.numEvents // 0')
size=$(echo "$info" | jq '.compressedBytes // 0 | . / 1048576 | . * 100 | round / 100')
fields=$(echo "$info" | jq '.fields | length')
echo "$ds\tevents:${events}\tsize:${size}MB\tfields:${fields}"
} &
done
wait
echo ""
echo "=== Dataset Field Analysis ==="
DATASET="${1:-}"
if [ -n "$DATASET" ]; then
axiom_api GET "/datasets/${DATASET}/info" \
| jq -r '.fields | sort_by(.name)[] | "\(.name)\t\(.type)\t\(.description // "")"' | head -20
fi
Monitor Management
#!/bin/bash
echo "=== All Monitors ==="
axiom_api GET "/monitors" \
| jq -r '.[] | "\(.id)\t\(.name)\t\(.disabled)\t\(.dataset)"' | head -20
echo ""
echo "=== Active Monitors ==="
axiom_api GET "/monitors" \
| jq -r '.[] | select(.disabled == false) | "\(.name)\t\(.dataset)\t\(.comparison)\t\(.threshold)"' | head -15
echo ""
echo "=== Monitor Alert History ==="
MONITOR_ID="${1:-}"
if [ -n "$MONITOR_ID" ]; then
axiom_api GET "/monitors/${MONITOR_ID}" \
| jq '{name, dataset, query: .aplQuery, threshold, comparison, frequency: .intervalMinutes}'
fi
Dashboard Analysis
#!/bin/bash
echo "=== Dashboards ==="
axiom_api GET "/dashboards" \
| jq -r '.[] | "\(.id)\t\(.name)\t\(.charts | length) charts"' | head -15
echo ""
echo "=== Dashboard Details ==="
DASHBOARD_ID="${1:-}"
if [ -n "$DASHBOARD_ID" ]; then
axiom_api GET "/dashboards/${DASHBOARD_ID}" \
| jq -r '{name, description, charts: [.charts[] | {name, dataset, query: (.aplQuery // .query)[0:60]}]}'
fi
Virtual Fields
#!/bin/bash
DATASET="${1:?Dataset name required}"
echo "=== Virtual Fields ==="
axiom_api GET "/datasets/${DATASET}/virtualfields" \
| jq -r '.[] | "\(.name)\t\(.type)\t\(.expression[0:60])"' | head -15
echo ""
echo "=== Dataset Schema with Virtual Fields ==="
axiom_api GET "/datasets/${DATASET}/info" \
| jq -r '.fields[] | "\(.name)\t\(.type)\t\(if .virtual then "virtual" else "physical" end)"' | head -20
Output Format
Present results as a structured report:
Monitoring Axiom Report
═══════════════════════
Resources discovered: [count]
Resource Status Key Metric Issues
──────────────────────────────────────────────
[name] [ok/warn] [value] [findings]
Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical
Action Items: [list of prioritized findings]
Target ≤50 lines of output. Use tables for multi-resource comparisons.
Counter-Rationalizations
| Shortcut | Counter | Why |
|---|---|---|
| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |
Common Pitfalls
- APL syntax: Dataset names in square brackets —
['my-dataset'] | where status == 500 - Dataset names: Case-sensitive and may contain hyphens — always discover first
- Time field:
_timeis the default timestamp field — always present in events - Query response format: Results in
.matches[]for raw events,.bucketsfor aggregations - Virtual fields: Computed at query time — do not appear in raw ingestion data
- Rate limits: Depends on plan tier — check
X-RateLimit-Remainingresponse header - APL vs SQL: APL uses pipe syntax like KQL —
| where,| summarize,| project, not SQL - Ingestion format: JSON array or NDJSON — timestamps should be ISO 8601 in
_timefield