Skip to main content

Provider API Usage Quickstart

This guide is for provider teams who need to call the API with the minimum required setup.
It covers batch submission and async job tracking.

:::tip Get your token first Before sending any request, obtain a bearer token:

Use it in every request: Authorization: Bearer <access_token> :::


1. Endpoints

PurposeMethodPath
Submit batchPOST/api/provider/v1/controls
Check job statusGET/api/provider/v1/jobs/{job_id}
Check per-item resultsGET/api/provider/v1/jobs/{job_id}/items

Base URLs

EnvironmentURL
Localhttp://localhost:3002
INThttps://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io

2. Request Model

POST /api/provider/v1/controls expects:

{
"schema_version": "1.0",
"device_id": "AndId_c428366d643e9ce2",
"controls": [ /* array, 1–500 items */ ]
}

Required fields at request root

FieldDescriptionFormat
schema_versionContract versionMust be exactly "1.0"
device_idUnique identifier of the provider device/app instance that sent the batchString, max 100 chars
controlsBatch of controls to ingestNon-empty array, max 500

Required fields per controls[] item

FieldDescriptionFormat
started_atControl run startISO 8601 with timezone
partial_closeInterim close timestampISO 8601 with timezone; must be after started_at
ended_atEnd-of-inspection timestampOptional ISO 8601; if set, must be ≥ partial_close
control_grade_pctInspection coverage levelInteger: 25, 50, 75, or 100
executing_tu_codeOrganization executing the controlNon-empty numeric string (e.g. 801)
inspectorInspector identifierNon-empty string
journeyJourney context blockObject — see below
paper_ticketsAggregated paper-ticket countsNon-empty array — see below

Required journey fields

FieldDescriptionFormat
journey.carrier_tu_codeCarrier/company responsible for transport executionNon-empty numeric string (e.g. 801)
journey.from_stop_nameHuman-readable boarding stop nameNon-empty string
journey.to_stop_nameHuman-readable destination stop nameNon-empty string
journey.line.slnidSwiss Line IDch:1:slnid:XXXXXX
journey.line.startDirection start of the lineNon-empty string
journey.line.endDirection end of the lineNon-empty string
journey.from_stop_sloidBoarding stop identifierch:1:sloid:XXXXXX or legacy numeric ID
journey.to_stop_sloidDestination stop identifierch:1:sloid:XXXXXX or legacy numeric ID
journey.planned_departurePlanned departure for timetable matchingISO 8601 with timezone

Required paper_tickets[] fields

FieldDescriptionFormat
categoryTicket categoryEINZELBILLETT, VERBUNDABO, INCOMING, or SONSTIGE
countCount for this category at this timestampInteger 0999
timestampWhen the count was recordedISO 8601 with timezone

3. Submit Example

curl -X POST "https://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io/api/provider/v1/controls" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"schema_version": "1.0",
"device_id": "AndId_c428366d643e9ce2",
"controls": [
{
"started_at": "2026-03-09T08:00:00Z",
"partial_close": "2026-03-09T08:10:00Z",
"ended_at": "2026-03-09T08:20:00Z",
"control_grade_pct": 75,
"executing_tu_code": "801",
"inspector": "UE67277",
"journey": {
"carrier_tu_code": "801",
"sjyid": "ch:1:sjyid:100058:12952-002",
"line": {
"slnid": "ch:1:slnid:103456",
"start": "Bernau (Schw), Poche",
"end": "Neuenburg (Baden), Bahnhof"
},
"from_stop_sloid": "ch:1:sloid:8578391",
"to_stop_sloid": "ch:1:sloid:8578391",
"from_stop_name": "Bernau (Schw), Poche",
"to_stop_name": "Neuenburg (Baden), Bahnhof",
"planned_departure": "2026-03-09T08:06:00Z"
},
"paper_tickets": [
{
"category": "EINZELBILLETT",
"count": 2,
"timestamp": "2026-03-09T08:09:00Z"
}
]
}
]
}'

Submit Response (202 Accepted)

{
"job_id": "job-20260309-082001-8f9ab1",
"status": "QUEUED",
"message": "Batch accepted for asynchronous processing",
"accepted_count": 1,
"created_at": "2026-03-09T08:20:01Z",
"status_url": "/api/provider/v1/jobs/job-20260309-082001-8f9ab1"
}

4. Check Job Status

curl -X GET "https://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io/api/provider/v1/jobs/$JOB_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"job_id": "job-20260309-082001-8f9ab1",
"status": "RUNNING",
"total_items": 100,
"processed_items": 45,
"succeeded_items": 38,
"duplicate_items": 2,
"failed_items": 5,
"created_at": "2026-03-09T08:20:01Z",
"started_at": "2026-03-09T08:20:03Z",
"completed_at": null
}

Possible job statuses: QUEUED · RUNNING · COMPLETED · COMPLETED_WITH_ERRORS · FAILED · EXPIRED


5. Check Per-Item Results

# All items
curl -X GET ".../api/provider/v1/jobs/$JOB_ID/items" \
-H "Authorization: Bearer $ACCESS_TOKEN"

# Only failed items
curl -X GET ".../api/provider/v1/jobs/$JOB_ID/items?status=VALIDATION_FAILED" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"job_id": "job-20260309-082001-8f9ab1",
"items": [
{
"item_id": "item-001",
"control_id": "insp-ue67277-20260309T080000Z",
"status": "PROCESSED",
"retryable": false,
"message": "Control processed successfully"
},
{
"item_id": "item-002",
"control_id": "insp-ue67277-20260309T081000Z",
"status": "VALIDATION_FAILED",
"retryable": false,
"message": "Validation failed for control_grade_pct",
"error_code": "INVALID_VALUE"
}
]
}

Possible item statuses: PROCESSED · DUPLICATE · VALIDATION_FAILED · TRANSIENT_ERROR · PERMANENT_ERROR


6. HTTP Status Reference

POST /api/provider/v1/controls

CodeMeaning
202 AcceptedBatch accepted and queued
400 Bad RequestMalformed payload
401 UnauthorizedToken missing, invalid, or expired
403 ForbiddenRole or allow-list restriction
422 Unprocessable EntitySemantic validation failed
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorUnexpected error

GET /jobs/{job_id} and GET /jobs/{job_id}/items

CodeMeaning
200 OKSuccess
401 UnauthorizedToken issue
403 ForbiddenRole restriction
404 Not FoundUnknown job_id