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.

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.

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 a Quantity: { value, unit }. The unit travels with the value.
{ "value": 80, "unit": "percent" }
{ "value": 3.5, "unit": "kw" }
This eliminates silent misinterpretation. The contract is explicit in every request. No 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:
CanonicalMeaning
charge / dischargeDirect imperatives.
auto.balancedSelf-consumption mode.
auto.exportGrid-export mode.
auto.reserveBackup mode.
targetAn SOC bound for charge or discharge.
reserveAn 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 no supported: 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 without start 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 accept auto.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:
SurfaceQuestionProperties
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.
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. Mode changes route through the push path; settings route through the settings path. The same shape works across every supported device.

No per-vendor escape hatches

There are no per-vendor keys on action payloads, no vendorParameters[] 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 422 UNSUPPORTED_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 of GET /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 a Quantity: { 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 no vendorParameters[] 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 422 UNSUPPORTED_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.

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.