Skip to content

Any platform over HTTP

There is no SDK requirement. If your tool can make an HTTP POST, it can report runs. The full field reference is in Recording runs; these are working starting points for the platforms we see most.

In every example: report failures too (wire the error path), and pass the job id your platform already has as external_id so retries dedupe.

curl / bash

curl -sS -X POST "$LUMATRACK_URL/api/v1/runs" \
  -H "Authorization: Bearer $LUMATRACK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"automation": "cert-renewal", "status": "success", "source": "cron", "external_id": "'"$(date +%Y%m%d)"'-cert-renewal"}'

Ansible

A task at the end of the play; add a rescue block that posts status: failure.

- name: Report run to LumaTrack
  ansible.builtin.uri:
    url: "{{ lumatrack_url }}/api/v1/runs"
    method: POST
    headers:
      Authorization: "Bearer {{ lumatrack_key }}"
    body_format: json
    body:
      automation: os-patching
      status: success
      source: ansible
      units: "{{ ansible_play_hosts_all | length }}"
      external_id: "{{ awx_job_id | default(omit) }}"
    status_code: [200, 201, 202]

GitHub Actions

The published action is one step, reports failures and cancellations honestly, and never breaks your pipeline when LumaTrack is unreachable:

- name: Report run to LumaTrack
  if: always()
  uses: LumaTrack/report-run@v1
  with:
    api-key: ${{ secrets.LUMATRACK_KEY }}
    automation: deploy-pipeline
    status: ${{ job.status }}

The action reports to https://lumatrack.io unless you pass base-url, which is only needed on a self-hosted or custom domain.

The action also takes units (per-unit valuation), duration-seconds, failure-reason, and a metadata JSON object; retries are idempotent via the default external id. Prefer plain curl? The equivalent request:

- name: Report run to LumaTrack
  if: always()
  env:
    LUMATRACK_URL: ${{ vars.LUMATRACK_URL }}
    LUMATRACK_KEY: ${{ secrets.LUMATRACK_KEY }}
  run: |
    STATUS="${{ job.status == 'success' && 'success' || 'failure' }}"
    curl -sS -X POST "$LUMATRACK_URL/api/v1/runs" \
      -H "Authorization: Bearer $LUMATRACK_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"automation\": \"deploy-pipeline\", \"status\": \"$STATUS\", \"source\": \"github-actions\", \"external_id\": \"${{ github.run_id }}-${{ github.run_attempt }}\"}"

PowerShell

Full recipe with retry-safe function and RMM notes: PowerShell & RMM.

$Body = @{
    automation  = "ad-user-offboarding"
    status      = "success"
    source      = "powershell"
    external_id = $JobId
} | ConvertTo-Json

Invoke-RestMethod -Method Post -Uri "$BaseUrl/api/v1/runs" `
    -Headers @{ Authorization = "Bearer $ApiKey" } `
    -ContentType "application/json" -Body $Body

Go

payload, _ := json.Marshal(map[string]any{
    "automation":  "log-rotation",
    "status":      "success",
    "source":      "go",
    "external_id": jobID,
})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/runs", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

C

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", apiKey);
var payload = JsonSerializer.Serialize(new {
    automation = "mailbox-provisioning",
    status = "success",
    source = "csharp",
    external_id = jobId,
});
var response = await http.PostAsync($"{baseUrl}/api/v1/runs",
    new StringContent(payload, Encoding.UTF8, "application/json"));

Java

String payload = """
    {"automation": "vm-snapshot-cleanup", "status": "success",
     "source": "java", "external_id": "%s"}""".formatted(jobId);
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(baseUrl + "/api/v1/runs"))
    .header("Authorization", "Bearer " + apiKey)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(payload))
    .build();
HttpResponse<String> response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());

ServiceNow, Jira, and other ITSM platforms

Any platform with an outbound REST step works today: in ServiceNow, a Flow Designer REST step (or an outbound REST Message fired from a Business Rule) posting the JSON above; equivalents exist in Jira Automation and Zendesk webhooks. Purpose-built ITSM connectors with deflection measurement are on the LumaTrack roadmap; until then, outbound REST is the supported path.

MSP automation platforms (Rewst, Pia, custom RMM scripts)

Dedicated walkthroughs: Rewst, HaloPSA, PowerShell & RMM. For SOC platforms, see Tines.

MSP platforms that can make an outbound HTTP call at the end of a workflow report runs the same way. The pattern for a multi-client book:

  1. One LumaTrack client workspace per managed client (MSP plan). Each client org has its own API key, so the value ledger, the QBR report, and the audit trail stay per client by construction.
  2. Add one HTTP step at the end of each workflow (in Rewst: an HTTP request action as the workflow's last step; consult your platform's docs for the exact action name). POST the JSON below with the client org's key. Map your platform's execution id to external_id so retried workflows dedupe instead of double-counting.
  3. Wire the failure path too. A workflow that only reports success produces a number nobody should put in front of a client.
{
  "automation": "user-onboarding",
  "status": "success",
  "source": "rewst",
  "external_id": "<your platform's execution id>",
  "duration_seconds": 42,
  "units": 3
}

Time-saved dashboards inside the platform remain useful as internal meters. What this step adds is the client-facing layer: dollars, per client, across every tool you run, with the methodology printed on the page. Setup cost, honestly: one HTTP step per workflow, then a few minutes of baseline entry per automation (the counterfactual minutes and rate the dollar figure is computed from; labeled as your assumption on every report).

If editing every workflow is impractical, two alternatives: a single "reporter" workflow your other workflows call as a sub-step, or a nightly backfill of an execution-log CSV export.