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.
When to use this
Section titled “When to use this”- 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
Prerequisites
Section titled “Prerequisites”- API credentials with the
reportingapiv1scope — 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
reportingapiv1token works on the file URLs (see Step 2)
Examples use the environment variables from Environments and default to the test environment.
Available endpoints
Section titled “Available endpoints”| Endpoint | Description |
|---|---|
GET /v1/incident-registrations | Safety/HSE incident reports (alerts) |
GET /v1/checklist-registrations | QA and safety checklist submissions |
GET /v1/machine-checklist-registrations | Machine inspection checklists |
GET /v1/machine-incident-registrations | Machine-specific safety incidents |
All four support the shared extraction
filters, except that
the two checklist endpoints ignore OwnCompanyDataOnly (see Checklists from
subsidiaries).
Machine incidents
Section titled “Machine incidents”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.
Machine checklists
Section titled “Machine checklists”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).
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.
Checklists from subsidiaries
Section titled “Checklists from subsidiaries”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.
Step 1 — Pull incident registrations
Section titled “Step 1 — Pull incident registrations”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.
Step 2 — Archive the generated PDFs
Section titled “Step 2 — Archive the generated PDFs”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.
# $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"; doneChecklist records also expose the files attached inside the checklist via
sections[].attachments[].url and sections[].images[].url.
Step 3 — Pull the site photos
Section titled “Step 3 — Pull the site photos”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:
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.
Python example — incremental HSE sync
Section titled “Python example — incremental HSE sync”import osimport 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 logicC# example — incremental HSE sync
Section titled “C# example — incremental HSE sync”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-expiryreportingHttp.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-codeawait 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); }}Common issues
Section titled “Common issues”| Issue | Cause | Fix |
|---|---|---|
pdfUrl is null | The 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 URL | For these bearer-only downloads: no Authorization header (or an invalid, expired or wrong-scope token) was sent, or the file no longer exists | Send the same reportingapiv1 bearer token you use for the list — see Step 2 |
401 when downloading a pdfUrl or image URL | API-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 set | Several sources sent in one comma-separated value | Repeat the parameter: Sources=alert&Sources=checklist |
| A subsidiary’s checklists are missing | The client isn’t an administrator on the parent company, ExcludeDataFromSubsidiaries=true is set, or (machine checklists) your sync is incremental from before they were added | Use an administrator client on the parent company and run one full sync — see Checklists from subsidiaries |
Machine incidents missing from v1/incident-registrations | They are left out by default | Add IncludeMachineIncidents=true, or sync v1/machine-incident-registrations — see Machine incidents |
| Missing recent edits | Full sync filters on creation window | Use ModifiedSince incremental sync for ongoing pulls |
Checklists missing from v1/checklist-registrations, without isDeleted | Their template is a machine checklist template, or was changed to one | Add includeMachineChecklists=true and run one full sync — see Machine checklists |
400 / invalid_scope at the token endpoint | Your client isn’t granted reportingapiv1 | Contact [email protected] with your client ID — see 400 invalid_scope |
401 Unauthorized on a token that works elsewhere | Wrong scope — this API needs reportingapiv1 | See 401 — wrong scope |
| Empty results for a project you expect | The ProjectId/CompanyId filter doesn’t match data your client can see | Check the filter and the client’s company |
Related
Section titled “Related”- Data Extraction reference — every extraction endpoint, schemas, and “Try it”
- Project Data — resolving project IDs in incident records