> ## Documentation Index
> Fetch the complete documentation index at: https://docs.amps.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Charge an Exact Energy Amount

> Deliver a fixed number of kilowatt-hours with the charge command's energy parameter, and check the charger declares it before you push.

## Overview

Some sessions have an amount, not an end time. A guest gets 20 kWh. A prepaid top-up buys 8 kWh. A fleet allowance releases 30 kWh per driver per night. Push `charge` with an `energy` parameter and the session stops at the amount.

**The charger enforces the stop, not the platform.** Amps sends the amount once, in the command. The charger's own meter counts the kilowatt-hours and ends the session at the target. Amps does not watch the meter and does not send a second command to stop. That is why this is a per-device capability: a charger that cannot meter its own stop cannot accept the parameter, so read the device before you push.

<Callout icon="clock" color="#ED6D2C">
  **Coming soon.** Live control for EV chargers. The sandbox environment serves the full `commands` and `settings` surface, so the walkthrough below works against a sandbox device today. A live EV charger push returns 503 `NOT_YET_AVAILABLE` until the live path opens.
</Callout>

## Step 1: Check that the charger declares energy

Read the device, then read `data.commands.charge.parameters`. The `energy` entry carries the unit and the range the charger accepts.

```bash theme={null}
curl -X GET https://api.amps.ai/ev-charger/device_ev_001 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx"
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "device_ev_001",
    "vendor": "example_vendor_a",
    "sync": { "available": true, "lastPulledAt": "2026-08-12T09:14:00.000Z" },
    "metadata": { "model": "7kW AC Charger", "source": "projection" },
    "state": {
      "status": "available",
      "isConnected": true,
      "isCharging": false,
      "currentPower": 0,
      "maxCurrent": 32,
      "powerRateLimit": 7.4,
      "sessionEnergy": 0,
      "phases": 1,
      "voltage": 230,
      "activeControlMode": "idle"
    },
    "conflictStrategies": ["cancel_and_replace", "queue_after"],
    "sessions": true,
    "commands": {
      "charge": {
        "parameters": {
          "power":   { "unit": "kw", "min": 1.4, "max": 22, "step": 0.1 },
          "current": { "unit": "amps", "min": 6, "max": 32, "step": 1 },
          "energy":  { "unit": "kwh", "min": 1, "max": 100 }
        },
        "execution": ["immediate", "scheduled", "windowed"]
      },
      "idle": { "parameters": {}, "execution": ["immediate", "scheduled"] },
      "auto.charge_tariff": { "parameters": {}, "execution": ["immediate", "scheduled"] }
    },
    "settings": {
      "max_charge_rate":    { "value": 7.4, "unit": "kw", "min": 0, "max": 50, "step": 0.1 },
      "max_charge_current": { "value": 32, "unit": "amps", "min": 6, "max": 32, "step": 1 },
      "cable_lock":         { "value": true }
    },
    "vehicle": { "id": "device_veh_001", "links": { "self": "/vehicle/device_veh_001" } },
    "lastAction": null,
    "currentSchedule": null
  },
  "meta": {
    "requestId": "req_2bC3dEfG",
    "environment": "sandbox",
    "timestamp": "2026-08-12T09:14:00.000Z",
    "latencyMs": 24
  }
}
```

This charger meters between 1 and 100 kWh. It declares no `step` on `energy`, so any value inside the range is accepted. Other chargers do declare a `step`, and a value between the increments is refused, so read the key before you round.

A charger that omits `energy` cannot meter the stop. Two alternatives exist. A charger that reads the car's battery level declares `target` instead, in percent, and stops at a state of charge. A charger that declares neither leaves the stop to you. Push `charge`, then poll `state.sessionEnergy`. Push `idle` when the amount arrives.

## Step 2: Push the charge with an amount

Send the amount as the canonical `{ value, unit }` pair.

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -X POST https://api.amps.ai/ev-charger/device_ev_001 \
      -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{
        "action": {
          "command": "charge",
          "parameters": { "energy": { "value": 20, "unit": "kwh" } }
        }
      }'
    ```
  </Tab>

  <Tab title="Node">
    ```javascript theme={null}
    await fetch("https://api.amps.ai/ev-charger/device_ev_001", {
      method: "POST",
      headers: {
        "x-api-key": process.env.AMPS_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        action: {
          command: "charge",
          parameters: { energy: { value: 20, unit: "kwh" } },
        },
      }),
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os, requests

    requests.post(
        "https://api.amps.ai/ev-charger/device_ev_001",
        headers={
            "x-api-key": os.environ["AMPS_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "action": {
                "command": "charge",
                "parameters": {"energy": {"value": 20, "unit": "kwh"}},
            }
        },
    )
    ```
  </Tab>
</Tabs>

The push is immediate, so the action starts in `acknowledged` rather than `scheduled`.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_ev_2026081209150",
    "deviceId": "device_ev_001",
    "deviceType": "ev_charger",
    "command": "charge",
    "parameters": { "energy": { "value": 20, "unit": "kwh" } },
    "state": "acknowledged",
    "createdAt": "2026-08-12T09:15:22.000Z",
    "start": null,
    "links": { "self": "/actions/act_ev_2026081209150" }
  },
  "meta": {
    "requestId": "req_3cD4eFgH",
    "environment": "sandbox",
    "timestamp": "2026-08-12T09:15:22.000Z",
    "latencyMs": 81
  }
}
```

**Combine the amount with a rate cap.** An amount says how much, and `power` or `current` says how fast. A guest session that gives 20 kWh at no more than 7.4 kW carries both in one command.

```json theme={null}
{
  "action": {
    "command": "charge",
    "parameters": {
      "energy": { "value": 20, "unit": "kwh" },
      "power": { "value": 7.4, "unit": "kw" }
    }
  }
}
```

Send `power` or `current`, never both. They cap the same rate in different units, so the pair returns 422 `UNSUPPORTED_PARAMETER_COMBINATION`. To defer the session, add a `start` as a plant-local wall-clock time. See [smart charging an EV](/guides/cookbook/ev-smart-charging) for windows and strategies.

## Step 3: Read the two refusals

Both refusals a first-time caller meets return 422, and both carry enough in `details` to fix the request without a second device read.

**An amount outside the declared range** returns `PARAMETER_OUT_OF_RANGE`. The `details` object carries the range.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "PARAMETER_OUT_OF_RANGE",
    "message": "One or more parameters are outside the supported range.",
    "details": {
      "parameter": "energy",
      "value": 150,
      "min": 1,
      "max": 100,
      "unit": "kwh",
      "description": "Parameter `energy` value 150 exceeds maximum 100."
    }
  },
  "meta": {
    "requestId": "req_4dE5fGhI",
    "timestamp": "2026-08-12T09:16:04.000Z",
    "path": "/ev-charger/device_ev_001",
    "latencyMs": 7
  }
}
```

**An amount on a charger that cannot meter it** returns `UNSUPPORTED_PARAMETER`. The refusal names the offending key and lists what the command does accept on this device.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "UNSUPPORTED_PARAMETER",
    "message": "One or more parameters are not supported by this device.",
    "details": {
      "unsupportedParameters": ["energy"],
      "deviceCapabilities": {
        "supportedParameters": ["power", "current"]
      },
      "description": "Parameter(s) not supported for mode `charge`: energy."
    }
  },
  "meta": {
    "requestId": "req_5eF6gHiJ",
    "timestamp": "2026-08-12T09:16:41.000Z",
    "path": "/ev-charger/device_ev_001",
    "latencyMs": 8
  }
}
```

Clamp the amount to `min` and `max` before you push. Branch on `supportedParameters` when a charger refuses the key. The platform refuses both requests before it dispatches anything, so nothing changed at the site.

## Step 4: Track the delivery

Two surfaces answer two different questions.

**The action record** says whether the charger accepted the instruction. Read it at `GET /actions/{actionId}`.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_ev_2026081209150",
    "deviceId": "device_ev_001",
    "deviceType": "ev_charger",
    "command": "charge",
    "parameters": { "energy": { "value": 20, "unit": "kwh" } },
    "state": "completed",
    "result": { "success": true, "message": "EV charger started charging" },
    "errorCode": null,
    "errorMessage": null,
    "createdAt": "2026-08-12T09:15:22.000Z",
    "updatedAt": "2026-08-12T09:15:24.000Z",
    "acknowledgedAt": "2026-08-12T09:15:22.500Z",
    "completedAt": "2026-08-12T09:15:24.000Z",
    "start": null,
    "end": null,
    "endedAt": null,
    "links": { "self": "/actions/act_ev_2026081209150" }
  },
  "meta": {
    "requestId": "req_6fG7hIjK",
    "environment": "sandbox",
    "timestamp": "2026-08-12T09:15:25.000Z",
    "latencyMs": 14
  }
}
```

Read `completed` carefully. It means the charger accepted the command and started the session. It does not mean 20 kWh arrived. The delivery takes hours, and the action record closes in seconds.

**The device read** says how much energy has arrived. `state.sessionEnergy` counts the kilowatt-hours of the current session, so poll the device to drive a progress bar. When `state.isCharging` turns false and `sessionEnergy` sits at the amount, the charger met the target. For a completed session and its measurement provenance, read the history instead: see [read charging sessions](/guides/cookbook/ev-charging-sessions).

## Why this works

A canonical parameter earns its place when several manufacturers offer the same lever. Each device then declares whether it has that lever. `energy` is declared per charger because the meter that enforces the stop sits in the charger, not in the platform. Amps refuses the parameter on a charger that cannot honour it. The alternative is a platform timer, and a timer delivers an amount the caller never requested. See [canonical actions](/concepts/canonical-actions) and [capabilities](/concepts/capabilities).

## What next

<CardGroup cols={2}>
  <Card title="Read charging sessions" icon="receipt" href="/guides/cookbook/ev-charging-sessions">
    What each session delivered, and how it was measured.
  </Card>

  <Card title="Cap charging for a grid event" icon="shield-alert" href="/guides/cookbook/ev-grid-event-cap">
    Hold a charger under an operator's limit, then restore it.
  </Card>

  <Card title="Smart charging modes" icon="bolt" href="/guides/cookbook/ev-smart-charging">
    Windows you own, and strategies the charger owns.
  </Card>

  <Card title="EV charger cheat sheet" icon="list-checks" href="/reference/ev-charger-cheat-sheet">
    Every command, parameter, and state field in one page.
  </Card>
</CardGroup>

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Charge an Exact Energy Amount",
"description": "Deliver a fixed number of kilowatt-hours with the charge command's energy parameter, and check the charger declares it before you push.",
"step": [
{ "@type": "HowToStep", "name": "Step 1: Check that the charger declares energy", "position": 1 },
{ "@type": "HowToStep", "name": "Step 2: Push the charge with an amount", "position": 2 },
{ "@type": "HowToStep", "name": "Step 3: Read the two refusals", "position": 3 },
{ "@type": "HowToStep", "name": "Step 4: Track the delivery", "position": 4 }
]
})}}
/>
