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

# Conflicts

> What happens when you push to a device that already has an active action, and how to resolve it.

A device runs one action at a time. Use `onConflict` to decide what happens when you push while another action still holds the device.

## What counts as a conflict

An action on the same device that is `scheduled` or `acknowledged`, or a windowed action whose window is still running, even if it's `completed`. Time windows aren't compared, so two scheduled actions with separate windows still conflict. `cancelled` actions, and finished actions with no window still running, never conflict.

## Strategies

| `onConflict` | Behaviour |
| - | - |
| Omitted | Returns `409 CONFLICT`. Nothing changes. |
| `cancel_and_replace` | Cancels the existing action and accepts yours. |
| `queue_after` | Accepts yours and starts it when the existing action's `end` is reached. |

```bash theme={null}
curl -X POST https://api.amps.ai/battery/device_abc123 \
  -H "x-api-key: $AMPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": { "command": "discharge", "parameters": { "target": { "value": 30, "unit": "percent" } } },
    "onConflict": "cancel_and_replace"
  }'
```

A replaced `scheduled` action moves to `cancelled`. A replaced window that is already running keeps its state, and your action takes over the device.

## Supported strategies

Each device lists the strategies it supports in `conflictStrategies` on its read response.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "device_abc123",
    "conflictStrategies": ["cancel_and_replace", "queue_after"]
  }
}
```

Sending a strategy the device doesn't list returns `422 DEVICE_NOT_CAPABLE` with `requestedStrategy` and `supportedStrategies` in `details`, even when there's no conflict. Many devices list `cancel_and_replace` only.

## The 409 response

```json theme={null}
{
  "success": false,
  "error": {
    "code": "CONFLICT",
    "message": "The request conflicts with the current resource state.",
    "details": {
      "reason": "no_strategy_supplied",
      "conflictingActionIds": ["action_BqZ9oVoc6f"],
      "strategies": ["cancel_and_replace", "queue_after"]
    }
  }
}
```

`details.strategies` lists what will resolve it. Resend with one of them. `conflictingActionIds` is always an array.

| `code` | `details.reason` | Cause | What to do |
| - | - | - | - |
| `CONFLICT` | `no_strategy_supplied` | You didn't send `onConflict`. | Resend with a strategy from `details.strategies`. |
| `CONFLICT` | `conflicting_action_not_windowed` | `queue_after`, but the existing action has no `end`. | Use `cancel_and_replace`. |
| `CONFLICT_IN_EXECUTION` | `conflicting_action_in_progress` | The existing action is `acknowledged`, or its window is held in the device's own schedule. It can't be interrupted. | Wait for it to finish, then resend. |

## How queue\_after works

The existing action must have an `end`. Your action is created as `scheduled` and starts at that `end`. If your `start` is already later, it's kept.

A windowed push can't be moved, because its times are fixed. If the existing action ends after your window opens, you get `422 INVALID_TIME_WINDOW` with `details.reason: "queue_after_overlaps_window"`. Use `cancel_and_replace` or pick a later window. You also can't queue behind an `auto.*` command, because its `end` isn't a time the device is busy until. That returns `422 DEVICE_NOT_CAPABLE`.

## Conflicts on the device

Some conflicts come from the device rather than from Amps. These are also `409`s.

| Code | Cause |
| - | - |
| `SCHEDULER_ACTIVE` | The device has its own schedule, set outside Amps, that blocks the command. |
| `SCHEDULER_FULL` | The device's schedule has no free slots. |
| `MODE_OVERRIDDEN` | The device is in a state that overrides the command. |
| `VPP_LOCKED` | Another control programme has locked the device. |

`SCHEDULER_ACTIVE` can be returned when you submit a windowed battery push that overlaps the device's own schedule. It can also arrive later as the `errorCode` on a failed action, because the device's schedule can change before your action runs. The other three usually arrive on the failed action.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "SCHEDULER_ACTIVE",
    "message": "A schedule is currently active on the device and must be cleared first.",
    "details": {
      "reason": "foreign_scheduler_window_overlap",
      "recoveryStrategies": ["cancel_and_replace", "manual_clear_via_device_app"]
    }
  }
}
```

If `recoveryStrategies` includes `cancel_and_replace`, resend with it to overwrite the overlapping entries on the device. Otherwise, ask the end user to clear the schedule in the manufacturer's app.

## Next steps

<CardGroup cols={2}>
  <Card title="Cancelling" icon="xmark" href="/guides/push/cancelling">
    Cancel a scheduled action by ID.
  </Card>

  <Card title="Scheduling" icon="calendar" href="/guides/scheduling">
    Set `start` and `end` on a push.
  </Card>

  <Card title="Error codes" icon="list" href="/guides/error-handling/error-codes">
    Every code and its status.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.