# Actions & Execution

> Start and stop operations executed against cloud resources — triggered by schedules or by manual API calls — with idempotent retries, structured state history, and bulk dispatch.

Source: https://zop.dev/developer-docs/operations/actions

---

Actions are start/stop operations executed against cloud resources — either triggered automatically by schedules or manually through the API.

## How Execution Works

1. An action intent is created (by scheduler or manual API call)
2. The intent is queued in Redis for processing
3. The executor picks up the intent and calls the cloud provider API
4. The result (success/failure) is recorded as an execution log
5. State transitions are tracked in the state history

**Info**

All start/stop operations are idempotent. Starting an already-running resource or stopping an already-stopped resource will succeed without error. This makes retries safe.

## Action Statuses

| Status | Description |
|---|---|
| `pending` | Action is queued, waiting to be processed |
| `running` | Action is being executed against the cloud provider |
| `succeeded` | Action completed successfully |
| `failed` | Action failed — check errorMessage for details |

## Manual Action

Trigger a start or stop action on a specific resource outside of any schedule.

### POST /actions

Submit a manual start or stop action.

```bash title="Request"
curl -X POST https://zopnight.com/api/actions \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "stop",
    "resourceUID": "i-0abc123def456",
    "provider": "aws",
    "resourceType": "aws-ec2",
    "cloudAccountID": "ca_abc123",
    "region": "us-east-1"
  }'
```

```json title="Response"
{
  "data": {
    "actionID": "act_xyz789",
    "status": "queued"
  }
}
```

## Bulk Action

Start or stop all resources in a resource group with a single request. Resources are processed according to their execution tier and order.

### POST /actions/bulk

Submit a bulk action for a resource group.

```bash title="Request"
curl -X POST https://zopnight.com/api/actions/bulk \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "start",
    "groupID": "grp_abc123"
  }'
```

```json title="Response"
{
  "data": {
    "bulkActionID": "bulk_xyz789",
    "status": "queued",
    "total": 4
  }
}
```

## Get Action Status

### GET /actions/{actionID}

Check the status of a submitted action.

```json title="Response"
{
  "data": {
    "id": "act_xyz789",
    "resourceUID": "i-0abc123def456",
    "provider": "aws",
    "cloudAccountID": "ca_abc123",
    "region": "us-east-1",
    "action": "stop",
    "reason": "manual",
    "status": "succeeded",
    "startedAt": "2025-01-20T10:00:05Z",
    "completedAt": "2025-01-20T10:00:12Z",
    "createdAt": "2025-01-20T10:00:00Z"
  }
}
```

## Get Bulk Action Status

### GET /actions/bulk/{bulkActionID}

Check the status of a bulk action.

```json title="Response"
{
  "data": {
    "id": "bulk_xyz789",
    "groupID": "grp_abc123",
    "type": "start",
    "status": "completed",
    "total": 4,
    "completed": 4,
    "failed": 0,
    "createdAt": "2025-01-20T10:00:00Z"
  }
}
```

## Latest Bulk Action

Returns the most recent bulk action for the org. Useful for "is there still something running?" indicators that don't already have a bulk ID to look up.

### GET /actions/bulk/latest

Get the most recent bulk action submitted by this org.

```json title="Response"
{
  "data": {
    "id": "bulk_xyz789",
    "groupID": "grp_abc123",
    "type": "start",
    "status": "running",
    "total": 12,
    "completed": 7,
    "failed": 0,
    "createdAt": "2026-04-29T10:00:00Z"
  }
}
```

## State History

Query the state transition history for resources. This provides an audit trail of when resources were started or stopped and why.

### GET /state-history

Get resource state transition history with pagination.

#### Query Parameters

| Parameter | Description |
|---|---|
| `resource_uid` | Filter by specific resource |
| `changed_by_type` | Filter by source: schedule, manual, bulk, override, override_expired |
| `page` | Page number |
| `limit` | Items per page |

```json title="Response"
{
  "data": {
    "items": [
      {
        "id": "sh_001",
        "resourceUid": "i-0abc123def456",
        "state": "stopped",
        "changedAt": "2025-01-20T18:00:00Z",
        "changedBy": "schedule:nightly-shutdown:cron_xyz",
        "scheduleId": "sch_abc123",
        "executionState": { "step": "stop-instance", "status": "succeeded" }
      },
      {
        "id": "sh_002",
        "resourceUid": "i-0abc123def456",
        "state": "failed",
        "changedAt": "2025-01-21T08:00:00Z",
        "changedBy": "schedule:nightly-shutdown:cron_xyz",
        "scheduleId": "sch_abc123",
        "errorMessage": "InsufficientCapacity: m6i.large not available in us-east-1a"
      }
    ],
    "total": 2,
    "page": 1,
    "limit": 50
  }
}
```

**Tip**

The `changedBy` field is a structured colon-delimited string:

- `schedule:<name>:<cronId>` — triggered by a schedule
- `manual:<email>` — manually triggered via API or UI
- `bulk:<email>:<bulkId>` — bulk start/stop action
- `override:<action>` — triggered by an active override
- `override_expired:<action>` — triggered when an override expired

`errorMessage` is populated only on failures; `executionState` is a free-form map of step-level status that the executor records during multi-step transitions (e.g. for k8s namespaces or container apps).
