Skip to content

Image Extraction

Extract every image attached anywhere in a Ditio project — feed posts, alerts (incident registrations), and checklist submissions — in a single paginated stream. Intended for BI tools, archives, and integrations that need a complete image inventory with GPS, uploader, timestamps, and project/task context.

GET /v1/images
  • Test: https://core-api.ditio.dev/reporting/v1/images
  • Production: https://core-api.ditio.app/reporting/v1/images
  • Auth: OAuth 2.0 client credentials with the reportingapiv1 scope — see Authentication

One row per (image × parent record) combination. A parent record can be a feed post, an alert/incident, or a checklist submission. Each row includes:

  • File identity: id, original filename, type, size
  • Four download URLs (original + thumbnail/medium/large resizes)
  • Source: feed, alert, or checklist
  • Project/task context: projectId, projectNumber, projectName, taskId, taskName, activityId, activityNumber, companyId, projectCompanyId
  • Uploader: createdByUserId, createdByUserName, createdDateTime
  • GPS: latitude, longitude, accuracy (falls back to the parent record’s location when the image itself has no embedded coordinates)
  • Road reference: roadReference — country-agnostic road-network position resolved asynchronously after upload (e.g. NVDB vegreferanse for Norwegian roads)

Images are returned as URLs, not bytes. Fetch the bytes from the returned URLs using the same bearer token.

ParameterTypeRequiredDescription
ProjectIdstringNoRestrict to one project.
CompanyIdstringNoRestrict to data owned by this project-company.
Sourcesstring[]NoRestrict to a subset. Allowed values: feed, alert, checklist. Omit or leave empty to include all three.
FromDateTimedatetimeNoFull-sync window start. Applies to the parent record’s creation date.
ToDateTimedatetimeNoFull-sync window end.
ModifiedSincedatetimeNoIncremental-sync window start. When set, switches the response into incremental mode — results are ordered by the parent’s last modification timestamp.
ModifiedBeforedatetimeNoIncremental-sync window end. Requires ModifiedSince. Must be strictly greater than ModifiedSince.
ChunkLimitintNoTarget page size, measured in parent records. Default 2500, range 1–10000. A single parent may emit multiple image rows, so data.length can exceed ChunkLimit.
ContinuationTokenstringNoOpaque token from a previous response — echo it back verbatim to get the next page.
RequireGeoCoordinateboolNoWhen true, drop images that have neither file-level nor parent-level GPS. Default false.
OwnCompanyDataOnlyboolNoWhen true, only return records owned by the calling company (not those merely shared with it).
ExcludeDataFromSubsidiariesboolNoFor parent-company callers, exclude data from subsidiary companies.
ModeTriggerWhen to use
FullModifiedSince not setInitial backfill. Optionally bound the window with FromDateTime / ToDateTime.
IncrementalModifiedSince setOngoing sync — pass your last successful sync timestamp. Tighter is better (see performance notes).

The response field syncTypeName echoes back "full" or "incremental".

{
"data": [],
"meta": {},
"continuationToken": "cG9zdHwyMDI2LTA0LTIzVDEwOjAwOjAwWg==",
"syncTypeName": "incremental",
"recordCount": 1842,
"deletedRecordCount": 0,
"links": { "next": "/v1/images?ProjectId=...&ContinuationToken=..." }
}
{
"id": "6610f1a3c5000000000000aa",
"fileName": "uploaded-6610f1a3c5.jpg",
"fileNameOriginal": "IMG_20260412_093015.jpg",
"fileType": "image/jpeg",
"fileSize": 2847391,
"downloadUrl": "https://core-api.ditio.dev/core/api/file/6610f1a3c5000000000000aa",
"thumbnailUrl": "https://core-api.ditio.dev/core/api/file/6610f1a3c5000000000000aa?MaxDimension=128",
"mediumUrl": "https://core-api.ditio.dev/core/api/file/6610f1a3c5000000000000aa?MaxDimension=640",
"largeUrl": "https://core-api.ditio.dev/core/api/file/6610f1a3c5000000000000aa?MaxDimension=1980",
"source": "feed",
"originDescription": "Feed",
"collectionRef": "Post",
"idRef": "6610e02a00000000000000bb",
"companyId": "620000000000000000000001",
"projectCompanyId": "620000000000000000000001",
"projectId": "65a000000000000000000002",
"projectNumber": "P-2026-042",
"projectName": "Prosjekt Fjellhall",
"taskId": "65b000000000000000000003",
"taskName": "Brudekke",
"activityId": null,
"activityNumber": null,
"createdByUserId": "7a3f0000000000000000000c",
"createdByUserName": "Kari Nordmann",
"createdDateTime": "2026-04-12T09:30:15Z",
"parentModifiedDateTime": "2026-04-12T10:05:02Z",
"latitude": 59.9139,
"longitude": 10.7522,
"accuracy": 8.5,
"roadReference": {
"provider": "nvdb",
"countryCode": "NO",
"referenceText": "Ev6 S2D1 m1234",
"roadCategory": "E",
"roadNumber": "6",
"section": "S2D1",
"meterFromStart": 1234,
"distanceFromCoordinateMeters": 4.5
},
"userSecurityLevel": 0
}

roadReference is resolved asynchronously from the image’s GPS after upload. It is null when there is no GPS, no road match within tolerance, the provider was unreachable, the uploader’s company doesn’t have the nvdb-road-reference pilot flag enabled, or the background enrichment job has not yet processed the upload (typically populated within seconds — re-read the image to pick up the value). The first provider is NVDB (Statens vegvesen, Norway) — referenceText is the same kortform you’d see in Vegkart.

  • feed — user posted the image directly to the project feed
  • alert — image attached to an alert / incident registration
  • checklist — image on a checklist submission (section banner or question answer)

Use source to categorize and originDescription for a human-readable label (e.g. a checklist template name).

The four URL fields point to Ditio’s authenticated file endpoint. Call them with the same bearer token:

Terminal window
curl -L -o image.jpg \
-H "Authorization: Bearer $TOKEN" \
"https://core-api.ditio.dev/core/api/file/6610f1a3c5000000000000aa?MaxDimension=640"

Responses include Cache-Control headers — bytes are cacheable for 14 days by default, so CDN/client caching is safe.

Paging is driven by continuationToken:

  1. Call the endpoint with your initial filter (no ContinuationToken).
  2. If the response has a non-empty continuationToken, pass it verbatim as the ContinuationToken query parameter on the next call (keep everything else the same).
  3. Repeat until the response’s continuationToken is absent or empty.

The token is opaque — don’t parse or modify it. It encodes which source (feed / alert / checklist) is being drained and where within that source; when one source exhausts, the token automatically advances to the next enabled source. See Pagination.

Terminal window
export DITIO_REPORTING_BASE="https://core-api.ditio.dev/reporting" # test
# First page
curl -s -G "$DITIO_REPORTING_BASE/v1/images" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "ProjectId=65a000000000000000000002" \
--data-urlencode "ChunkLimit=1000" \
> page1.json
# Follow the continuation token until empty
CT=$(jq -r '.continuationToken // empty' page1.json)
while [ -n "$CT" ]; do
curl -s -G "$DITIO_REPORTING_BASE/v1/images" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "ProjectId=65a000000000000000000002" \
--data-urlencode "ChunkLimit=1000" \
--data-urlencode "ContinuationToken=$CT" \
> page_next.json
CT=$(jq -r '.continuationToken // empty' page_next.json)
done

Example — hourly incremental sync (Python)

Section titled “Example — hourly incremental sync (Python)”
import os
import requests
REPORTING_BASE = os.environ.get(
"DITIO_REPORTING_BASE", "https://core-api.ditio.dev/reporting"
)
headers = {"Authorization": f"Bearer {token}"}
def sync_images(last_sync_timestamp):
params = {"ModifiedSince": last_sync_timestamp, "ChunkLimit": 2500}
while True:
page = requests.get(
f"{REPORTING_BASE}/v1/images", headers=headers, params=params
).json()
for image_row in page["data"]:
process(image_row) # your logic
continuation = page.get("continuationToken")
if not continuation:
break
params["ContinuationToken"] = continuation
# Persist the timestamp of the last row you processed,
# and pass it as ModifiedSince on the next run.
sync_images("2026-04-12T00:00:00Z")
  • Narrow your window. For incremental sync, pass ModifiedSince close to your last successful sync. For full backfills, prefer a paged FromDateTime / ToDateTime window (e.g. month-by-month) over a single unbounded call.
  • Scope by project or company when you can. ProjectId and CompanyId dramatically narrow the underlying query.
  • Use Sources to skip what you don’t need. If you only care about checklist images, pass Sources=checklist.
  • Respect the ChunkLimit=2500 default. Larger values (up to 10000) increase per-call payload and latency; smaller values increase round-trips.
  • Cache image bytes. URL patterns are stable for the lifetime of the file; use standard HTTP caching.
  • deletedRecordCount is always 0. Deletion tracking isn’t supported in v1; if a file is removed it simply stops appearing in future responses.
  • ChunkLimit is counted in parent records, not image rows. A parent with multiple attached images contributes multiple rows.
  • EXIF data is not returned. Ditio does not persist camera make/model, orientation, or capture-time metadata beyond the upload timestamp and GPS. Width/height are likewise not stored — read the image bytes if you need them.
  • No dedup across parents. The same underlying file referenced from two parents (rare) yields two rows. Use (id, collectionRef, idRef) as the row identity to dedup client-side.
  • Initial backfill for large tenants is the heaviest pattern this API supports. Chunk your backfill with ModifiedSince + ModifiedBefore windows if you hit timeouts.
StatusWhen
400 Bad RequestModifiedBefore without ModifiedSince, ModifiedBefore <= ModifiedSince, or malformed ContinuationToken
401 UnauthorizedMissing/expired bearer token
403 ForbiddenToken valid but caller doesn’t have access to the requested ProjectId / CompanyId
500 Internal Server ErrorUnexpected failure — retry with backoff

A ready-to-use Postman collection with the token request and example image-extraction calls is available on request — contact support@ditio.no.