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

# Quickstart

> Integrate Amps in four steps: get sandbox keys, send users through Link UI, pull device state, push the first action. Includes a worked example.

## Prerequisites

Before you begin, you'll need:

* An Amps AI customer account
* API keys (sandbox and/or live)
* A webhook endpoint URL (optional but recommended)
* Access to the Link UI for device authentication

## Step 1: Get your API keys

<Info>
  You will need to request Sandbox access before completing these steps. [Get sandbox access](https://tally.so/r/D4zgZE) to continue.
</Info>

Every request goes to the same host:

```
https://api.amps.ai
```

The environment is decided by the **prefix on the API key**, not the URL. An `sk_test_` key routes to sandbox simulations; an `sk_live_` key routes to real OEM devices. Send the key in the `x-api-key` header.

<Steps>
  <Step title="Create Account">
    Sign up for an Amps AI account in the [dashboard](https://app.amps.ai)
  </Step>

  <Step title="Generate API Keys">
    Create separate API keys for sandbox and live environments in your dashboard settings
  </Step>

  <Step title="Configure Webhooks">
    Set up webhook endpoints to receive device events and action notifications
  </Step>
</Steps>

<Tip>
  Keep your API keys secure. Never commit them to version control or expose them in client-side code.
</Tip>

### Device-type routes

Five device families, one URL shape. The path segment names the device type and the rest is the same across all of them:

| Device type    | Read                             | Push                                                     | Settings                               |
| -------------- | -------------------------------- | -------------------------------------------------------- | -------------------------------------- |
| Battery        | `GET /battery/{deviceId}`        | `POST /battery/{deviceId}`                               | `POST /battery/{deviceId}/settings`    |
| EV charger     | `GET /ev-charger/{deviceId}`     | `POST /ev-charger/{deviceId}`                            | `POST /ev-charger/{deviceId}/settings` |
| HVAC           | `GET /hvac/{deviceId}`           | `POST /hvac/{deviceId}`                                  | none                                   |
| Solar inverter | `GET /solar-inverter/{deviceId}` | read-only (push returns 422 `DIRECT_ACTION_UNSUPPORTED`) | none                                   |
| Vehicle        | `GET /vehicle/{deviceId}`        | read-only (push returns 422 `DIRECT_ACTION_UNSUPPORTED`) | none                                   |

Solar inverters and vehicles accept no imperative commands. They report state; they do not take actions. Use them for monitoring and inclusion in fleet-wide reads.

## Step 2: Connect devices via Link UI

Direct your end users to the Link UI to authenticate with OEMs and connect their devices:

**Base URL:**

```
https://auth.amps.ai/{appId}
```

**For Sandbox Testing:**

```
https://auth.amps.ai/{appId}?sandbox=true
```

**For Production:**

```
https://auth.amps.ai/{appId}
```

<Note>
  Replace `{appId}` with your actual application ID from the dashboard. The Link UI handles the entire authentication flow including OEM selection, credential entry, MFA (if required), device selection, and consent collection.
</Note>

<Card title="Environments Guide" icon="info" href="/get-started/environments">
  Learn more about Sandbox and Live environments
</Card>

## Step 3: Make your first API call

Test your integration by retrieving device data:

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

### Example response

Every response is wrapped in a consistent envelope: `success`, `data`, and `meta`. A single read carries the device under `data`; a list carries `{items, pagination}` under `data`. See [error envelope](/concepts/error-envelope) for the full shape and the failure variant.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "device_abc123",
    "vendor": "tesla",
    "state": {
      "level": 85,
      "status": "charging",
      "capacity": 13.5,
      "chargeRate": 5.2,
      "dischargeLimit": 10,
      "currentMode": "charge"
    },
    "metadata": {
      "model": "Powerwall 2",
      "source": "projection"
    },
    "sync": {
      "available": true,
      "lastPulledAt": "2026-06-01T10:30:00.000Z"
    },
    "commands": {
      "charge": {
        "parameters": {
          "target": { "unit": "percent", "min": 10, "max": 100 },
          "power": { "unit": "kw", "min": 0, "max": 5.0 }
        },
        "execution": ["immediate", "scheduled"]
      },
      "discharge": {
        "parameters": {
          "target": { "unit": "percent", "min": 10, "max": 100 },
          "power": { "unit": "kw", "min": 0, "max": 5.0 }
        },
        "execution": ["immediate", "scheduled"]
      },
      "auto.balance": {
        "parameters": {},
        "execution": ["immediate", "scheduled"]
      }
    },
    "conflictStrategies": ["cancel_and_replace", "queue_after"],
    "settings": {
      "discharge_floor": { "value": 10, "unit": "percent", "min": 10, "max": 100 },
      "charge_ceiling": { "value": 100, "unit": "percent", "min": 10, "max": 100 }
    },
    "lastAction": {
      "id": "act_abc123",
      "command": "auto.balance",
      "state": "completed",
      "createdAt": "2026-06-01T09:15:00.000Z",
      "updatedAt": "2026-06-01T09:15:03.000Z",
      "errorCode": null,
      "errorMessage": null,
      "links": { "self": "/actions/act_abc123" }
    },
    "currentSchedule": null
  },
  "meta": {
    "requestId": "req_8sW2dRtX",
    "environment": "sandbox",
    "timestamp": "2026-06-01T10:30:00.000Z",
    "latencyMs": 42
  }
}
```

In code, unwrap the envelope before reading device fields:

```javascript theme={null}
const res = await fetch("https://api.amps.ai/battery/device_abc123", {
  headers: { "x-api-key": process.env.AMPS_API_KEY },
});
const body = await res.json();          // { success, data, meta }
const device = body.data;                // the device record
console.log(device.state.status);        // "charging"
```

```python theme={null}
body = res.json()                        # { success, data, meta }
device = body["data"]                    # the device record
print(device["state"]["status"])         # "charging"
```

Reading `body.state` or `body["state"]` returns `undefined` / raises `KeyError`. The shape is the same on every endpoint, so write the unwrap once and reuse it.

The `state` object names what the device is doing now: `status` is `charging`, `discharging`, `idle`, or `standby`; `currentMode` echoes the standing command (a `charge` that has already reached its target reads `status: "idle"`, `currentMode: "charge"`, `chargeRate: 0`, because the command stands but no energy is moving). See [device state](/concepts/device-state) for the full state schema per device type.

The `conflictStrategies` array lists the `onConflict` values this device accepts when a push collides with an action already in flight. Read it before scheduling; conflict handling is covered in [Step 5](#step-5-push-your-first-command) and in [conflict resolution](/concepts/conflict-resolution).

`lastAction` is a summary of the most recent action dispatched to this device, enough to render a device card in one call: the canonical `command`, the lifecycle `state`, `createdAt` and `updatedAt` (the most recent state transition), and `errorCode`/`errorMessage` (populated on a failed action, null otherwise). It is a HATEOAS pointer rather than a denormalised copy: follow `lastAction.links.self` to `GET /actions/{actionId}` for the full record (parameters, full result, all timestamps). A just-pushed action can take a moment to appear here; if `lastAction` looks stale right after a write, read the action by id at `links.self` for the current lifecycle state. `currentSchedule` is the schedule governing the device. It is `null` for now and arrives with the scheduler; setting a schedule via `PUT /battery/{deviceId}/schedule` returns `501 NOT_IMPLEMENTED` until it lands. Following a schedule already works today through the HVAC `auto.schedule` command.

## Step 4: Set up webhooks

Configure webhook endpoints to receive real-time notifications:

1. Add your webhook URL in the dashboard
2. Subscribe to the events you want to receive:
   * `device.connected`
   * `device.disconnected`
   * `push.completed`
   * `push.failed`
3. Verify webhook signatures to ensure requests are from Amps AI

<Card title="Webhook Setup Guide" icon="webhook" href="/guides/webhooks/receive">
  Learn more about configuring webhooks
</Card>

## Step 5: Push your first command

Send a command to a device. Every device type takes the same body: an `action` with a `command` and its `parameters`. This example holds a thermostat in `auto.maintain` between a heat and cool setpoint in degrees Celsius:

```bash curl theme={null}
curl -X POST "https://api.amps.ai/hvac/{deviceId}" \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action": {
      "command": "auto.maintain",
      "parameters": {
        "heatSetpoint": { "value": 20, "unit": "celsius" },
        "coolSetpoint": { "value": 24, "unit": "celsius" }
      }
    }
  }'
```

The response carries the new action under `data`, wrapped in the same `{success, data, meta}` envelope as the read:

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_hvac_004",
    "deviceId": "device_hvac_001",
    "deviceType": "hvac",
    "command": "auto.maintain",
    "parameters": {
      "heatSetpoint": { "value": 20, "unit": "celsius" },
      "coolSetpoint": { "value": 24, "unit": "celsius" }
    },
    "state": "acknowledged",
    "createdAt": "2026-06-01T10:30:00.000Z",
    "links": { "self": "/actions/act_hvac_004" }
  },
  "meta": {
    "requestId": "req_9tX3eRuY",
    "environment": "sandbox",
    "timestamp": "2026-06-01T10:30:00.000Z",
    "latencyMs": 84
  }
}
```

```javascript theme={null}
const body = await res.json();
const { id, deviceId, state, links } = body.data;  // unwrap before reading
```

An **immediate push** (no `start`) lands in `acknowledged` and reaches `completed`. A **deferred or windowed push** (with `start`, optionally with `end`) waits in `scheduled` until the fire time, then moves to `acknowledged` and on to `completed`. Use webhooks or `GET /actions/{actionId}` (via `links.self`) to track the transition.

The same shape drives a battery (`charge`, `discharge`, the auto modes) and an EV charger (`charge`, `idle`); only the verb and its parameters change. Live battery control is available; live EV charger and thermostat control is coming soon, and the sandbox environment runs the full surface in the meantime.

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/get-started/authentication">
    Learn about API authentication
  </Card>

  <Card title="Link UI Guide" icon="link" href="/guides/link-ui/getting-started">
    Understand the device connection flow
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guides/webhooks/receive">
    Set up webhook endpoints
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Explore the full API documentation
  </Card>
</CardGroup>

## Common tasks

<AccordionGroup>
  <Accordion title="How do I test without real devices?">
    Use the sandbox environment. The sandbox simulates the full canonical surface across every device type. When you push a command in sandbox, the next read reflects that command right away: push `charge` and the device reads `charging`, push `idle` and it reads idle. See the <a href="/get-started/environments">Environments Guide</a> for the full sandbox model.
  </Accordion>

  <Accordion title="How do I handle multiple devices?">
    Each device has a unique `deviceId`. List all devices of a type with `GET /battery`, `GET /ev-charger`, `GET /hvac`, `GET /solar-inverter`, or `GET /vehicle`. The response shape is `{ success: true, data: { items: [...], pagination: {...} }, meta }`. Filter to one end-user with `?userId=`.
  </Accordion>

  <Accordion title="What happens if a device goes offline?">
    It depends on the failure. A transient OEM error (network blip, timeout, rate limit) falls back to the last-known state: you get a `200` with `sync.available: false` so you know the data is not fresh. A permanent error (`DEVICE_OFFLINE`, `DEVICE_NOT_FOUND`) returns a 4xx with the standard error envelope and no stale fallback. See [device state](/concepts/device-state) for the full three-tier model. Webhooks notify you of connection status changes.
  </Accordion>

  <Accordion title="How do I check action status?">
    Actions are processed asynchronously. The push response's `data.links.self` points at the action. Poll `GET /actions/{actionId}` (device-agnostic, the same route for every device type) and unwrap `body.data` for the current lifecycle state, or subscribe to `push.completed` and `push.failed` webhooks. An immediate push starts at `acknowledged`; a deferred or windowed push waits in `scheduled` until the fire time.
  </Accordion>
</AccordionGroup>
