GuidesAPI reference
Introduction
  • Overview
Orientation
  • What a CSMS does
  • OCPP in ten minutes
  • The vocabulary
  • Lifecycle of a charging session
  • Which OCPP version am I dealing with?
  • How Flowion maps onto OCPP
Getting started
  • Quickstart
  • Core concepts
  • Set up your workspace
  • Connect a simulated charger
  • Connect a real charger
  • Environments
API guide
  • Authentication
  • Your first API call
  • Interactive API reference
  • Roles and permissions
  • Errors, pagination and versioning
  • Live updates
  • Sending commands to a charger
  • Rate limits
How-to guides
  • Register and provision a charger
  • Read and change charger settings
  • Authorize drivers with RFID cards
  • Start and stop sessions remotely
  • Track sessions and energy
  • Take a charger in and out of service
  • Unlock a stuck connector
  • Reset a charger
  • Send vendor-specific data
  • Keep a site within its electrical limits
  • Use day-ahead electricity prices
  • Receive events with webhooks
  • OCPP feature coverage
Troubleshooting
  • 8 more coming soon
    • My charger will not connectSoon
    • It connects, then dropsSoon
    • A command timed out or returned 409Soon
    • A card is not being authorizedSoon
    • My transaction looks wrongSoon
    • Meter values are missing or sparseSoon
    • Reading the OCPP message logSoon
    • Known limitationsSoon
Reference
  • 5 more coming soon
    • GlossarySoon
    • Endpoint indexSoon
    • OCPP message supportSoon
    • Status and error codesSoon
    • ChangelogSoon
Flowion Docsflowion.io
PreviousLive updatesNextRate limits
Was this page helpful?
Edit this page

Sending commands to a charger

Every action you ask a charging station to take — start a session, unlock a connector, reset itself — goes through one resource: POST /v1/charging-stations/{id}/commands. This page is the shape that endpoint shares across every command; the how-to guides cover what each individual command means and when to reach for it.

This replaced a blocking design

An earlier version of this endpoint held the HTTP request open until the station answered over its OCPP socket, and returned 409 if the station was offline. That is no longer how it works, and nothing below describes it — read on for the current, asynchronous shape.

The request creates a record, not an answer#

Shell
curl -X POST "$API/charging-stations/$CHARGER/commands" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"type": "unlock_connector", "connector_id": 1}'

The response is a 201 (or a 200 — see below) with a Command resource, created immediately, before the station has said anything:

JSON
{
  "id": "1f2e...",
  "command_type": "unlock_connector",
  "charging_station_id": "9a7c...",
  "evse_id": null,
  "status": "queued",
  "created_at": "2026-08-31T10:00:00Z",
  "sent_at": null,
  "resolved_at": null,
  "expires_at": "2026-08-31T10:05:00Z",
  "request": { "connector_id": 1 },
  "result": null,
  "failure_reason": null,
  "superseded_by": null
}

status starts at queued or sent and is never the station's answer — poll or watch for it to reach a terminal value:

Shell
curl "$API/charging-stations/$CHARGER/commands/$COMMAND_ID" \
  -H "Authorization: Bearer $TOKEN"

status is one of queued, sent, accepted, rejected, failed, superseded, or expired. accepted/rejected is the station's own answer, in result; failed and expired mean the platform gave up without one. Rather than polling, subscribe to live updates and watch for a command_status_changed event carrying this command's id — it fires on every status transition.

accepted means the station agreed to try, not that the action happened. A remote_start_transaction the station accepts still has to find a vehicle plugged in and close its contactor; if nothing is plugged in, most stations accept and then simply wait. Watch for the resulting resource to actually change — a new transaction, a connector's status — rather than trusting accepted alone.

It queues even when the station is offline#

A station does not need a live connection at the moment you call this endpoint. The command is written down and delivered the next time the station is reachable; queued is exactly that state. expires_at bounds how long it waits — a command still queued past its expiry moves to expired on its own rather than being delivered indefinitely late.

Idempotency-Key is required, not optional#

Every POST to this endpoint needs one:

Idempotency-Key: 7d6a2f10-...

Retry the same request with the same key and the same body, and you get back the original command — 200, not 201 — instead of a second one being queued. Retry with the same key but a different body and you get 409: the key is already bound to a different request. A missing key is 400. This is what makes it safe to retry a start-transaction call after a timeout without risking two sessions.

Command types#

The type field selects which command, and each type has its own body fields — see each guide for the full shape:

typeGuide
remote_start_transactionStart and stop sessions remotely
remote_stop_transactionStart and stop sessions remotely
unlock_connectorUnlock a stuck connector
change_availabilityTake a charger in and out of service
resetReset a charger
reserve_nowHolds a connector for one id-token (RFID card) until it either starts a session or the reservation expires.
cancel_reservationCancels a reservation created with reserve_now.
update_firmware1.6 stations only — points the station at a firmware image to fetch.
get_diagnostics1.6 stations only — asks the station to upload a diagnostics bundle.

Listing and filtering#

GET /v1/charging-stations/{id}/commands returns the same paginated envelope as every other list endpoint — data, page, per_page, total — and accepts status and evse_id to narrow it, newest first. Useful for a station you suspect has a stuck or repeatedly failing command queued against it.

Next: Rate limits.