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.

GET /battery/{deviceId} returns the device’s current state, its capability declaration, and metadata identifying where the data came from. The retrieval path is identical across device types; only the state schema and command set change.

The response envelope

{
  "id": "device_abc123",
  "vendor": "FoxESS",
  "sync": { "available": true, "lastPulledAt": "2026-06-01T10:14:23Z" },
  "metadata": { "source": "cache", "cacheType": "normal", "model": "H1-5.0-E" },
  "state": {
    "status": "charging",
    "level": 50,
    "capacity": 10400,
    "chargeRate": 2.4
  },
  "commands": {
    "charge": { "parameters": { "power": { "unit": "kw", "min": 0, "max": 5 } }, "execution": ["immediate", "scheduled", "windowed"] },
    "auto.balanced": { "parameters": {}, "execution": ["immediate", "scheduled"] }
  },
  "settings": {
    "discharge_floor": { "value": 10, "unit": "percent", "min": 0, "max": 100 }
  }
}
state is the live snapshot. commands and settings are computed from the device’s declared capabilities at read time and merged with live values. See capabilities for the full surface.

Three-tier retrieval

Reads pass through three tiers in order:
TierSourceWhen
CacheCache layerTTL-valid entry exists.
LiveOEM APICache miss, or expedited request.
FallbackLast-known stored stateLive call fails with a transient error.
GET /battery/{id}
  -> Cache (TTL-bounded)
    -> miss or expedite
      -> Live OEM API
        -> transient failure -> stored last-known
Cache and live both report sync.available: true. Fallback reports sync.available: false so you know the data is not authoritative for the current request. The metadata.source field names the tier explicitly.

Cache modes: normal and expedited

Two cache TTLs exist:
ModeTTLWhen to use
Normal15 minutesDefault. Covers dashboard reads, periodic checks.
Expedited1 minuteTriggered by ?expedite=true. Forces near-fresh data.
A normal request prefers a fresher expedited entry when one exists. Both tiers are checked; whichever is freshest returns. An expedited request checks only the expedited tier and goes live on miss; it does not fall back to a 10-minute-old normal cache, because the explicit signal was “I want fresh data”. Cache invalidation is purely TTL-based. Push completions and state changes do not invalidate the cache. Reads see the world through the cache window; webhooks deliver state-change notifications independently.
After a push completes, do not poll the device immediately expecting a fresh state: the cache TTL still applies. Subscribe to the matching push.completed webhook, or call with ?expedite=true when freshness matters more than the OEM rate.

Transient versus permanent errors

OEM errors are classified:
ClassCodesBehaviour
TransientNETWORK_ERROR, RATE_LIMITED, SERVICE_UNAVAILABLE, TIMEOUT, UNKNOWN_ERRORFall back to last-known stored state.
PermanentDEVICE_NOT_FOUND, DEVICE_OFFLINEReturn to caller. No fallback.
Permanent errors mean the device is definitively unknown for this request, and stale fallback data would mislead you. Transient errors mean the OEM is briefly unavailable, so last-known state is the best answer Amps can give. metadata.source: 'fallback' plus sync.available: false make the difference visible. If no data exists at any tier (empty cache, failing OEM call, no last-known record), the API returns 404 DEVICE_NOT_FOUND.

What is live versus cached

The state object is the live or cached device state. The commands and settings objects merge capability declarations with live values: a setting’s bounds come from its declared capabilities, its current value comes from the device. Capability declarations themselves take effect on the next read; there is no separate cache invalidation for capability data. You see canonical names only; OEM-native identifiers never appear in the response.

Polling cadence and OEM rate limits

Per-OEM rate limits are honoured automatically. A burst of expedited reads against the same device coalesces; the platform enforces a minimum gap. Short cache TTLs paired with high read volumes will not inadvertently DDoS the OEM. For state changes that require sub-minute reaction, prefer webhooks over polling. push.completed and push.failed events deliver to your registered endpoint with retry and signature verification built in. Polling is the right default for dashboard-style displays, where the user accepts the visible cache freshness.

Per-OEM availability

Different OEMs expose different fields. A vehicle reports batteryLevel but no chargeRate. A heat pump reports temperature and mode, no SOC. The state schema is per-device-type:
Device typeKey fields
Batterystatus, capacity, level, chargeRate, dischargeLimit
EV chargerstatus, isConnected, isCharging, currentPower, maxCurrent
HVACtemperature, active, heatSetpoint, coolSetpoint, mode
Solar inverterpower, systemState
VehiclebatteryLevel, chargeState
Within a device type, the schema is identical across every supported device. Differences in OEM physics (does this battery report a cell-temperature breakdown? does this inverter expose per-string voltages?) surface as nullable fields when the OEM does not populate them.

Listing devices

GET /{deviceType} returns a paginated list of devices your API key can access in the current environment. Each entry carries its last known state. Filter by userId to scope to one end-user; paginate via limit (1-50, default 10) and offset (default 0). The list view uses cached state; for fresh data on a single device, hit the device-id endpoint with ?expedite=true.

Sandbox versus live

Sandbox runs the same code paths, the same cache rules, and the same response shapes as live. Sandbox simulators return deterministic data with realistic timing. The only divergence is at the OEM-call layer; everything above (auth, access control, cache, response shape) is identical. See auth and environments for environment routing.

Frequently asked questions

How fresh is the data returned by GET /battery/?

By default, up to 15 minutes old. The API serves device state from a cache with a 15-minute TTL. On cache hit, the response is served instantly. On miss, the platform calls the OEM live, normalises the response, and warms the cache. For sub-15-minute freshness, append ?expedite=true and the cache TTL drops to 1 minute. The metadata.source field on the response names the tier (cache, live, or fallback).

What is the difference between live data and cached data?

Live data is fetched from the OEM API on the request thread; cached data is served from a previous fetch. Both indicate sync.available: true because the data is authoritative. Fallback data, served from the last-known stored state when a live call fails with a transient error, indicates sync.available: false because it is not a fresh fetch. The caller can distinguish all three via metadata.source.

How do I detect when a device is offline?

The platform classifies OEM errors as transient or permanent. DEVICE_OFFLINE is permanent and returns to the caller without falling back to stale data; the response has the standard error envelope and a 4xx-class status. Transient errors (NETWORK_ERROR, RATE_LIMITED, SERVICE_UNAVAILABLE, TIMEOUT) fall back to last-known and set sync.available: false.

Why does a fresh push not invalidate the cache?

Cache invalidation is purely TTL-based. Push completions and state changes do not invalidate the cache. Webhooks deliver state-change notifications without depending on cache freshness; reads see the world via the cache window. After a push completes, do not poll the device immediately expecting fresh state. Subscribe to push.completed instead, or call with ?expedite=true when freshness matters more than the OEM rate limit.

Why does GET /battery (the list endpoint) not show fresh data?

Listing devices returns up to 50 entries paginated; calling the OEM API for each on every list request would multiply per-OEM load by the number of devices. The list view uses cached state. For fresh data on a single device, fetch the device-id endpoint with ?expedite=true. The list is shaped for dashboards and inventories; the per-device read is shaped for control loops.

Capabilities

The commands and settings objects on the response.

Webhooks

State-change notifications without polling.

Auth and Environments

Sandbox versus live, environment routing.

Error Envelope

DEVICE_NOT_FOUND, transient versus permanent.
For a read-then-push walkthrough, see the cookbook: schedule an overnight charge.