Skip to content

Safety Reporting

HSE data is only useful if it reaches the people who act on it. Ditio captures incidents (alerts), checklist submissions, and machine inspections in the field — this guide covers pulling that data out for HSE dashboards, compliance archives, and external safety management systems.

  • You aggregate HSE KPIs (incident counts, checklist completion rates) across systems in a BI tool
  • Your compliance process requires archiving submitted checklists and incident reports as PDFs outside Ditio
  • You forward incidents to an external safety management system
  • API credentials with the reportingapiv1 scope — see Authentication. This scope can’t be added from the API client screen in Ditio Web; ask [email protected] to enable it on your client
  • Understanding of Pagination — all extraction endpoints use continuation tokens
  • Nothing extra to download PDFs and images: the same reportingapiv1 token works on the file URLs (see Step 2)

Examples use the environment variables from Environments and default to the test environment.

EndpointDescription
GET /v1/incident-registrationsSafety/HSE incident reports (alerts)
GET /v1/checklist-registrationsQA and safety checklist submissions
GET /v1/machine-checklist-registrationsMachine inspection checklists
GET /v1/machine-incident-registrationsMachine-specific safety incidents

All four support the shared extraction filters, except that the two checklist endpoints ignore OwnCompanyDataOnly (see Checklists from subsidiaries).

By default, v1/incident-registrations leaves out incidents registered on a machine; those come from v1/machine-incident-registrations. Add IncludeMachineIncidents=true to get them from v1/incident-registrations as well. If you do, don’t also sync v1/machine-incident-registrations into the same table, or you’ll get each machine incident twice.

By default, v1/checklist-registrations leaves out checklists made from a machine checklist template; those come from v1/machine-checklist-registrations. Add includeMachineChecklists=true to get both kinds from one endpoint. Each record’s baseTypeName tells them apart (Machine for machine checklists).

Terminal window
curl -s -G "$DITIO_REPORTING_BASE/v1/checklist-registrations" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "includeMachineChecklists=true" \
--data-urlencode "ModifiedSince=2026-06-01T00:00:00Z"

Use it when you sync checklists incrementally. When someone in Ditio changes a template’s type to or from machine checklist, all existing checklists made from it move between the two endpoints without a new modified time, so an incremental sync of one endpoint never sees them leave or arrive. With includeMachineChecklists=true they stay in your feed.

If your API client is an administrator on a parent company, both checklist endpoints also return the checklists of its subsidiary companies: regular checklists from v1/checklist-registrations, and machine checklists on the subsidiaries’ projects from v1/machine-checklist-registrations (or from v1/checklist-registrations with includeMachineChecklists=true). A subsidiary’s machine checked on a project outside your company group is not included. Add ExcludeDataFromSubsidiaries=true to leave subsidiaries out. A client with another role gets checklists on the projects it has access to, not its subsidiaries’ checklists company-wide.

On both checklist endpoints, CompanyId filters on the project’s company, and OwnCompanyDataOnly has no effect.

Terminal window
curl -s -G "$DITIO_REPORTING_BASE/v1/incident-registrations" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "FromDateTime=2026-06-01T00:00:00Z" \
--data-urlencode "ToDateTime=2026-07-01T00:00:00Z"

For ongoing syncs, switch to ModifiedSince with your last successful sync timestamp — see Full vs incremental sync.

Each incident, checklist, and absence record carries a pdfUrl field — an absolute link to the PDF Ditio generated when the record was submitted. It is null until generation has happened; skip those records and pick them up on the next sync.

The link points at the file endpoint of the Integration API, not at the Data Extraction API. Fetch it with the same reportingapiv1 token you used for the list (a ditioapiv3 token also works). For these bearer-only partner downloads, a request without a valid token gets 404 and no bytes, as does a file that no longer exists; for the partner client-credentials flow shown here, API-client authorization failures return 401 (for example, an unknown, disabled or expired client, or one with no configured scopes). File responses are private, no-store, so keep your own copy instead of relying on HTTP or CDN caching.

Terminal window
# $TOKEN from the token snippet on the Authentication page, with DITIO_SCOPE=reportingapiv1.
curl -s -G "$DITIO_REPORTING_BASE/v1/checklist-registrations" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "ModifiedSince=2026-06-01T00:00:00Z" \
| jq -r '.data[] | select(.pdfUrl != null) | .pdfUrl' \
| while read -r url; do curl -sfL -H "Authorization: Bearer $TOKEN" -O "$url"; done

Checklist records also expose the files attached inside the checklist via sections[].attachments[].url and sections[].images[].url.

Photos attached to incidents and checklists (and feed posts) are available in one paginated stream through Image Extraction, with GPS coordinates, uploader, and project/task context per image. To skip feed images, pass Sources once for each source you want. A comma-separated value such as Sources=alert,checklist matches no source and returns nothing:

Terminal window
curl -s -G "$DITIO_REPORTING_BASE/v1/images" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "Sources=alert" \
--data-urlencode "Sources=checklist" \
--data-urlencode "ModifiedSince=2026-06-01T00:00:00Z"

The image URLs point at the same Integration API file endpoint as pdfUrl, so download them with the same token.

import os
import requests
# Paste get_token (/getting-started/authentication/#getting-a-token-in-your-scripts) and fetch_all (/getting-started/pagination/#fetching-all-pages-in-code) above this.
REPORTING_BASE = os.environ.get(
"DITIO_REPORTING_BASE", "https://core-api.ditio.dev/reporting"
)
headers = {"Authorization": f"Bearer {get_token('reportingapiv1')}"}
last_sync = "2026-06-01T00:00:00Z"
for incident in fetch_all(f"{REPORTING_BASE}/v1/incident-registrations",
headers, {"ModifiedSince": last_sync}):
forward_to_hse_system(incident) # your logic
if incident.get("pdfUrl"):
response = requests.get(incident["pdfUrl"], headers=headers, timeout=60)
response.raise_for_status()
archive_pdf(incident["id"], response.content) # your logic
using System.Net.Http.Headers;
using System.Text.Json;
var reportingBase = Environment.GetEnvironmentVariable("DITIO_REPORTING_BASE")
?? "https://core-api.ditio.dev/reporting";
// One reportingapiv1 token covers the list and the PDF downloads.
using var reportingHttp = new HttpClient();
// DitioAuth.GetTokenAsync: see /getting-started/authentication/#token-expiry
reportingHttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", await DitioAuth.GetTokenAsync(reportingHttp, "reportingapiv1"));
var lastSync = "2026-06-01T00:00:00Z";
var url = $"{reportingBase}/v1/incident-registrations?ModifiedSince={Uri.EscapeDataString(lastSync)}";
// DitioPaging.FetchAllAsync: see /getting-started/pagination/#fetching-all-pages-in-code
await foreach (var incident in DitioPaging.FetchAllAsync<JsonElement>(reportingHttp, url))
{
// Forward the incident to your HSE system here.
if (incident.TryGetProperty("pdfUrl", out var pdfUrl) && pdfUrl.ValueKind == JsonValueKind.String)
{
var pdf = await reportingHttp.GetByteArrayAsync(pdfUrl.GetString());
await File.WriteAllBytesAsync($"{incident.GetProperty("id").GetString()}.pdf", pdf);
}
}
IssueCauseFix
pdfUrl is nullThe PDF hasn’t been generated yet (happens on submit/report)Skip and pick up on the next incremental sync
404 when downloading a pdfUrl or image URLFor these bearer-only downloads: no Authorization header (or an invalid, expired or wrong-scope token) was sent, or the file no longer existsSend the same reportingapiv1 bearer token you use for the list — see Step 2
401 when downloading a pdfUrl or image URLAPI-client authorization failed for the partner client-credentials token (for example, an unknown, disabled or expired client, or one with no configured scopes)Contact [email protected] with your client ID
v1/images returns nothing with Sources setSeveral sources sent in one comma-separated valueRepeat the parameter: Sources=alert&Sources=checklist
A subsidiary’s checklists are missingThe client isn’t an administrator on the parent company, ExcludeDataFromSubsidiaries=true is set, or (machine checklists) your sync is incremental from before they were addedUse an administrator client on the parent company and run one full sync — see Checklists from subsidiaries
Machine incidents missing from v1/incident-registrationsThey are left out by defaultAdd IncludeMachineIncidents=true, or sync v1/machine-incident-registrations — see Machine incidents
Missing recent editsFull sync filters on creation windowUse ModifiedSince incremental sync for ongoing pulls
Checklists missing from v1/checklist-registrations, without isDeletedTheir template is a machine checklist template, or was changed to oneAdd includeMachineChecklists=true and run one full sync — see Machine checklists
400 / invalid_scope at the token endpointYour client isn’t granted reportingapiv1Contact [email protected] with your client ID — see 400 invalid_scope
401 Unauthorized on a token that works elsewhereWrong scope — this API needs reportingapiv1See 401 — wrong scope
Empty results for a project you expectThe ProjectId/CompanyId filter doesn’t match data your client can seeCheck the filter and the client’s company