Skip to main content
When a push, pull, or settings request cannot be honoured, the API returns a stable canonical code, a fixed HTTP status, and a customer-safe message. Most codes on this page name a rejection from the device manufacturer; a smaller group at the end name a rejection from the platform itself. Every message is API-owned, so the manufacturer’s raw text is never surfaced and the wording is the same whether the rejection comes from one manufacturer or another. Branch on error.code, not on message, and you will not see drift. The envelope is the standard one. The code rides at error.code, the status is the HTTP status, the message at error.message is the line below, and error.details carries any structured context the platform forwarded from the device’s response.

Authentication and account

These codes appear when the manufacturer rejects the stored credentials for the linked account. Re-link the device through Link UI to capture a fresh credential.

Device discovery

These codes name a problem locating the device on the manufacturer’s side.

Command and parameter

These codes name a request the manufacturer would not run. The request body needs to change, not the timing.

State conflict

These codes are 409s. The request is well-formed but the device is in a state that overrides or blocks it. Most are resolvable with onConflict: cancel_and_replace on the next push.

Transport and capacity

These codes are transient. The device manufacturer is reachable but is briefly unable to accept the request. Retry with exponential backoff.

Platform

These codes are raised by the platform rather than forwarded from the manufacturer, though the fault behind one may still be the manufacturer’s. Most appear only on failed action records, delivered as the errorCode on a push.failed webhook and readable from GET /actions/{actionId}. OEM_CIRCUIT_OPEN can also come back live on a push response when the platform has stopped calling the manufacturer.

What came back

INVALID_OEM_RESPONSE carries a details.fields array naming each reading that failed and why, so you can see whether the manufacturer sent something unrecognised or stopped sending it at all. received is present only when there was a value, and is truncated.
The message is identical for every occurrence, so match on code and read details.fields for the specifics. Readings the manufacturer sends that are not part of the canonical device state never cause this error. They are simply not read.

When to retry

The platform never retries push commands on the manufacturer’s behalf. A command that runs minutes later is usually worse than one that fails, so the caller decides whether to resubmit, with what, and when.

Frequently asked questions

Why is the error message the same regardless of which manufacturer rejected the request?

The error message is API-owned. The platform translates the manufacturer’s response into one of the canonical codes on this page, and the message is fixed per code. This keeps customer-facing prose stable and avoids leaking manufacturer-internal language (codes, hostnames, product names) into the response. The manufacturer’s original text is retained in platform logs for diagnostics and is tied to the response through meta.requestId.

How do I tell a transient failure apart from a fatal one?

By the code, not by the HTTP status alone. Transport codes (NETWORK_ERROR, RATE_LIMITED, SERVICE_UNAVAILABLE, TIMEOUT) and DEVICE_OFFLINE are retryable. Auth and command codes are not retryable as-is: the credential needs refreshing or the request needs changing. State-conflict codes are resolvable with onConflict or by clearing the controlling state on the device. Treat UNKNOWN_ERROR as an unclassified manufacturer response: retry once and escalate if it persists.

What lives in error.details?

Structured context the platform forwarded from the manufacturer’s response, plus any platform-side context relevant to the rejection. For SCHEDULER_ACTIVE on a windowed push, that includes reason, description, overlappingGroups, existingGroupCount, recoveryStrategies, and detectedAt. For RATE_LIMITED, it may include a hint at the manufacturer’s reported retry window. For command codes, it typically carries the offending parameter and the device’s supported subset. The details shape is stable per code, so an integration can branch on it.

What if I receive a code that is not on this page?

The platform routes new manufacturer codes through UNKNOWN_ERROR until they have a stable canonical mapping, so callers never see a raw manufacturer code. If you see UNKNOWN_ERROR against a request that should have succeeded, surface meta.requestId and the path and contact support; that pair lets the platform team trace the request and decide whether the response deserves its own code.

Error envelope

The full code taxonomy, including request-validation and platform codes.

Conflict resolution

SCHEDULER_ACTIVE and onConflict in depth.

Handle a 409 conflict

Worked recovery patterns in code.

Battery cheat sheet

The canonical battery vocabulary in one page.