> ## 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.

# Hold an HVAC Device at a Fixed Mode

> Override the native schedule by pushing an operation mode (heat, cool, auto), then release it with auto.schedule. Request, lifecycle, and release.

## Overview

To hold an HVAC device at a fixed mode, push an operation mode: `heat`, `cool`, or `auto.maintain`. The device's own schedule is ignored until you release the hold. Use it for a manual override, a guest in the spare room, a skipped vacation week, or to drive the thermostat from your own logic.

The companion command, `auto.schedule`, hands control back to the device's native program. Together they form an open-and-close pair. The body is the canonical action envelope every device type shares: an `action` with a `command` and its `parameters`. See [canonical actions](/concepts/canonical-actions).

<Callout icon="clock" color="#ED6D2C">
  **Coming soon.** Live control for HVAC devices. The sandbox environment runs the full thermostat command surface end to end, so the walkthrough below works against a sandbox device today. A live HVAC push returns 503 `NOT_YET_AVAILABLE` until the live path opens.
</Callout>

## Step 1: Set the hold

Push `auto.maintain` with a comfort band. `heatSetpoint` and `coolSetpoint` are `{value, unit}` quantities in degrees Celsius, each bounded 10 to 35. The device heats below `heatSetpoint` and cools above `coolSetpoint`.

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -X POST https://api.amps.ai/hvac/device_hvac_001 \
      -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{
        "action": {
          "command": "auto.maintain",
          "parameters": {
            "heatSetpoint": { "value": 20, "unit": "celsius" },
            "coolSetpoint": { "value": 24, "unit": "celsius" }
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Node">
    ```javascript theme={null}
    await fetch("https://api.amps.ai/hvac/device_hvac_001", {
      method: "POST",
      headers: {
        "x-api-key": process.env.AMPS_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        action: {
          command: "auto.maintain",
          parameters: {
            heatSetpoint: { value: 20, unit: "celsius" },
            coolSetpoint: { value: 24, unit: "celsius" },
          },
        },
      }),
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os, requests

    requests.post(
        "https://api.amps.ai/hvac/device_hvac_001",
        headers={
            "x-api-key": os.environ["AMPS_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "action": {
                "command": "auto.maintain",
                "parameters": {
                    "heatSetpoint": {"value": 20, "unit": "celsius"},
                    "coolSetpoint": {"value": 24, "unit": "celsius"},
                },
            }
        },
    )
    ```
  </Tab>
</Tabs>

To pin a single temperature instead of a band, push `heat` or `cool` with a `target`. You get back `202 Accepted`, wrapped in the standard envelope. Because this is an immediate push, the action begins life in `acknowledged` rather than `scheduled`.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_hvac_004",
    "deviceId": "device_hvac_001",
    "deviceType": "hvac",
    "command": "auto.maintain",
    "parameters": {
      "heatSetpoint": { "value": 20, "unit": "celsius" },
      "coolSetpoint": { "value": 24, "unit": "celsius" }
    },
    "state": "acknowledged",
    "createdAt": "2026-05-08T10:00:00.000Z",
    "links": { "self": "/actions/act_hvac_004" }
  },
  "meta": { "requestId": "req_5iA9wJmZ", "environment": "sandbox", "timestamp": "2026-05-08T10:00:00.000Z", "latencyMs": 79 }
}
```

## Step 2: Confirm the hold applied

Poll the action to watch the lifecycle.

```bash theme={null}
curl -X GET https://api.amps.ai/actions/act_hvac_004 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx"
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_hvac_004",
    "deviceId": "device_hvac_001",
    "deviceType": "hvac",
    "command": "auto.maintain",
    "parameters": {
      "heatSetpoint": { "value": 20, "unit": "celsius" },
      "coolSetpoint": { "value": 24, "unit": "celsius" }
    },
    "state": "completed",
    "result": { "success": true, "message": "Mode applied" },
    "errorCode": null,
    "errorMessage": null,
    "createdAt": "2026-05-08T10:00:00.000Z",
    "updatedAt": "2026-05-08T10:00:02.000Z",
    "acknowledgedAt": "2026-05-08T10:00:01.000Z",
    "completedAt": "2026-05-08T10:00:02.000Z",
    "start": null,
    "end": null,
    "links": { "self": "/actions/act_hvac_004" }
  },
  "meta": { "requestId": "req_6jB0xKnA", "environment": "sandbox", "timestamp": "2026-05-08T10:00:03.000Z", "latencyMs": 11 }
}
```

The hold persists until you release it or push a different mode. `auto.schedule` is the canonical release.

## Step 3: Release the hold

When the override is no longer needed, hand control back to the device's schedule. `auto.schedule` is a command in the same envelope and carries no parameters.

```bash theme={null}
curl -X POST https://api.amps.ai/hvac/device_hvac_001 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action": { "command": "auto.schedule" }
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_hvac_005",
    "deviceId": "device_hvac_001",
    "deviceType": "hvac",
    "command": "auto.schedule",
    "parameters": null,
    "state": "acknowledged",
    "createdAt": "2026-05-08T11:00:00.000Z",
    "links": { "self": "/actions/act_hvac_005" }
  },
  "meta": { "requestId": "req_7kC1yLoB", "environment": "sandbox", "timestamp": "2026-05-08T11:00:01.000Z", "latencyMs": 14 }
}
```

A follow-up read confirms the device is back on its own schedule.

`auto.schedule` is immediate-only: its `execution` array is `["immediate"]`, so it takes neither `start` nor `end`. Releasing the schedule is something you do now, not something you defer. Supplying a `start` or `end` alongside it returns 422 `EXECUTION_NOT_SUPPORTED`.

## Adjusting the hold

Push another operation mode with new values to raise or lower the setpoints, or switch from a band to a single-temperature hold. The new write supersedes the previous one. No explicit cancel needed.

```bash theme={null}
curl -X POST https://api.amps.ai/hvac/device_hvac_001 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action": {
      "command": "heat",
      "parameters": { "target": { "value": 22, "unit": "celsius" } }
    }
  }'
```

If a previous push is still being applied, you get `409 CONFLICT` with the in-flight action ID. Add `onConflict: "cancel_and_replace"` to drop the in-flight action and run the new one. Thermostats declare only `cancel_and_replace`, so `queue_after` returns 422 `STRATEGY_NOT_SUPPORTED`. See the conflict cookbook page for resolution.

## Why this works

Every thermostat command maps to one underlying operation, so only one is active at a time. That is why pushing a new mode supersedes the old one, and why `onConflict` governs an in-flight write. `auto.schedule` is a mode like the rest, and pushing it supersedes whatever mode was held, which is how it relinquishes the hold. The whole flow is the canonical action model applied to a thermostat. The same read-pick-build-post loop drives a battery and an EV charger. See [canonical actions](/concepts/canonical-actions) and [capabilities](/concepts/capabilities).

## What next

<CardGroup cols={2}>
  <Card title="EV charger power cap" icon="bolt" href="/guides/cookbook/ev-charger-set-power">
    Set the charging-power ceiling as a device setting.
  </Card>

  <Card title="Subscribe to webhooks" icon="webhook" href="/guides/cookbook/subscribe-webhooks">
    Get push.completed events on mode transitions.
  </Card>

  <Card title="Handle conflicts" icon="triangle-alert" href="/guides/cookbook/handle-conflict">
    Resolve 409s when an HVAC write is already in flight.
  </Card>

  <Card title="Canonical actions" icon="book-open" href="/concepts/canonical-actions">
    Where HVAC commands sit in the canonical model.
  </Card>
</CardGroup>

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Hold an HVAC Device at a Fixed Mode",
"description": "Override the native schedule by pushing an operation mode (heat, cool, auto), then release it with auto.schedule. Request, lifecycle, and release.",
"step": [
{
  "@type": "HowToStep",
  "name": "Step 1: Set the hold",
  "position": 1
},
{
  "@type": "HowToStep",
  "name": "Step 2: Confirm the hold applied",
  "position": 2
},
{
  "@type": "HowToStep",
  "name": "Step 3: Release the hold",
  "position": 3
}
]
})}}
/>
