Skip to main content

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.

A canonical action is the single body shape every device push accepts, regardless of OEM. Build it from the device’s read response, post it, get an actionId back. The same shape works for immediate dispatch, scheduled dispatch, and slots inside a schedule. Agents do not click buttons. They read a response, choose a verb, build the parameters, and post. One read and one write are enough to drive any supported device.

The action shape

{
  "action": {
    "command": "charge",
    "parameters": {
      "power": { "value": 3.0, "unit": "kw" },
      "target": { "value": 80, "unit": "percent" }
    },
    "start": "2026-06-01T18:00:00+01:00",
    "end": "2026-06-01T22:00:00+01:00"
  },
  "onConflict": "cancel_and_replace"
}
command selects the variant. parameters carries the mode’s inputs. start and end declare temporal intent. onConflict resolves collisions with active actions on the same device. Every field you send is either honoured, validated, or rejected. No keys are silently discarded.

Battery command vocabulary

The canonical battery surface defines exactly six commands:
CommandMeaning
chargeDirect imperative. Charge from grid or solar, optionally bounded by target and power.
dischargeDirect imperative. Discharge to load or grid, optionally bounded by target and power.
idlePause charge and discharge. Battery sits.
auto.balancedOptimise for self-consumption. Charge when solar is plentiful, discharge when the home needs it.
auto.reserveReserve capacity for grid outage. Stay charged above the reserve floor.
auto.exportMaximise grid export. Discharge when the export tariff is attractive.
Direct commands (charge, discharge, idle) are explicit instructions. Auto commands name the device’s own self-managing modes in canonical vocabulary. auto.balanced maps to the self-consumption mode; auto.reserve to the backup mode; auto.export to the grid-export mode. The canonical name stays the same across every supported device; your request shape does not change when you move between devices. A command present in the device’s commands map is supported. Absent means rejected. There is no supported: boolean field. See capabilities for how each device declares its subset.

Self-documenting parameters

Every numeric parameter is a Quantity: { value, unit }. The unit travels with the value, so 80 is never ambiguous between percent, kilowatts, or amps.
{ "value": 80, "unit": "percent" }
{ "value": 3.5, "unit": "kw" }
{ "value": 10, "unit": "percent" }
Three canonical battery parameters exist: target, power, reserve. target is an upper bound for charge and a lower bound for discharge. power caps the rate. reserve preserves a state-of-charge floor while the mode runs. Send a unit the device does not accept and the API returns 422 UNSUPPORTED_UNIT. Send a parameter the mode does not accept and you get 422 UNSUPPORTED_PARAMETER with a deviceCapabilities snapshot in details, so you can fix the call in one round trip. See error envelope for the full list.

Per-mode execution support

Each command on a device declares an execution array drawn from { "immediate", "scheduled", "windowed" }. Each token names a distinct request shape:
TokenRequest shapeMeaning
immediateNo startFire on receipt.
scheduledstart onlyDefer firing until start.
windowedstart and endRun between start and end, then revert.
A push whose shape does not match the mode’s execution array returns 422 EXECUTION_NOT_SUPPORTED with details: { requestedExecution, supportedExecution }. On a typical device, auto.balanced declares ["immediate", "scheduled"] (no end makes sense for a long-running mode), while charge may declare ["immediate", "scheduled", "windowed"].
Read the device once. Pick a command. Inspect its execution array. Build the request shape that array allows. The API tells you what is acceptable, so you do not need to know per-OEM physics.

Conflict resolution

Only one non-terminal action can target a device at a time. Submit a new push while another is active or scheduled and onConflict decides what happens:
StrategyBehaviour
cancel_and_replaceCancel the conflicting action, run the new one.
queue_afterDefer the new action’s start to the conflict’s end.
OmittedReturn 409 with the conflicting action ID and the available strategies.
The model is documented end-to-end on conflict resolution.

Datetime and time-window contract

start and end are ISO 8601 strings with an explicit timezone offset (Z or +01:00). Naive datetimes are rejected. Relative durations (30m, 1.5h) are accepted on start and normalised to absolute instants. start must be in the future and within 30 days. end must be after start. Time windows are right-open intervals [start, end).
{
  "command": "charge",
  "parameters": { "power": { "value": 5, "unit": "kw" } },
  "start": "30m",
  "end": "2026-06-01T22:00:00+01:00"
}

Capability introspection

The device read returns the same vocabulary you post back:
{
  "id": "device_abc123",
  "state": { "status": "charging", "level": 50 },
  "commands": {
    "charge": {
      "parameters": {
        "power": { "unit": "kw", "min": 0, "max": 5.0 },
        "target": { "unit": "percent", "min": 10, "max": 100 }
      },
      "execution": ["immediate", "scheduled", "windowed"]
    },
    "auto.balanced": {
      "parameters": {},
      "execution": ["immediate", "scheduled"]
    }
  },
  "settings": { "discharge_floor": { "value": 10, "unit": "percent" } }
}
commands.charge.parameters on the read maps directly to action.parameters on the write. The bounds (min, max) and units come straight from the device’s declared capabilities. Bounds are optional; an absent min or max means open-ended on that side, as documented in capabilities. Presence in the map means supported. Absence means rejected.

Actions are not settings

Actions answer “what should the device do?” Settings answer “within what bounds should it operate?” Actions are time-boundable, conflict-able, and audited. Settings are persistent, non-conflicting, and fire-and-forget. The litmus test: “does this make sense with a time window?” You can say “charge from 6pm to 10pm”. You would never say “set the safety reserve to 10% from 6pm to 10pm”. If it is time-boundable, it is an action. Actions run through POST /battery/{deviceId} and create an audit record. Settings run through POST /battery/{deviceId}/settings and do not. See the API reference for both.

Frequently asked questions

What is auto.balanced?

auto.balanced is a canonical battery mode that names the device’s self-managing self-consumption mode under a shared name. It is one of three auto modes (auto.balanced, auto.reserve, auto.export) that every supported battery speaks. The client sends the same request shape across every device.

How do I push an action with a time window?

Send start and end as ISO 8601 strings with explicit timezone offsets in the action body. The command must declare windowed in its execution array; charge and discharge do, the auto modes typically do not. The action runs from start, then reverts at end. Naive datetimes without an offset are rejected. Sub-minute windows are rejected on devices whose schedulers operate at minute resolution.

What is the difference between command and parameters in the action body?

command is the verb: charge, discharge, idle, auto.balanced, auto.reserve, or auto.export. parameters is the bag of inputs the verb accepts: target (an SOC bound), power (a rate cap), reserve (an SOC floor preserved during the command). Direct commands like charge accept all three. Auto commands accept none on the canonical surface; their behaviour is intent-only.

Can I send a charge and an auto.balanced action at the same time?

Only one non-terminal action targets a device at a time. Submit auto.balanced while a charge is scheduled or acknowledged and the API returns 409 CONFLICT unless onConflict resolves it. Use cancel_and_replace to drop the existing action and run the new one, or queue_after to defer the new action until the existing one ends. One active intent per device.

What happens if I send a parameter the device does not support?

The API returns 422 UNSUPPORTED_PARAMETER with details.unsupportedParameters[] and a deviceCapabilities snapshot. The snapshot has the same shape as the GET response, so the caller rebuilds the request from the rejection without a second fetch. Sending an unknown unit returns 422 UNSUPPORTED_UNIT with the supported units. Sending a value outside the declared range returns 422 PARAMETER_OUT_OF_RANGE.

Capabilities

How the device tells you which commands, parameters, and units it accepts.

Conflict Resolution

How onConflict handles overlap and 409s.

Scheduling

Push as the only execution primitive. Schedules as coordinators.

Error Envelope

The shape of every rejection, including UNSUPPORTED_UNIT and EXECUTION_NOT_SUPPORTED.
For a worked walkthrough, see the cookbook: charge overnight.