Bulk operations
Public API — Bulk Operations
Run one maintenance command — firmware update, OCPP config change, reset, availability change — across a fleet of charging stations, immediately or scheduled, with per-station progress tracking
Base path: /api/v1/bulk-operations. Permissions: bulk-operation:read for reads, bulk-operation:create for create, update, and cancel.
Model
A bulk operation fans out into one item per charging station. The operation aggregates its items' outcomes:
stateDiagram-v2
[*] --> PENDING: created (scheduled)
PENDING --> RUNNING: scheduledAt reached / immediate
PENDING --> CANCELLED: cancel
RUNNING --> CANCELLED: cancel
RUNNING --> COMPLETED: all items succeeded
RUNNING --> PARTIALLY_FAILED: some items failed
RUNNING --> FAILED: all items failed
The operation DTO carries live counters: totalCount, successCount, failureCount, partialFailureCount, pendingCount, plus timestamps (scheduledAt, startedAt, completedAt, cancelledAt) and blame fields (createdByApiKeyId / createdByUser, same for cancellation).
Item statuses: PENDING → IN_PROGRESS → (WAITING_FOR_COMPLETION) → SUCCEEDED / PARTIALLY_FAILED / FAILED, plus SKIPPED and CANCELLED. WAITING_FOR_COMPLETION means the command was delivered and the backbone now waits for the station's asynchronous confirmation (firmware installed, station rebooted).
Operation types
type selects the command; payload must match it:
type | payload |
|---|---|
FIRMWARE_UPDATE | { "firmwareId": "<uuid>", "retries"?: n, "retryInterval"?: seconds } |
OCPP_CONFIG_UPDATE | { "ocppVersion": "OCPP_1_6" | "OCPP_2_0_1", "configUpdates": [{ "key", "value", "component"? }] } |
CHARGING_STATION_RESET | { "resetType": "SOFT" | "HARD" } |
CHANGE_AVAILABILITY | { "availabilityType": "OPERATIVE" | "INOPERATIVE" } |
Notes:
FIRMWARE_UPDATE— the firmware must be registered (see/api/v1/firmware) and in statusPRODUCTION_READY,LIVE, orSTABLE; other statuses are rejected with403unless the key holdsfirmware:test-unapproved.OCPP_CONFIG_UPDATE—componentapplies to OCPP 2.0.1 variables only.postExecutionResetType(optional, top-level) reboots each station after the command: forFIRMWARE_UPDATEit must beHARD; forCHARGING_STATION_RESETit is not allowed (the operation is the reset).
Endpoints
| Operation | Endpoint | Permission |
|---|---|---|
| Create | POST /bulk-operations → 201 | bulk-operation:create |
| List | GET /bulk-operations | bulk-operation:read |
Get one (incl. chargingStationIds, firmware details) | GET /bulk-operations/{id} | bulk-operation:read |
| Update (full replace) | PUT /bulk-operations/{id} | bulk-operation:create |
| Cancel | POST /bulk-operations/{id}/cancel | bulk-operation:create |
| List items | GET /bulk-operations/{id}/items | bulk-operation:read |
| Get one item | GET /bulk-operations/{id}/items/{itemId} | bulk-operation:read |
List filters (query): chargingStationId, name, type, statuses (array), scheduledAtAfter / scheduledAtBefore; standard pagination envelope.
Creating an operation
curl -s -X POST -H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"type": "OCPP_CONFIG_UPDATE",
"name": "Fleet heartbeat interval 300s",
"scheduledAt": "2026-07-15T02:00:00Z",
"chargingStationIds": ["<station-uuid>", "<station-uuid>"],
"payload": {
"ocppVersion": "OCPP_1_6",
"configUpdates": [{ "key": "HeartbeatInterval", "value": "300" }]
}
}' \
"https://<host>/api/v1/bulk-operations"Scheduling rules:
scheduledAt: null→ runs immediately (the response hasimmediate: true).- Otherwise
scheduledAtmust be in the future. - Conflict window: a station cannot appear in two bulk operations scheduled within 10 minutes of each other — the create/update is rejected (
400, reason names the conflicting operation).
Tracking progress
Poll the operation for aggregate counts, drill into items for per-station outcomes:
curl -s -H "x-api-key: $API_KEY" \
"https://<host>/api/v1/bulk-operations/<id>/items?statuses[]=FAILED&page=1&limit=50"Each item carries:
statusand timestamps (publishedAt,startedAt,completedAt),chargingStation(identity of the target),executionDetails— type-specific progress (e.g. firmware status notifications, per-key config results),- on failure:
errorCode,errorMessage,errorDetails(e.g. station offline, OCPP rejection).
Terminal operation statuses: COMPLETED (all succeeded), PARTIALLY_FAILED (mixed), FAILED (all failed), CANCELLED.
Rescheduling and cancelling
Update (PUT) is a full replace of name, scheduledAt, chargingStationIds, and payload, and is only accepted while the operation:
- is still
PENDING, and - its
scheduledAthas not passed yet.
type is immutable — create a new operation instead. The new scheduledAt must be in the future and passes the same 10-minute conflict check.
Cancel works on PENDING or RUNNING operations (400 otherwise):
- items still
PENDINGorWAITING_FOR_COMPLETIONbecomeCANCELLEDand count as failures; - items already
IN_PROGRESSon a station are not interrupted.
Errors
| Status | Cause |
|---|---|
400 | scheduledAt in the past; station in another operation within the 10-minute window; update on a non-PENDING or already-started operation; type change on update; cancel on a finished operation; empty chargingStationIds |
403 | Missing permission, or firmware not in an allowed status |
404 | Unknown operation / item / station ID |
Domain errors use the { "reason": "...", "issues": [...] } shape (see General guide).
Related
- Remote Commands & Troubleshooting — the same commands for a single station, and how to diagnose stations that don't react
- Authentication — keys, roles, permissions
- General guide — pagination, filtering, error shapes
Updated 7 days ago