Skip to main content

Provider API Usage Quickstart

This guide is for provider teams who only need to call the API. It focuses on the minimum required request to submit controls in batch and track asynchronous processing jobs.

1. Authorization First

Get your bearer token first using one of these guides:

Use the token in every API request:

  • Authorization: Bearer <access_token>

2. Endpoints to Use

Main submission endpoint:

  • POST /api/provider/v1/controls

Job tracking endpoints:

  • GET /api/provider/v1/jobs/{job_id}
  • GET /api/provider/v1/jobs/{job_id}/items

Base URL examples:

3. Request Model (Batch)

POST /api/provider/v1/controls now expects a batch object:

  • schema_version (required, must be "1.0")
  • device_id (required, max length 100)
  • controls (non-empty array, max 500)

Each item in controls must be a full control payload.

3.1 Mandatory Fields and Meaning

Required fields at request root:

FieldDescriptionFormat / Rules
schema_versionContract version expected by the API.Must be exactly "1.0".
device_idUnique identifier of the provider device/app instance that sent the batch.String, max length 100.
controlsBatch of control payloads to ingest.Non-empty array, max 500.

Required fields per controls[] item:

FieldDescriptionFormat / Rules
started_atTimestamp when the control run started.ISO 8601 date-time with timezone (Z or offset).
partial_closeInterim close timestamp of the control run.ISO 8601 date-time with timezone; must be later than started_at.
ended_atEnd-of-inspection timestamp.Optional ISO 8601 date-time with timezone; if provided, must be later than or equal to partial_close.
control_grade_pctInspection coverage level used in reporting.Integer: 25, 50, 75, or 100.
executing_tu_codeOrganization code of the company executing the control.Non-empty numeric string (for example 801).
inspectorIdentifier of the inspector who executed the control.Non-empty string.
journeyJourney context block used for route/timetable matching.Object; required subfields listed below.
paper_ticketsAggregated paper-ticket counts captured during the control.Non-empty array; each item must contain category, count, timestamp.

Required journey fields:

FieldDescriptionFormat / Rules
journey.carrier_tu_codeCarrier/company responsible for transport execution.Non-empty numeric string (for example 801).
journey.from_stop_nameHuman-readable start/boarding stop name.Non-empty string.
journey.to_stop_nameHuman-readable destination/end stop name.Non-empty string.
journey.lineNested line object.Required object with fields below.
journey.line.slnidSwiss Line ID.Canonical format: ch:1:slnid:XXXXXX.
journey.line.startDirection where the line starts.Non-empty string.
journey.line.endDirection where the line ends.Non-empty string.
journey.from_stop_sloidBoarding stop identifier.Canonical ch:1:sloid:XXXXXX or legacy numeric stop ID XXXXXX.
journey.to_stop_sloidDestination stop identifier.Canonical ch:1:sloid:XXXXXX or legacy numeric stop ID XXXXXX.
journey.planned_departurePlanned departure timestamp used for context/matching.ISO 8601 date-time with timezone.

Required paper_tickets[] fields:

FieldDescriptionFormat / Rules
paper_tickets[].categoryPaper ticket category for the count entry.One of EINZELBILLETT, VERBUNDABO, INCOMING, SONSTIGE.
paper_tickets[].countCount of tickets for the category at that timestamp.Integer from 0 to 999.
paper_tickets[].timestampTimestamp when the count was recorded.ISO 8601 date-time with timezone.

4. Minimal 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"
}
]
}
]
}'

5. Submit Response (Async Job Created)

Successful submit now returns 202 Accepted with a job id.

Example:

{
"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"
}

6. Check Job Status

Use job_id from submit response.

curl -X GET "https://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io/api/provider/v1/jobs/$JOB_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Example response:

{
"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 status values:

  • QUEUED
  • RUNNING
  • COMPLETED
  • COMPLETED_WITH_ERRORS
  • FAILED
  • EXPIRED

7. Check Per-Item Results

Get all item results:

curl -X GET "https://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io/api/provider/v1/jobs/$JOB_ID/items" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Get only failed items:

curl -X GET "https://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io/api/provider/v1/jobs/$JOB_ID/items?status=VALIDATION_FAILED" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Example response:

{
"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 status values:

  • PROCESSED
  • DUPLICATE
  • VALIDATION_FAILED
  • TRANSIENT_ERROR
  • PERMANENT_ERROR

8. Expected HTTP Responses

POST /controls:

  • 202 Accepted: batch accepted and queued
  • 400 Bad Request: malformed payload
  • 401 Unauthorized: token missing/invalid/expired
  • 403 Forbidden: role or access restrictions not satisfied
  • 422 Unprocessable Entity: semantic validation failed
  • 429 Too Many Requests: rate limit exceeded
  • 500 Internal Server Error

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

  • 200 OK
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found: unknown job_id

9. OpenAPI Reference