---
updatedAt: 2026-10-27T09:32:01.000Z
agentTools:
  projectIndex: https://developers.solidstudio.io/llms.txt
---

# 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:

```mermaid
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 status `PRODUCTION_READY`, `LIVE`, or `STABLE`; other statuses are rejected with `403` unless the key holds `firmware:test-unapproved`.
* `OCPP_CONFIG_UPDATE` — `component` applies to OCPP 2.0.1 variables only.
* `postExecutionResetType` (optional, top-level) reboots each station after the command: for `FIRMWARE_UPDATE` it must be `HARD`; for `CHARGING_STATION_RESET` it 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

```bash
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 has `immediate: true`).
* Otherwise `scheduledAt` must 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:

```bash
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:

* `status` and 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 `scheduledAt` has 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 `PENDING` or `WAITING_FOR_COMPLETION` become `CANCELLED` and count as failures;
* items already `IN_PROGRESS` on 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](https://developers.solidstudio.io/docs/getting-started)).

## Related

* [Remote Commands & Troubleshooting](https://developers.solidstudio.io/docs/remote-commands) — the same commands for a single station, and how to diagnose stations that don't react
* [Authentication](https://developers.solidstudio.io/docs/authorization) — keys, roles, permissions
* [General guide](https://developers.solidstudio.io/docs/getting-started) — pagination, filtering, error shapes

<br />