Generate and schedule reports
Generate reports on demand, poll until they’re ready, pull the JSON payload into your own tooling, download framework compliance PDFs, and set up schedules so reports are produced automatically.
Before you begin
Section titled “Before you begin”- Use a token with the
reportingcategory. Listing and fetching reports needsread; creating and deleting reports or schedules is admin only and needswrite. See Automate personal access tokens. - Some operations are admin-only. The gateway works out your role from the token, so a
403means the token’s user isn’t a tenant admin. - There are two report families. v2 (
/gateway/reporting/v2/reports) produces tenant-wide reports from templates such asactive-threatsorrecovery-readiness. v1 (/gateway/reporting/v1/reports) produces per-platform reports (aws,azure,onprem) from templates such ascompleted-scansorinfected-files. Both work the same way; this guide uses v2.
Generate a report
Section titled “Generate a report”Choose a template, set platform to all (the only v2 value), and give a period. periodStart must be before periodEnd, and periodEnd can’t be in the future. Optional filters narrow the rows; each key is a string array (values within a key are OR’d, keys are AND’d) and only keys valid for the chosen template are accepted.
v2 templates: asset-hunt-coverage, failed-hunts, recovery-readiness, active-threats, completed-hunts, evidence-report and machine-image-supply-chain (the Machine image report). A deployment accepts only the templates enabled for it.
curl -X POST "https://acme.app.elastio.com/gateway/reporting/v2/reports" \ -H "Authorization: Bearer $ELASTIO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "template": "active-threats", "platform": "all", "periodStart": "2026-10-01T00:00:00Z", "periodEnd": "2026-10-12T00:00:00Z", "description": "Weekly SOC review", "filters": { "severities": ["critical"], "account_ids": ["123456789012"] } }'import os, requests
BASE = "https://acme.app.elastio.com"HEADERS = {"Authorization": f"Bearer {os.environ['ELASTIO_TOKEN']}"}
body = { "template": "active-threats", "platform": "all", "periodStart": "2026-10-01T00:00:00Z", "periodEnd": "2026-10-12T00:00:00Z", "description": "Weekly SOC review", "filters": {"severities": ["critical"], "account_ids": ["123456789012"]},}resp = requests.post(f"{BASE}/gateway/reporting/v2/reports", headers=HEADERS, json=body, timeout=30)resp.raise_for_status()report_id = resp.json()["id"]$Base = "https://acme.app.elastio.com"$Headers = @{ Authorization = "Bearer $env:ELASTIO_TOKEN" }$Body = @{ template = "active-threats" platform = "all" periodStart = "2026-10-01T00:00:00Z" periodEnd = "2026-10-12T00:00:00Z" description = "Weekly SOC review" filters = @{ severities = @("critical"); account_ids = @("123456789012") }} | ConvertTo-Json -Depth 4$Report = Invoke-RestMethod -Method Post -Uri "$Base/gateway/reporting/v2/reports" -Headers $Headers ` -ContentType "application/json" -Body $Body$Report.idThe job is queued and the response is 202:
{ "id": "b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e", "status": "processing", "title": "…", "format": "json", "createdAt": "2026-10-12T09:00:00Z"}Wait for the report and fetch it
Section titled “Wait for the report and fetch it”Poll the lightweight status endpoint until status is ready or failed. A failed report still returns 200, with the reason in error. Fetching the full report before it’s ready returns 409 REPORT_NOT_READY.
ID=b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0euntil [ "$(curl -s "https://acme.app.elastio.com/gateway/reporting/v2/reports/$ID/status" \ -H "Authorization: Bearer $ELASTIO_TOKEN" | jq -r .status)" != "processing" ]; do sleep 10; done
curl "https://acme.app.elastio.com/gateway/reporting/v2/reports/$ID" \ -H "Authorization: Bearer $ELASTIO_TOKEN" -o report.jsonimport time, json
while True: s = requests.get(f"{BASE}/gateway/reporting/v2/reports/{report_id}/status", headers=HEADERS, timeout=30) s.raise_for_status() status = s.json() if status["status"] != "processing": break time.sleep(10)
if status["status"] == "failed": raise RuntimeError(status["error"]["message"])
report = requests.get(f"{BASE}/gateway/reporting/v2/reports/{report_id}", headers=HEADERS, timeout=60).json()print(report["title"], report["rowCount"], "rows")with open("report.json", "w") as f: json.dump(report["data"], f)$Id = $Report.iddo { Start-Sleep -Seconds 10 $Status = Invoke-RestMethod -Uri "$Base/gateway/reporting/v2/reports/$Id/status" -Headers $Headers} while ($Status.status -eq "processing")
if ($Status.status -eq "failed") { throw $Status.error.message }$Full = Invoke-RestMethod -Uri "$Base/gateway/reporting/v2/reports/$Id" -Headers $Headers$Full.data | ConvertTo-Json -Depth 10 | Set-Content report.jsonStatus while the job runs, then the full payload when it’s ready:
{ "id": "b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e", "status": "processing" }{ "id": "b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e", "title": "…_2026-10-01_2026-10-12", "template": "active-threats", "format": "json", "platform": "all", "periodStart": "2026-10-01T00:00:00Z", "periodEnd": "2026-10-12T00:00:00Z", "description": "Weekly SOC review", "createdAt": "2026-10-12T09:00:00Z", "generatedAt": "2026-10-12T09:01:12Z", "status": "ready", "data": [ { … } ], "rowCount": 14, "filters": { "severities": ["critical"], "account_ids": ["123456789012"] }, "createdBy": "jane.doe@example.com", "contentSha256": "…"}data is an array of row objects (or a single object for evidence-report). The row fields depend on the template and aren’t described column by column in the spec, so inspect a sample before mapping them.
List and delete reports
Section titled “List and delete reports”List reports with page-number pagination: page (1-based) and pageSize (default 20, capped at 100). Filter by template, status, createdFrom/createdTo (RFC 3339) or search (2–200 characters, matched against title and description).
curl "https://acme.app.elastio.com/gateway/reporting/v2/reports?status=ready&template=active-threats&pageSize=50" \ -H "Authorization: Bearer $ELASTIO_TOKEN"page, reports = 1, []while True: r = requests.get(f"{BASE}/gateway/reporting/v2/reports", headers=HEADERS, params={"status": "ready", "page": page, "pageSize": 100}, timeout=30) r.raise_for_status() body = r.json() reports += body["items"] if page * body["pageSize"] >= body["total"]: break page += 1(Invoke-RestMethod -Uri "$Base/gateway/reporting/v2/reports?status=ready&pageSize=50" -Headers $Headers).items | Format-Table title, template, status, rowCount, createdAt{ "items": [ { "id": "b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e", "title": "…_2026-10-01_2026-10-12", "template": "active-threats", "format": "json", "platform": "all", "periodStart": "2026-10-01T00:00:00Z", "periodEnd": "2026-10-12T00:00:00Z", "description": "Weekly SOC review", "generatedAt": "2026-10-12T09:01:12Z", "status": "ready", "createdAt": "2026-10-12T09:00:00Z", "rowCount": 14, "filters": { "severities": ["critical"] }, "createdBy": "jane.doe@example.com" } ], "total": 1, "page": 1, "pageSize": 50}Delete a report you no longer need with DELETE /gateway/reporting/v2/reports/{id} (admin only, returns 204).
curl -X DELETE "https://acme.app.elastio.com/gateway/reporting/v2/reports/b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e" \ -H "Authorization: Bearer $ELASTIO_TOKEN"requests.delete(f"{BASE}/gateway/reporting/v2/reports/{report_id}", headers=HEADERS, timeout=30).raise_for_status()Invoke-RestMethod -Method Delete -Uri "$Base/gateway/reporting/v2/reports/$Id" -Headers $HeadersDownload compliance and document PDFs
Section titled “Download compliance and document PDFs”The Reports tag also includes PDF compliance reports for DORA, ISO 27001, NIST CSF, NYDFS and PCI DSS under /gateway/api/v1/reports/compliance/…. Each accepts optional start_date and end_date (ISO dates; default month to date) and type (pdf, the default, or json for the underlying compliance data).
curl "https://acme.app.elastio.com/gateway/api/v1/reports/compliance/nist?start_date=2026-09-01&end_date=2026-09-30" \ -H "Authorization: Bearer $ELASTIO_TOKEN" \ -o nist-csf-2026-09.pdfresp = requests.get(f"{BASE}/gateway/api/v1/reports/compliance/nist", headers=HEADERS, params={"start_date": "2026-09-01", "end_date": "2026-09-30"}, timeout=120)resp.raise_for_status()with open("nist-csf-2026-09.pdf", "wb") as f: f.write(resp.content)Invoke-WebRequest -Uri "$Base/gateway/api/v1/reports/compliance/nist?start_date=2026-09-01&end_date=2026-09-30" ` -Headers $Headers -OutFile "nist-csf-2026-09.pdf"To turn your own data (for example, a report payload) into a PDF, send a structured document to the renderer. It’s tenant-admin only and enabled when the tenant has the eql-reporting or documents-pdf feature; otherwise it returns 404. The body has a required title, optional subtitle, and up to 32 sections, each with an optional heading, paragraphs and a table of columns and rows. HTML, executable templates and external resources aren’t supported. Limits: 8 MiB, 10,000 table rows and 512 pages; a 429 means the renderer is busy, so retry later.
curl -X POST "https://acme.app.elastio.com/gateway/shared-services/v1/documents/pdf" \ -H "Authorization: Bearer $ELASTIO_TOKEN" \ -H "Content-Type: application/json" \ -o soc-review.pdf \ -d '{ "title": "Weekly SOC review", "subtitle": "1-12 October 2026", "sections": [ { "heading": "Summary", "paragraphs": ["14 critical findings were open during the period."] }, { "heading": "Accounts", "table": { "columns": ["Account", "Findings"], "rows": [["123456789012", "14"]] } } ] }'doc = { "title": "Weekly SOC review", "subtitle": "1-12 October 2026", "sections": [ {"heading": "Summary", "paragraphs": [f"{report['rowCount']} rows in the active-threats report."]}, {"heading": "Accounts", "table": {"columns": ["Account", "Findings"], "rows": [["123456789012", "14"]]}}, ],}resp = requests.post(f"{BASE}/gateway/shared-services/v1/documents/pdf", headers=HEADERS, json=doc, timeout=120)resp.raise_for_status()open("soc-review.pdf", "wb").write(resp.content)$Doc = @{ title = "Weekly SOC review" subtitle = "1-12 October 2026" sections = @( @{ heading = "Summary"; paragraphs = @("14 critical findings were open during the period.") }, @{ heading = "Accounts"; table = @{ columns = @("Account", "Findings"); rows = @(,@("123456789012", "14")) } } )} | ConvertTo-Json -Depth 6Invoke-WebRequest -Method Post -Uri "$Base/gateway/shared-services/v1/documents/pdf" -Headers $Headers ` -ContentType "application/json" -Body $Doc -OutFile "soc-review.pdf"Both operations return the PDF bytes (application/pdf) on success. Table cells are strings.
Schedule recurring reports
Section titled “Schedule recurring reports”A schedule generates a v2 report on a cadence over a rolling window. name and template are required; the rest default to period: last_7_days, cadence: daily, format: csv, filters: {}, delivery: {}, enabled: true, and cadence_config of {"time_utc": "08:00"} for daily or {} for hourly.
period:last_24_hours,last_7_days,last_30_daysorlast_90_days.cadence:hourly,daily,weeklyormonthly.weeklyneedsday_of_weekandtime_utc;monthlyneedsday_of_month(at most 28) andtime_utc.time_utcisHH:MMin UTC.format(csvorjsonl) anddelivery(email_recipients,webhook_url) are validated and stored, but generated reports remain JSON and nothing is emailed or posted yet. Collect scheduled reports from the reports list.- The tenant has a cap on live schedules; creating one past the cap returns
422.
curl -X POST "https://acme.app.elastio.com/gateway/reporting/v2/schedules" \ -H "Authorization: Bearer $ELASTIO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Monday recovery readiness", "template": "recovery-readiness", "period": "last_7_days", "cadence": "weekly", "cadence_config": { "day_of_week": "monday", "time_utc": "07:00" } }'body = { "name": "Monday recovery readiness", "template": "recovery-readiness", "period": "last_7_days", "cadence": "weekly", "cadence_config": {"day_of_week": "monday", "time_utc": "07:00"},}resp = requests.post(f"{BASE}/gateway/reporting/v2/schedules", headers=HEADERS, json=body, timeout=30)resp.raise_for_status()schedule = resp.json()print(schedule["id"], schedule["next_run_at"], schedule["sync_status"])$Body = @{ name = "Monday recovery readiness" template = "recovery-readiness" period = "last_7_days" cadence = "weekly" cadence_config = @{ day_of_week = "monday"; time_utc = "07:00" }} | ConvertTo-Json -Depth 3Invoke-RestMethod -Method Post -Uri "$Base/gateway/reporting/v2/schedules" -Headers $Headers ` -ContentType "application/json" -Body $Body{ "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", "name": "Monday recovery readiness", "template": "recovery-readiness", "period": "last_7_days", "cadence": "weekly", "cadence_config": { "day_of_week": "monday", "time_utc": "07:00" }, "format": "csv", "filters": {}, "delivery": {}, "enabled": true, "created_at": "2026-10-12T09:10:00Z", "created_by": "jane.doe@example.com", "last_run_at": null, "next_run_at": "2026-10-19T07:00:00Z", "sync_status": "pending"}Pause a schedule with PATCH /gateway/reporting/v2/schedules/{id} and {"enabled": false}. Any change resets sync_status to pending until the scheduler catches up. GET /gateway/reporting/v2/schedules returns every schedule as items and total, with no pagination.
All endpoints
Section titled “All endpoints”Reports
18 operations in the reference →- GET
/gateway/api/v1/scan-jobs/{scan_job_id}/reportGet a hunt report by job ID - GET
/gateway/api/v1/scans/{scan_id}/reportGet a hunt report by run ID - GET
/gateway/api/v1/reports/compliance/doraGenerate DORA Compliance Report - GET
/gateway/api/v1/reports/compliance/iso27001Generate ISO 27001 Compliance Report - GET
/gateway/api/v1/reports/compliance/nistGenerate NIST CSF Compliance Report - GET
/gateway/api/v1/reports/compliance/nydfsGenerate NYDFS Compliance Report - GET
/gateway/api/v1/reports/compliance/pciGenerate PCI DSS Compliance Report - POST
/gateway/shared-services/v1/documents/pdfRender a structured document as a PDF - GET
/gateway/reporting/v1/reportsList reports - POST
/gateway/reporting/v1/reportsCreate a report - GET
/gateway/reporting/v1/reports/{id}Get a report - DELETE
/gateway/reporting/v1/reports/{id}Delete a report - GET
/gateway/reporting/v1/reports/{id}/statusGet report status - GET
/gateway/reporting/v2/reportsList v2 reports - POST
/gateway/reporting/v2/reportsCreate a v2 report - GET
/gateway/reporting/v2/reports/{id}Get a v2 report - DELETE
/gateway/reporting/v2/reports/{id}Delete a v2 report - GET
/gateway/reporting/v2/reports/{id}/statusGet v2 report status
Report Schedules
5 operations in the reference →- GET
/gateway/reporting/v2/schedulesList report schedules - POST
/gateway/reporting/v2/schedulesCreate a report schedule - GET
/gateway/reporting/v2/schedules/{id}Get a report schedule - PATCH
/gateway/reporting/v2/schedules/{id}Update a report schedule - DELETE
/gateway/reporting/v2/schedules/{id}Delete a report schedule