Skip to content

Backfilling history

Your automation platform already knows about the runs that happened before you adopted LumaTrack. Import them so the trend lines, cumulative value, and reliability charts start with the truth instead of a cliff.

Two ways in:

  • In the app: Automations, Import runs. Paste or upload, see the results per row.
  • Over the API: POST /api/v1/runs/backfill.

Either way, imported runs carry a permanent backfilled provenance flag, so a chart reader can always tell evidence that arrived live from evidence that arrived in bulk.

Formats

CSV (a template is downloadable in the app at /automations/import/template.csv):

automation,status,executed_at,duration_seconds,source,external_id
os-patching,success,2026-05-14T02:30:00Z,142,ansible,tower-job-99412
os-patching,failure,2026-05-15T02:30:00Z,98,ansible,tower-job-99511

Or JSONL, one object per line, same fields plus optional metadata:

{"automation": "os-patching", "status": "success", "executed_at": "2026-05-14T02:30:00Z", "external_id": "tower-job-99412", "metadata": {"hosts": 240}}

automation (slug) and executed_at (ISO 8601; naive timestamps are treated as UTC) are required. A backfill row without a timestamp is meaningless, so unlike live ingest there is no "default to now".

Over the API

curl -s -X POST "$LUMATRACK_URL/api/v1/runs/backfill" \
  -H "Authorization: Bearer $LUMATRACK_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"format\": \"csv\", \"data\": $(jq -Rs . < history.csv)}"

The response is an honest accounting:

{
  "processed": 1193,
  "created": 1180,
  "deduplicated": 12,
  "held": 0,
  "errors": [
    {"line": 481, "error": "March 2026 is closed; its numbers are frozen."}
  ]
}

processed is the total number of input rows: created (which includes the held subset) plus deduplicated plus the count of errors. It lets you reconcile the response against the file you sent in one number.

The rules, in the order each row meets them

  1. The automation slug must exist in your organization.
  2. executed_at must be present, parse as ISO 8601, and not be in the future.
  3. Rows older than your plan's retention window are refused outright (they would be pruned immediately anyway).
  4. Rows in frozen months are refused, exactly like live ingest. Closed numbers never move, not even for bulk loads.
  5. external_id replays dedupe: re-importing the same file is safe and reports deduplicated counts instead of creating duplicates.
  6. Monthly plan caps apply per target month: rows over a month's cap are stored as held, never dropped.
  7. A bad row never aborts the rest of the file. Every rejected row comes back in errors with its line number and the reason.

Rules 5 and 7 together make the sane strategy simple: export everything, import it all, fix the rows listed in errors, and import the whole file again.