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

# Cancel a Scheduled Action

> Cancel an action two ways: drop a scheduled action by actionId, or push fresh with onConflict cancel_and_replace to replace one that has not been dispatched yet.

## Overview

You have two ways to cancel. Use the cancel endpoint to drop a scheduled action by ID; it only works while the action is in `scheduled` state. Or use `onConflict: "cancel_and_replace"` on a fresh push to cancel the colliding action and apply the new one in a single round-trip.

## Pattern A: Cancel a scheduled action by ID

### Step 1: Schedule an action

```bash theme={null}
curl -X POST https://api.amps.ai/battery/device_abc123 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action": {
      "command": "charge",
      "start": "2026-05-09T22:00:00",
      "end": "2026-05-10T05:00:00",
      "parameters": {
        "target": { "value": 100, "unit": "percent" }
      }
    }
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_pending_001",
    "deviceId": "device_abc123",
    "deviceType": "battery",
    "command": "charge",
    "parameters": { "target": { "value": 100, "unit": "percent" } },
    "state": "scheduled",
    "createdAt": "2026-05-08T08:00:00.000Z",
    "start": "2026-05-09T22:00:00.000Z",
    "end": "2026-05-10T05:00:00.000Z",
    "links": { "self": "/actions/act_pending_001" }
  },
  "meta": { "requestId": "req_5yQ9mZcP", "environment": "sandbox", "timestamp": "2026-05-08T08:00:00.000Z", "latencyMs": 81 }
}
```

### Step 2: Cancel before it fires

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

The response carries the cancelled action under `data` with `state: cancelled`. It will not fire.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_pending_001",
    "deviceId": "device_abc123",
    "deviceType": "battery",
    "command": "charge",
    "parameters": { "target": { "value": 100, "unit": "percent" } },
    "state": "cancelled",
    "result": null,
    "errorCode": null,
    "errorMessage": null,
    "createdAt": "2026-05-08T08:00:00.000Z",
    "updatedAt": "2026-05-08T11:32:00.000Z",
    "acknowledgedAt": null,
    "completedAt": "2026-05-08T11:32:00.000Z",
    "start": "2026-05-09T22:00:00.000Z",
    "end": "2026-05-10T05:00:00.000Z",
    "links": { "self": "/actions/act_pending_001" }
  },
  "meta": { "requestId": "req_6zR0nAdQ", "environment": "sandbox", "timestamp": "2026-05-08T11:32:00.000Z", "latencyMs": 17 }
}
```

### Cancellation only works in scheduled state

Once the action is in flight, cancel returns `409 ACTION_NOT_CANCELLABLE`.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ACTION_NOT_CANCELLABLE",
    "message": "Action in state 'acknowledged' cannot be cancelled"
  },
  "meta": {
    "requestId": "req_4nF8bMqE",
    "timestamp": "2026-05-08T11:32:00.000Z",
    "path": "/actions/act_inflight_002/cancel",
    "latencyMs": 8
  }
}
```

If the action is already in flight, wait for it to reach `completed` or `failed`.

## Pattern B: Cancel and replace in a single push

When the new intent is also a push, combine the cancellation into the new request. This avoids a race where the original fires between cancel and re-push.

```bash theme={null}
curl -X POST https://api.amps.ai/battery/device_abc123 \
  -H "x-api-key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action": {
      "command": "discharge",
      "start": "2026-05-09T22:00:00",
      "end": "2026-05-10T05:00:00",
      "parameters": {
        "target": { "value": 30, "unit": "percent" }
      }
    },
    "onConflict": "cancel_and_replace"
  }'
```

The colliding scheduled charge is cancelled and the new discharge is accepted in a single transaction.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "act_pending_002",
    "deviceId": "device_abc123",
    "deviceType": "battery",
    "command": "discharge",
    "parameters": { "target": { "value": 30, "unit": "percent" } },
    "state": "scheduled",
    "createdAt": "2026-05-08T11:32:00.000Z",
    "start": "2026-05-09T22:00:00.000Z",
    "end": "2026-05-10T05:00:00.000Z",
    "links": { "self": "/actions/act_pending_002" }
  },
  "meta": { "requestId": "req_7aS1oBeR", "environment": "sandbox", "timestamp": "2026-05-08T11:32:00.000Z", "latencyMs": 104 }
}
```

The cancelled action remains visible by ID. A follow-up `GET /actions/act_pending_001` returns `data.state: cancelled`.

### When cancel\_and\_replace cannot help

If the colliding action is already `acknowledged`, the cancel half of `cancel_and_replace` fails. You get `409 CONFLICT` with the in-flight action ID.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "CONFLICT_IN_EXECUTION",
    "message": "The conflicting action is already in progress and cannot be cancelled. Wait for it to complete or fail.",
    "details": {
      "conflictingActionIds": ["act_inflight_002"]
    }
  },
  "meta": {
    "requestId": "req_3dC6hOsT",
    "timestamp": "2026-05-08T11:32:00.000Z",
    "path": "/battery/device_abc123",
    "latencyMs": 18
  }
}
```

Poll the in-flight action. Once it reaches `completed` or `failed`, retry the push.

## What next

<CardGroup cols={2}>
  <Card title="Handle conflict 409s" icon="triangle-alert" href="/guides/cookbook/handle-conflict">
    Both onConflict strategies, plus SCHEDULER\_ACTIVE behaviours.
  </Card>

  <Card title="Schedule a charge for later" icon="moon" href="/guides/cookbook/schedule-charge-later">
    Recipe that creates the kind of action this page cancels.
  </Card>

  <Card title="Subscribe to webhooks" icon="webhook" href="/guides/cookbook/subscribe-webhooks">
    Detect terminal state transitions without polling.
  </Card>

  <Card title="Canonical actions" icon="book-open" href="/concepts/canonical-actions">
    The action lifecycle and where cancellation fits.
  </Card>
</CardGroup>

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Cancel a Scheduled Action",
"description": "Cancel an action two ways: drop a scheduled action by actionId, or push fresh with onConflict cancel_and_replace to replace one that has not been dispatched yet.",
"step": [
{
  "@type": "HowToStep",
  "name": "Overview",
  "position": 1
},
{
  "@type": "HowToStep",
  "name": "Pattern A: Cancel a scheduled action by ID",
  "position": 2
},
{
  "@type": "HowToStep",
  "name": "Pattern B: Cancel and replace in a single push",
  "position": 3
},
{
  "@type": "HowToStep",
  "name": "What next",
  "position": 4
}
]
})}}
/>
