Recording runs¶
POST /api/v1/runs is the heart of the integration: one HTTP call per
execution, from any tool that can speak HTTP.
Request¶
curl -X POST "$LUMATRACK_URL/api/v1/runs" \
-H "Authorization: Bearer $LUMATRACK_KEY" \
-H "Content-Type: application/json" \
-d '{
"automation": "os-patching",
"status": "success",
"executed_at": "2026-06-11T14:30:00Z",
"duration_seconds": 142,
"source": "ansible",
"external_id": "tower-job-99412",
"metadata": {"hosts": 240}
}'
| Field | Required | Notes |
|---|---|---|
automation |
yes | The automation's slug. GET /api/v1/automations lists them. |
status |
no | success (default) or failure. |
executed_at |
no | ISO 8601. Defaults to now. Naive datetimes are treated as UTC. Timestamps more than a few minutes in the future are refused; frozen months are refused. |
duration_seconds |
no | Non-negative integer. |
source |
no | The reporting system, e.g. ansible, n8n, github-actions. Shows up in the run list. |
external_id |
no, but send it | Your system's run or job id. Makes ingestion idempotent. |
ai |
no | Token usage for AI runs: {"model", "input_tokens", "output_tokens", "cached_tokens", "cost"?}. LumaTrack prices it server-side; see AI workloads. |
metadata |
no | Arbitrary JSON kept with the run (token counts, hosts touched, ticket ids). Capped at 16 KB; store a reference, not a full payload or transcript. |
Responses¶
| Code | Meaning |
|---|---|
201 |
Recorded. |
200 |
Replay: this external_id already exists. The body carries the original run and "deduplicated": true. |
202 |
Recorded but held: you are over the plan's monthly event cap. The run is stored and excluded from value math until you upgrade or the month resets. The body says so in warning. |
400 |
Something in the body is wrong, or executed_at falls in a frozen month. The error string names the exact field. |
401 |
Missing or invalid API key. |
404 |
No automation with that slug in your organization. |
429 |
Two distinct cases, told apart by the Retry-After header. With Retry-After: the org's per-minute rate limit; back off for that many seconds and retry (see Rate limits). Without it: past 3x the plan's monthly cap; the event was refused (with a durable record kept on our side); held events release when you upgrade. |
Held events are released automatically when capacity allows (an upgrade, or
the monthly reset). A released event keeps its original executed_at and
posts to that month; it counts against the event cap of the month it is
released in. If its month has frozen in the meantime, the frozen figures
stay as published and the event books a
closed-period correction instead:
held evidence is never dropped and never re-dated.
Every non-2xx body is {"error": "..."} with a message written for a
human.
Practices that pay off¶
Always send external_id. Delivery code retries; networks flake. With
an external_id, a duplicate POST is a no-op instead of double-counted
savings. Use the id your platform already has: the n8n execution id, the
Ansible Tower job id, the GitHub Actions run id.
Report failures. Wire your error path to send "status": "failure".
Failure runs subtract real cost and earn no savings, and the reliability
chart needs them to tell the truth. An integration that only reports
success produces a number nobody should sign.
Let executed_at default for live events. Set it only when reporting
something that genuinely happened earlier. If the timestamp falls in a
frozen month you get a 400; resubmit without executed_at to land the
evidence in the current period, or use backfill for real
historical loads.
Put measurements in metadata. Token usage, hosts patched, tickets
closed. It is stored with the run and comes back on GET /api/v1/runs.
Reading runs back¶
curl -s "$LUMATRACK_URL/api/v1/runs?automation=os-patching&status=failure&since=2026-06-01T00:00:00Z&limit=50" \
-H "Authorization: Bearer $LUMATRACK_KEY"
Newest first. Filters: automation (slug), status, since (ISO 8601),
limit (default 100, max 500). To page past the window, pass
before=<id of the last run you received> (an opaque run_... id) and
repeat while the response's has_more field is true. Held runs are included and flagged, and each run
carries its metadata.