The Amps API is built so any caller, whether a human developer or an AI agent, can read one response, understand what is possible, and construct a valid command without reading the docs first. The GET response describes the device. The POST request mirrors that structure with values filled in.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.
Canonical vocabulary, not canonical behaviour
The API normalises vocabulary, not behaviour.charge means “the battery should be gaining energy”. How that happens varies by OEM. The verb is universal; the underlying implementation is specific. The capabilities response on a specific device tells you what to expect there.
Self-documenting requests
Every numeric parameter is aQuantity: { value, unit }. The unit travels with the value.
powerKw vs powerPercent ambiguity, no mutual exclusivity validation, no docs lookup to know what the number represents. Send a unit the device does not accept and the API rejects with UNSUPPORTED_UNIT, naming the units that do work.
Developer-accessible naming
Canonical command and parameter names are human-readable and uniform across every supported device:| Canonical | Meaning |
|---|---|
charge / discharge | Direct imperatives. |
auto.balanced | Self-consumption mode. |
auto.export | Grid-export mode. |
auto.reserve | Backup mode. |
target | An SOC bound for charge or discharge. |
reserve | An SOC floor preserved during the command. |
Capability-driven design
Capability is declared by presence. A command, parameter, or setting present in the device’s capabilities response is supported; absent means rejected. There is nosupported: boolean field. An empty parameters: {} means “valid command, no parameters”. The structure you read is the structure you write back: commands.charge.parameters.power on the read maps directly to action.parameters.power on the write. This shape makes agentic workflows mechanical. An AI agent reads the device, picks a command, builds parameters from the constraint bounds, and posts. No hardcoded OEM knowledge required.
See capabilities for the full response shape.
Honest API: no hidden defaults
What you push is what the device receives. Amps does not paper over OEM limits with hidden defaults. Invoke a windowed-only mode withoutstart and end and the API rejects with EXECUTION_NOT_SUPPORTED. No silent 24-hour covering window faking an immediate execution.
The same rule applies to settings writes. When an OEM treats omitted fields as “revert to defaults”, Amps preserves your other settings automatically. You only send the field you want to change; nothing else is silently rewritten.
When Amps does not know how to honour a request, it surfaces the gap rather than guessing. The 422 carries details rich enough to recover from in one round trip.
Per-mode granularity
Scheduling support is per-mode, not per-device. A device may acceptauto.balanced immediately but require charge to be windowed. Each command on the device declares an execution array drawn from { "immediate", "scheduled", "windowed" }. Send a request shape that the array does not allow and the API returns 422 EXECUTION_NOT_SUPPORTED with details: { requestedExecution, supportedExecution }. Three tokens name three valid request shapes; the API rejects the call when the device cannot run the shape you asked for.
Actions versus settings
Two surfaces exist on every controllable device:| Surface | Question | Properties |
|---|---|---|
Actions (POST /battery/{id}) | What should the device do? | Time-boundable, conflict-able, audited. |
Settings (POST /battery/{id}/settings) | Within what bounds should it operate? | Persistent, non-conflicting, fire-and-forget. |
No per-vendor escape hatches
There are no per-vendor keys on action payloads, novendorParameters[] array, no OEM-specific parameters on the canonical schema. The same request shape works across every supported device, and your code never needs to know which OEM is on the other end.
No silent field discarding
Every field you supply is either honoured, validated, or rejected. Sending an unknown field returns 422. Sending a known-but-unsupported parameter returns 422UNSUPPORTED_PARAMETER with the supported set in details. Sending a parameter outside its declared bounds returns 422 PARAMETER_OUT_OF_RANGE. There is no path where a field is accepted, ignored, and you believe it took effect.
Read-write parity
The same shape that comes out ofGET /battery/{id} is what goes into POST /battery/{id}. The self-consistency test: any field the read names, the write either accepts or rejects with a precise reason. New concerns (settings, schedules, capabilities) get a new top-level field on the read and a new endpoint for the write. No retrofitting. No overloaded endpoints. Every concern has its own read field and its own write surface.
Frequently asked questions
Why does every parameter carry a unit?
The unit travels with the value to eliminate silent misinterpretation. Every numeric parameter is aQuantity: { value, unit }. There is no powerKw vs powerPercent ambiguity, no docs lookup to know what 80 means. Send a unit a device does not accept and the API rejects with 422 UNSUPPORTED_UNIT, naming the units that do work. The contract is explicit in every request.
Does Amps normalise OEM behaviour?
No. The API normalises vocabulary, not behaviour.charge means “the battery should be gaining energy”; how that happens varies by OEM. Each device declares what it accepts via capabilities, and the read tells you what to expect there.
Why are auto modes the same across OEMs?
Because they name intent at the protocol level, not implementation.auto.balanced is the device’s self-managing self-consumption mode under a shared name; auto.reserve is its backup mode; auto.export is its grid-export mode. The client sends the same request shape across every supported device.
Are there per-vendor escape hatches?
No. There is novendorParameters[] array and no OEM-specific keys on action payloads. The same request shape works across every supported device, and your code never needs to know which OEM is on the other end.
What happens to fields the API does not recognise?
It returns 422. Every field a client supplies is either honoured, validated, or rejected. Sending an unknown field returns 422. Sending a known-but-unsupported parameter returns 422UNSUPPORTED_PARAMETER with the supported set in details. Sending a value outside its declared bounds returns 422 PARAMETER_OUT_OF_RANGE. There is no path where a field is accepted, ignored, and you believe it took effect.
Related concepts
Canonical Actions
The action shape these principles produce.
Capabilities
How devices declare what they accept.
How Amps works
Device control, strategy, and routing across a single API.
Error Envelope
Honest rejections that tell you exactly what is wrong.