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:
- Local: http://localhost:3002
- INT: https://kontrolleplus-dev-data.orangeglacier-1536cc06.switzerlandnorth.azurecontainerapps.io
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:
| Field | Description | Format / Rules |
|---|---|---|
| schema_version | Contract version expected by the API. | Must be exactly "1.0". |
| device_id | Unique identifier of the provider device/app instance that sent the batch. | String, max length 100. |
| controls | Batch of control payloads to ingest. | Non-empty array, max 500. |
Required fields per controls[] item:
| Field | Description | Format / Rules |
|---|---|---|
| started_at | Timestamp when the control run started. | ISO 8601 date-time with timezone (Z or offset). |
| partial_close | Interim close timestamp of the control run. | ISO 8601 date-time with timezone; must be later than started_at. |
| ended_at | End-of-inspection timestamp. | Optional ISO 8601 date-time with timezone; if provided, must be later than or equal to partial_close. |
| control_grade_pct | Inspection coverage level used in reporting. | Integer: 25, 50, 75, or 100. |
| executing_tu_code | Organization code of the company executing the control. | Non-empty numeric string (for example 801). |
| inspector | Identifier of the inspector who executed the control. | Non-empty string. |
| journey | Journey context block used for route/timetable matching. | Object; required subfields listed below. |
| paper_tickets | Aggregated paper-ticket counts captured during the control. | Non-empty array; each item must contain category, count, timestamp. |
Required journey fields:
| Field | Description | Format / Rules |
|---|---|---|
| journey.carrier_tu_code | Carrier/company responsible for transport execution. | Non-empty numeric string (for example 801). |
| journey.from_stop_name | Human-readable start/boarding stop name. | Non-empty string. |
| journey.to_stop_name | Human-readable destination/end stop name. | Non-empty string. |
| journey.line | Nested line object. | Required object with fields below. |
| journey.line.slnid | Swiss Line ID. | Canonical format: ch:1:slnid:XXXXXX. |
| journey.line.start | Direction where the line starts. | Non-empty string. |
| journey.line.end | Direction where the line ends. | Non-empty string. |
| journey.from_stop_sloid | Boarding stop identifier. | Canonical ch:1:sloid:XXXXXX or legacy numeric stop ID XXXXXX. |
| journey.to_stop_sloid | Destination stop identifier. | Canonical ch:1:sloid:XXXXXX or legacy numeric stop ID XXXXXX. |
| journey.planned_departure | Planned departure timestamp used for context/matching. | ISO 8601 date-time with timezone. |
Required paper_tickets[] fields:
| Field | Description | Format / Rules |
|---|---|---|
| paper_tickets[].category | Paper ticket category for the count entry. | One of EINZELBILLETT, VERBUNDABO, INCOMING, SONSTIGE. |
| paper_tickets[].count | Count of tickets for the category at that timestamp. | Integer from 0 to 999. |
| paper_tickets[].timestamp | Timestamp 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