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.

FieldMeaning
idUUID of the operation.
typeASSIGN_TARIFF or CHANGE_POOLS_VISIBILITY.
statusIN_PROGRESS, SUCCEEDED, or FAILED.
updatedCountSet when the operation reaches SUCCEEDED. See Result counts.
failureReasonSet when the operation reaches FAILED. It holds the exception message.
updatedAtTime 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

typeEndpointsEffect
ASSIGN_TARIFFassign-tariff/byIds, assign-tariff/byQuerySets tariffId on every connector under the selected targets.
CHANGE_POOLS_VISIBILITYcharging-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/byIds accepts chargingPoolsIds, chargingPointsIds, and connectorsIds. 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/*/byIds accepts ids, 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/byQuery accepts chargingPoolsQuery, chargingPointsQuery, and connectorsQuery. All three are optional and can be combined.
    • charging-pools/*/query requires chargingPoolsQuery.
    • 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:

  1. Synchronously: it validates the request, checks that the tariff exists (ASSIGN_TARIFF only), and checks the concurrency rule. Then it creates the record with IN_PROGRESS and returns it with 200.
  2. Asynchronously: it runs the change on the application's async executor and then sets SUCCEEDED or FAILED.

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 type is IN_PROGRESS, the create call fails with 400 Bulk 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:

EndpointupdatedCount
assign-tariff/byIdsConnector rows updated, summed across the three ID sets.
assign-tariff/byQueryFor 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/*/byIdsThe number of IDs submitted. This includes unknown and deleted pools.
charging-pools/*/queryThe 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:

StatuscodeWhen
400BAD_REQUESTValidation failed, for example a missing tariffId, ids, or chargingPoolsQuery.
400BAD_REQUESTCannot assign tariff with id <id> - Tariff does not exist
400BAD_REQUESTBulk operation of type (<TYPE>) already in progress
400BAD_REQUESTIds set should not exceed limit of <n> (assign-tariff/byIds)
400BAD_REQUESTBulk 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


Did this page help you?