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

# Schedule a Charge for Later

> Schedule a battery charge for later: defer it with a bare start, or bound a cheap-rate window with start and end. Capability check, curl, action lifecycle, cancellation.

## Overview

Schedule a `charge` action two ways. Send `start` on its own to **defer** the charge: it fires once, at that instant, and holds until your next push. Add an `end` and it becomes a **window**: the platform fires at `start` and reverts the battery at `end`, so you can ride a cheap-rate tariff overnight without touching the API again.

A cheap-rate window is the most common reason to schedule a charge: an Octopus Agile dip, a Cosy off-peak slot, a tariff window the customer's app already knows about. The platform fires the command, reverts the device at the end of any window, and webhook payloads land at each transition so the customer's app reflects what actually happened.

Which shapes a battery accepts is declared in `commands.charge.execution`: `scheduled` for a bare `start`, `windowed` for a `start`/`end` pair. They vary by device, so check before you push.

## Step 1: Check capability before pushing

Read the device and inspect the `commands.charge.execution` array.

```bash theme={null}
curl -X GET https://api.amps.ai/battery/device_abc123 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json"
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "device_abc123",
    "vendor": "foxess",
    "sync": { "available": true, "lastPulledAt": "2026-05-07T19:42:11.000Z" },
    "metadata": { "model": "FoxESS H1-5.0-E", "source": "projection" },
    "state": {
      "status": "idle",
      "capacity": 10.4,
      "level": 67,
      "chargeRate": 0,
      "dischargeLimit": 10
    },
    "conflictStrategies": ["cancel_and_replace", "queue_after"],
    "commands": {
      "charge": {
        "parameters": {
          "target": { "unit": "percent", "min": 10, "max": 100 },
          "power": { "unit": "kw", "min": 0, "max": 5 }
        },
        "execution": ["windowed"], "readiness": "verified"
      }
    },
    "settings": { "charge_ceiling": { "value": 100, "unit": "percent" } },
    "lastAction": null,
    "currentSchedule": null
  },
  "meta": { "requestId": "req_1aB2cD3e", "environment": "sandbox", "timestamp": "2026-05-07T19:42:11.000Z", "latencyMs": 31 }
}
```

Support varies by battery. This FoxESS unit lists `windowed`, so it takes a `start`/`end` window. A unit that lists `scheduled` (GivEnergy's charge default, for instance) accepts a deferred `start` on its own. Match your push to what the array shows.

## Step 2: Schedule the charge

Adding a `start` turns an immediate charge into a scheduled one. Two shapes, gated by the device's `execution` array:

* **Deferred** — `start` only. Fires once at `start` and holds until your next push. Needs `scheduled`.
* **Windowed** — `start` and `end`. Fires at `start`, reverts the battery at `end`. Needs `windowed`.

Timestamps are plant-local wall-clock ISO 8601 (`YYYY-MM-DDTHH:MM:SS`, no offset, no `Z`) — the platform interprets them in the device's plant timezone. Parameters take the canonical `{ value, unit }` shape.

### Defer to a future time

Send `start` without an `end`. The GivEnergy unit below lists `scheduled`, so it accepts a bare start; the charge fires at 22:00 and holds until you push something else.

```bash theme={null}
curl -X POST https://api.amps.ai/battery/dev_ge789 \
  -H "x-api-key: sk_live_abc123xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "action": {
      "command": "charge",
      "start": "2026-05-08T22:00:00",
      "parameters": {
        "target": { "value": 90, "unit": "percent" }
      }
    }
  }'
```

### Charge through a window

Send `start` and `end` together to bound the charge. The FoxESS unit from Step 1 lists `windowed`.

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -X POST https://api.amps.ai/battery/device_abc123 \
      -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{
        "action": {
          "command": "charge",
          "start": "2026-05-08T22:00:00",
          "end": "2026-05-09T05:00:00",
          "parameters": {
            "target": { "value": 90, "unit": "percent" },
            "power": { "value": 3, "unit": "kw" }
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Node">
    ```javascript theme={null}
    const res = await fetch("https://api.amps.ai/battery/device_abc123", {
      method: "POST",
      headers: {
        "x-api-key": process.env.AMPS_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        action: {
          command: "charge",
          start: "2026-05-08T22:00:00",
          end: "2026-05-09T05:00:00",
          parameters: {
            target: { value: 90, unit: "percent" },
            power: { value: 3, unit: "kw" },
          },
        },
      }),
    });

    const body = await res.json();
    const { id, deviceId, state } = body.data;
    ```
  </Tab>

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

    res = requests.post(
        "https://api.amps.ai/battery/device_abc123",
        headers={
            "x-api-key": os.environ["AMPS_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "action": {
                "command": "charge",
                "start": "2026-05-08T22:00:00",
                "end": "2026-05-09T05:00:00",
                "parameters": {
                    "target": {"value": 90, "unit": "percent"},
                    "power": {"value": 3, "unit": "kw"},
                },
            }
        },
    )
    body = res.json()
    action_id = body["data"]["id"]
    device_id = body["data"]["deviceId"]
    ```
  </Tab>
</Tabs>

Either shape returns `202 Accepted`. The action stays in `scheduled` state until `start`.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_pending_001",
    "deviceId": "device_abc123",
    "deviceType": "battery",
    "command": "charge",
    "parameters": {
      "target": { "value": 90, "unit": "percent" },
      "power": { "value": 3, "unit": "kw" }
    },
    "state": "scheduled",
    "createdAt": "2026-05-07T20:11:42.000Z",
    "start": "2026-05-08T22:00:00.000Z",
    "end": "2026-05-09T05:00:00.000Z",
    "links": { "self": "/actions/act_pending_001" }
  },
  "meta": { "requestId": "req_4fG5hI6j", "environment": "sandbox", "timestamp": "2026-05-07T20:11:42.000Z", "latencyMs": 88 }
}
```

The response mirrors the command you sent: the canonical `command`, the `parameters` you constrained it with, and the `deviceType`. It also returns `links.self` (and a `Location` header) pointing at the action record, so you always have a handle to track the work.

## Step 3: Track the action

Actions move through `scheduled` to `acknowledged` to `completed`. Poll `links.self`, or subscribe to webhooks for `push.completed` and `push.failed`.

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

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_pending_001",
    "deviceId": "device_abc123",
    "deviceType": "battery",
    "command": "charge",
    "parameters": {
      "target": { "value": 90, "unit": "percent" },
      "power": { "value": 3, "unit": "kw" }
    },
    "state": "scheduled",
    "result": null,
    "errorCode": null,
    "errorMessage": null,
    "createdAt": "2026-05-07T20:11:42.000Z",
    "updatedAt": "2026-05-07T20:11:42.000Z",
    "acknowledgedAt": null,
    "completedAt": null,
    "start": "2026-05-08T22:00:00.000Z",
    "end": "2026-05-09T05:00:00.000Z",
    "links": { "self": "/actions/act_pending_001" }
  },
  "meta": { "requestId": "req_7kL8mN9o", "environment": "sandbox", "timestamp": "2026-05-07T20:11:45.000Z", "latencyMs": 12 }
}
```

The read mirrors the dispatch: the same `command` and `parameters` you sent come back, alongside the lifecycle fields. The `deviceType` is the clean device family, and the `command` is the same canonical verb you posted.

When the window opens, the command dispatches to the OEM. `acknowledgedAt` populates and `state` advances to `acknowledged`. On success, `state` reaches `completed` and a `push.completed` webhook fires.

## Step 4: Cancel before it fires

Plans change. Cancel the action while it is still `scheduled`.

```bash theme={null}
curl -X POST https://api.amps.ai/actions/act_pending_001/cancel \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx"
```

The response echoes the action with `state: cancelled`.

## What next

<CardGroup cols={2}>
  <Card title="Discharge during peak" icon="bolt" href="/guides/cookbook/discharge-windowed">
    Mirror this recipe to force-discharge during expensive windows.
  </Card>

  <Card title="Subscribe to webhooks" icon="webhook" href="/guides/cookbook/subscribe-webhooks">
    Receive push.completed events instead of polling.
  </Card>

  <Card title="Handle conflicts" icon="triangle-alert" href="/guides/cookbook/handle-conflict">
    What happens when an action is already in flight.
  </Card>

  <Card title="Scheduling concepts" icon="calendar" href="/concepts/canonical-actions">
    The semantic background to start, end, and execution shapes.
  </Card>
</CardGroup>

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Schedule a Charge for Later",
"description": "Schedule a battery charge for later: defer it with a bare start, or bound a cheap-rate window with start and end. Capability check, curl, action lifecycle, cancellation.",
"step": [
{
  "@type": "HowToStep",
  "name": "Step 1: Check capability before pushing",
  "position": 1
},
{
  "@type": "HowToStep",
  "name": "Step 2: Schedule the charge",
  "position": 2
},
{
  "@type": "HowToStep",
  "name": "Step 3: Track the action",
  "position": 3
},
{
  "@type": "HowToStep",
  "name": "Step 4: Cancel before it fires",
  "position": 4
}
]
})}}
/>
