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

# Read Charging Sessions

> List an EV charger's charging sessions, page through the history, and read the measurement provenance behind each energy figure.

## Overview

A charging session is one plug-in-to-completion episode: when the vehicle arrived, when it left, and how much energy went into it. `GET /ev-charger/{deviceId}/sessions` reads them for one charger, newest first.

Use it to bill a driver, show a home owner last night's charge, or reconcile a month of energy against a tariff.

<Callout icon="clock" color="#ED6D2C">
  **Check `sessions` on the device read first.** `GET /ev-charger/{deviceId}` carries a `sessions` boolean. `false` means the manufacturer records no history and this endpoint answers 422 `SESSIONS_NOT_SUPPORTED`, permanently. Reading the flag costs you nothing and saves a request that can only fail.
</Callout>

## Step 1: Read one charger's sessions

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

```json theme={null}
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "evs_Ht6vB1zQnE5wXpKm7ArJdU",
        "deviceId": "device_abc123",
        "status": "active",
        "startedAt": "2026-07-29T22:48:00.000Z",
        "energyDelivered": { "value": 12.3, "unit": "kwh" },
        "measurement": "metered",
        "links": { "device": "/ev-charger/device_abc123" }
      },
      {
        "id": "evs_9tQ2mK4xPvR8sLdN3bWfYc",
        "deviceId": "device_abc123",
        "status": "completed",
        "startedAt": "2026-07-28T22:31:00.000Z",
        "endedAt": "2026-07-29T04:12:00.000Z",
        "energyDelivered": { "value": 41.6, "unit": "kwh" },
        "measurement": "metered",
        "endReason": "vehicle_finished",
        "links": { "device": "/ev-charger/device_abc123" }
      }
    ],
    "pagination": { "limit": 2, "offset": 0, "total": 3, "hasMore": true },
    "window": {
      "from": "2026-06-30T09:15:00.000Z",
      "to": "2026-07-30T09:15:00.000Z"
    }
  },
  "meta": {
    "requestId": "req_8a2Bf3kP",
    "environment": "sandbox",
    "timestamp": "2026-07-30T09:15:00.000Z",
    "latencyMs": 12
  }
}
```

Timestamps here are absolute UTC, unlike the plant-local wall-clock you send on a push. A session is a record of something that happened, so there is no local intent to preserve.

`id` is an Amps identifier, opaque and unique across every charger, so it is safe as a primary key in your own store. It is not the manufacturer's session number, which is often a position in a list and renumbers as older records age out.

### Two shapes, keyed by `status`

An active session is still running, so it carries no `endedAt` and no `endReason`, and its `energyDelivered` is a running total that may be absent if the charger reports no figure mid-session. A completed session always carries `endedAt`, `energyDelivered`, and `measurement`. Branch on `status` before reading the end fields:

```javascript theme={null}
const { items } = (await res.json()).data;

for (const s of items) {
  if (s.status === "active") {
    console.log(`charging since ${s.startedAt}`);
  } else {
    console.log(`${s.energyDelivered.value} kWh, ended ${s.endReason}`);
  }
}
```

`endReason` is present when the charger says why the session stopped: `unplugged`, `vehicle_finished`, `stopped` (someone ended it), `power_lost`, `fault`, or `unknown`. It is absent when the charger offers no reason at all, which is not the same as `unknown`.

## Step 2: Read the provenance before you bill

`measurement` says how the `energyDelivered` figure was arrived at, and the three values are not interchangeable.

| Value          | How the number was produced                                                                                                |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `metered`      | Read from a cumulative energy register on the charger.                                                                     |
| `oem_reported` | The manufacturer computed the session total itself.                                                                        |
| `inferred`     | Derived by integrating power readings across the session, so its accuracy is bounded by how often the charger was sampled. |

**Provenance is not accuracy.** No value here is a settlement-grade guarantee. `metered` says the charger kept the count, not that the charger is a certified revenue meter or that the reading has been reconciled. Take the accuracy you need for billing from the charger's own metering certification, and use `measurement` to decide whether a figure is worth billing on at all.

The practical rule: bill on `metered`, show `oem_reported` with the manufacturer named, and treat `inferred` as an estimate in the interface.

## Step 3: Page through the history

`pagination.hasMore` is the loop condition. `limit` accepts 1 to 50 and defaults to 10; `offset` defaults to 0.

**Pin the window before you page.** The list is newest first and the charger keeps being used, so the set grows at the head. With no explicit `to`, every request resolves the upper bound to the current instant, and a session that starts between two of your requests pushes every older row down one place — the row that was about to be your next page's first is never returned. Each page reports the window it was cut from as `window`; send its `to` back on every subsequent request and all your pages come from one set.

```javascript theme={null}
async function allSessions(deviceId) {
  const out = [];
  let offset = 0;
  let to = null;

  for (;;) {
    const url = new URL(`https://api.amps.ai/ev-charger/${deviceId}/sessions`);
    url.searchParams.set("limit", "50");
    url.searchParams.set("offset", String(offset));
    if (to) url.searchParams.set("to", to);

    const res = await fetch(url, {
      headers: { "x-api-key": process.env.AMPS_API_KEY },
    });
    const { data } = await res.json();
    to ??= data.window.to;
    out.push(...data.items);
    if (!data.pagination.hasMore) return out;
    offset += data.pagination.limit;
  }
}
```

Filter before you page rather than after. Three query parameters narrow the set:

| Parameter     | Effect                                                                                                                                                                                               |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from` / `to` | Only sessions overlapping the window, as ISO 8601 UTC instants. `to` defaults to now, `from` to 30 days before `to`. Send back the `window.to` a page reported to keep a multi-page walk on one set. |
| `status`      | `active` or `completed`.                                                                                                                                                                             |

A window is an overlap test, not a containment test: a session that began before `from` and was still running inside the window is returned, so a month's query does not lose the charge that started on the last night of the previous month.

## Sessions versus the device read

Both surfaces talk about energy, and they answer different questions.

| Question                                            | Where to look                                         |
| --------------------------------------------------- | ----------------------------------------------------- |
| How much has gone in since this car plugged in?     | `state.sessionEnergy` on `GET /ev-charger/{deviceId}` |
| How much went into each of the last thirty charges? | `GET /ev-charger/{deviceId}/sessions`                 |
| Is it charging right now, and if not, why not?      | `state.isCharging` and `state.notChargingReason`      |

`sessionEnergy` is a live reading that resets with each plug-in. The session list is the durable record, and it carries the provenance that the live reading does not. See [device state](/concepts/device-state) for the full telemetry surface.

## What next

<CardGroup cols={2}>
  <Card title="Smart charging modes" icon="bolt" href="/guides/cookbook/ev-smart-charging">
    Hand timing to the charger's own price or solar optimiser.
  </Card>

  <Card title="Cap an EV charger's power" icon="gauge" href="/guides/cookbook/ev-charger-set-power">
    The standing ceiling every session runs under.
  </Card>

  <Card title="EV charger cheat sheet" icon="list-checks" href="/reference/ev-charger-cheat-sheet">
    The whole charger surface in one page.
  </Card>

  <Card title="Subscribe to webhooks" icon="webhook" href="/guides/cookbook/subscribe-webhooks">
    React to charger events instead of polling.
  </Card>
</CardGroup>

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Read Charging Sessions",
"description": "List an EV charger's charging sessions, page through the history, and read the measurement provenance behind each energy figure.",
"step": [
{ "@type": "HowToStep", "name": "Step 1: Read one charger's sessions", "position": 1 },
{ "@type": "HowToStep", "name": "Step 2: Read the provenance before you bill", "position": 2 },
{ "@type": "HowToStep", "name": "Step 3: Page through the history", "position": 3 }
]
})}}
/>
