Bulk operations
Bulk operations apply one change, either a tariff assignment or a show/hide visibility change, to many locations in a single request. The change runs in the background, and you poll the operation for the result.
Model
A bulk operation is a single record. It has no per-target items, no counters for each target, and no list of failures.
| Field | Meaning |
|---|---|
id | UUID of the operation. |
type | ASSIGN_TARIFF or CHANGE_POOLS_VISIBILITY. |
status | IN_PROGRESS, SUCCEEDED, or FAILED. |
updatedCount | Set when the operation reaches SUCCEEDED. See Result counts. |
failureReason | Set when the operation reaches FAILED. It holds the exception message. |
updatedAt | Time of the last status change, in UTC. |
stateDiagram-v2
[*] --> IN_PROGRESS: POST accepted
IN_PROGRESS --> SUCCEEDED: change committed (updatedCount set)
IN_PROGRESS --> FAILED: exception, change rolled back (failureReason set)
SUCCEEDED and FAILED are terminal. You cannot cancel, retry, or update an operation.
Operation types
type | Endpoints | Effect |
|---|---|---|
ASSIGN_TARIFF | assign-tariff/byIds, assign-tariff/byQuery | Sets tariffId on every connector under the selected targets. |
CHANGE_POOLS_VISIBILITY | charging-pools/{hide|show}/{byIds|query} | hide stamps the pool's hide date. show clears it. Deleted pools are skipped. |
Targeting
Every operation selects its targets by IDs or by query:
- By IDs. You send explicit sets of IDs.
assign-tariff/byIdsacceptschargingPoolsIds,chargingPointsIds, andconnectorsIds. All three are optional and can be combined. A pool ID applies the tariff to every connector of every charging point in that pool. A charging-point ID applies it to every connector of that point. IDs that do not exist are ignored without an error.charging-pools/*/byIdsacceptsids, a list of 1 to 10,000 charging-pool IDs.
- By query. You send the same filter objects that the back-office list endpoints use. See Charging network.
assign-tariff/byQueryacceptschargingPoolsQuery,chargingPointsQuery, andconnectorsQuery. All three are optional and can be combined.charging-pools/*/queryrequireschargingPoolsQuery.- The query is resolved on the server in pages of 10,000 rows. If you leave a query section out, that level is skipped. If you send a query section with no filter fields, such as
{}, it matches every non-deleted entity at that level.
For ASSIGN_TARIFF, a tariffId that does not exist is rejected with 400 before the operation is created.
Sync vs. async
Every POST call works in two steps:
- Synchronously: it validates the request, checks that the tariff exists (
ASSIGN_TARIFFonly), and checks the concurrency rule. Then it creates the record withIN_PROGRESSand returns it with200. - Asynchronously: it runs the change on the application's async executor and then sets
SUCCEEDEDorFAILED.
Poll GET /v2/backoffice/bulk/{id} until status is no longer IN_PROGRESS. No webhook or event announces that an operation is complete.
sequenceDiagram
participant C as Client
participant API as EMSP API
participant W as Async executor
C->>API: POST /v2/backoffice/bulk/assign-tariff/byQuery
API->>API: validate, check tariff, check no IN_PROGRESS ASSIGN_TARIFF
API-->>C: 200 { id, status: IN_PROGRESS }
API->>W: execute in one DB transaction
W->>API: SUCCEEDED (updatedCount) | FAILED (failureReason)
loop until status != IN_PROGRESS
C->>API: GET /v2/backoffice/bulk/{id}
API-->>C: BulkOperationDto
end
Concurrency, atomicity, partial failure
- One operation for each type at a time. If an operation of the same
typeisIN_PROGRESS, the create call fails with400Bulk operation of type (ASSIGN_TARIFF) already in progress. Operations of different types can run in parallel. - All or nothing. Each run is one database transaction. If any step throws an exception, the transaction is rolled back and the operation is set to
FAILED. The API has no partially-applied state and no per-target failure list.
Result counts
updatedCount means something different for each endpoint:
| Endpoint | updatedCount |
|---|---|
assign-tariff/byIds | Connector rows updated, summed across the three ID sets. |
assign-tariff/byQuery | For each query section, the number of matches in the last 10,000-row page. The sections are then summed. If a section matches more than 10,000 rows, the count is lower than the real number. |
charging-pools/*/byIds | The number of IDs submitted. This includes unknown and deleted pools. |
charging-pools/*/query | The number of matches in the last 10,000-row page. |
Treat updatedCount as a hint. Do not use it to reconcile data.
Examples
Assign a tariff to every connector in two pools:
curl -X POST "https://<host>/v2/backoffice/bulk/assign-tariff/byIds" \
-H "Authorization: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tariffId": "8f1c2d3e-0000-4000-8000-000000000001",
"chargingPoolsIds": ["pool-0001", "pool-0002"]
}'{
"id": "3b9e6c1a-0000-4000-8000-0000000000aa",
"type": "ASSIGN_TARIFF",
"status": "IN_PROGRESS",
"updatedCount": null,
"failureReason": null,
"updatedAt": "2026-10-27T10:15:00Z"
}Assign a tariff to every DC connector of one CPO, selected by query:
curl -X POST "https://<host>/v2/backoffice/bulk/assign-tariff/byQuery" \
-H "Authorization: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tariffId": "8f1c2d3e-0000-4000-8000-000000000001",
"connectorsQuery": { "cposIds": ["cpo-0001"], "powerTypes": ["DC"] }
}'Hide every charging pool in a country:
curl -X POST "https://<host>/v2/backoffice/bulk/charging-pools/hide/query" \
-H "Authorization: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "chargingPoolsQuery": { "countries": ["PL"] } }'Poll the result:
curl "https://<host>/v2/backoffice/bulk/3b9e6c1a-0000-4000-8000-0000000000aa" \
-H "Authorization: $API_KEY"{
"id": "3b9e6c1a-0000-4000-8000-0000000000aa",
"type": "ASSIGN_TARIFF",
"status": "SUCCEEDED",
"updatedCount": 48,
"failureReason": null,
"updatedAt": "2026-10-27T10:15:04Z"
}Errors
These endpoints use the standard error body. Responses that apply only to bulk operations:
| Status | code | When |
|---|---|---|
400 | BAD_REQUEST | Validation failed, for example a missing tariffId, ids, or chargingPoolsQuery. |
400 | BAD_REQUEST | Cannot assign tariff with id <id> - Tariff does not exist |
400 | BAD_REQUEST | Bulk operation of type (<TYPE>) already in progress |
400 | BAD_REQUEST | Ids set should not exceed limit of <n> (assign-tariff/byIds) |
400 | BAD_REQUEST | Bulk operation (<id>) not found from GET /{id}. The response is 400, not 404. |
Errors that happen during the background run do not change the HTTP response. They appear only as status: FAILED with a failureReason.
Related
Updated 14 days ago