# Create an alert plan — API reference

> Creates an alert plan with ordered notification steps and optional repeats.

Source: https://upfour.io/docs/api/escalation-policies/create-an-alert-plan

[Developers](https://upfour.io/docs/api.md)/[Alert plans (escalation policies)](https://upfour.io/docs/api/escalation-policies.md)

Creates an alert plan with ordered notification steps and optional repeats.

POST https://app.upfour.io/v1/escalation-policies

scope escalation_policy.write stable [Idempotency-Key supported](https://upfour.io/docs/api/idempotency.md) returns 201

## Body

`application/json` required 5 fields

namestring (required)

Unique within the company.

In the app: **Alert plan name**

stepsCreateEscalationStepInput[] (required)

The steps in order, at least one. On update they replace the current steps.

Nested fields 3 fields

| Field | Type | Description |
| --- | --- | --- |
| `targets` optional | string[] | What the step notifies, at least one entry: alert channel IDs, or on-call rotations written as oncall:<schedule ID>. A rotation is resolved when the step fires, to the person on call at that moment (an override wins) and their own alert channels. Channels and rotations must belong to the company; a client plan may also use that client's. In the app: **Who should we alert?** |
| `urgency` optional | string one of`high` `low` `sms` `voice` | Which channel types the step uses: high every type, sms only SMS, voice only voice, low only email channels. Empty means high. In the app: **How urgent is this alert?** |
| `wait_seconds` optional | integer | Seconds to wait after the previous step; from the second step on at least 60\. In the app: **Wait for** |

All other fields3 fields

client_iduuid · nullable

descriptionstring

repeat_countinteger

How often the whole plan repeats after the last step, 0 to 8\. A value outside that range is stored as 0.

In the app: **Reminders after the last step**

## Returns

A successful request returns `201` with application/json.

Response fields 1 top-level fields

| Field | Type | Description |
| --- | --- | --- |
| `data` | EscalationPolicy | |
| EscalationPolicy · 18 fields FieldTypeDescription `created_at` string (date-time) `description` string `id` uuid `monitor_count` integer Monitors currently assigned to the plan. Only filled in the list. `name` string `organization_id` uuid `repeat_count` integer `repeat_interval_seconds` integer Pause between repeats of the plan; fixed at 15 minutes. `steps` EscalationStep[]  · nullable Each item · 7 fields FieldTypeDescription `created_at` string (date-time) `escalation_policy_id` uuid `id` uuid `position` integer Zero-based position within the plan. `targets` string[]  · nullable Alert channel IDs and on-call rotations written as oncall:<schedule ID>. `urgency` string one of`high` `low` `sms` `voice` `wait_seconds` integer Seconds to wait after the previous step. `updated_at` string (date-time) `client_id` uuid | | |

## Errors

Only the errors this operation can return. Every error uses the [same envelope](https://upfour.io/docs/api/responses.md).

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | [bad_request](https://upfour.io/docs/api/errors#code-bad_request) | The request is malformed or fails validation. |
| 402 | [billing_action_unavailable](https://upfour.io/docs/api/errors#code-billing_action_unavailable) | The Company's billing state does not allow this action. **When:** A metered or limited action while the billing state, such as paused or without credit, blocks it; details holds the action and billing_state. **Fix:** Top up the credit or change the plan, then retry. |
| 403 | [permission_denied](https://upfour.io/docs/api/errors#code-permission_denied) | The credentials do not permit this action. |
| 503 | [quota_gate_unavailable](https://upfour.io/docs/api/errors#code-quota_gate_unavailable) | The limit check itself failed, so the action was not carried out. **When:** The quota service could not decide while the request ran. **Fix:** Retry in a moment; nothing was changed. |
| 403 / 429 | [quota_limit_reached](https://upfour.io/docs/api/errors#code-quota_limit_reached) | A usage or inventory limit of the Company was reached. **When:** Counters and concurrency limits answer 429 with Retry-After, inventory and capacity limits answer 403; details holds purpose, limit and window. **Fix:** On 429 wait Retry-After seconds; on 403 free something up, request a higher limit where details.raise_request is true, or buy an extension. |

Every call can also return `unauthorized` (401), `rate_limit_exceeded` (429) or `internal_error` (500 / 502). See [Responses and errors](https://upfour.io/docs/api/responses.md).

[PreviousList alert plans](https://upfour.io/docs/api/escalation-policies/list-alert-plans.md) [NextGet an alert plan](https://upfour.io/docs/api/escalation-policies/get-an-alert-plan.md)

## Request

### cURL

```bash
curl -s -X POST "https://app.upfour.io/v1/escalation-policies" \
  -H "Authorization: Bearer $UP4_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Email the on-call mailbox, repeat once.",
    "name": "Default escalation",
    "repeat_count": 1,
    "steps": [
      {
        "targets": [
          "00000000-0000-4000-8000-000000000001"
        ],
        "urgency": "high",
        "wait_seconds": 0
      }
    ]
  }'
```

### JavaScript

```javascript
const res = await fetch("https://app.upfour.io/v1/escalation-policies", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.UP4_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    description: "Email the on-call mailbox, repeat once.",
    name: "Default escalation",
    repeat_count: 1,
    steps: [
      {
        targets: ["00000000-0000-4000-8000-000000000001"],
        urgency: "high",
        wait_seconds: 0,
      },
    ],
  }),
});
console.log(res.status, await res.json());
```

### Python

```python
import os
import requests

res = requests.post(
    "https://app.upfour.io/v1/escalation-policies",
    headers={"Authorization": f"Bearer {os.environ['UP4_API_KEY']}"},
    json={
        "description": "Email the on-call mailbox, repeat once.",
        "name": "Default escalation",
        "repeat_count": 1,
        "steps": [
            {
                "targets": ["00000000-0000-4000-8000-000000000001"],
                "urgency": "high",
                "wait_seconds": 0,
            },
        ],
    },
)
print(res.status_code, res.json())
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	payload := strings.NewReader(`{
  "description": "Email the on-call mailbox, repeat once.",
  "name": "Default escalation",
  "repeat_count": 1,
  "steps": [
    {
      "targets": [
        "00000000-0000-4000-8000-000000000001"
      ],
      "urgency": "high",
      "wait_seconds": 0
    }
  ]
}`)
	req, _ := http.NewRequest("POST", "https://app.upfour.io/v1/escalation-policies", payload)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("UP4_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(res.StatusCode, string(out))
}
```

### PHP

```php
<?php
$payload = <<<'JSON'
{
  "description": "Email the on-call mailbox, repeat once.",
  "name": "Default escalation",
  "repeat_count": 1,
  "steps": [
    {
      "targets": [
        "00000000-0000-4000-8000-000000000001"
      ],
      "urgency": "high",
      "wait_seconds": 0
    }
  ]
}
JSON;

$ch = curl_init("https://app.upfour.io/v1/escalation-policies");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("UP4_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), " ", $response, "\n";
curl_close($ch);
```

## Response

### 201

```json
{
  "data": {
    "created_at": "2026-10-27T09:00:00Z",
    "description": "Email the on-call mailbox, repeat once.",
    "id": "00000000-0000-4000-8000-000000000002",
    "monitor_count": 0,
    "name": "Default escalation",
    "organization_id": "00000000-0000-4000-8000-000000000003",
    "repeat_count": 1,
    "repeat_interval_seconds": 900,
    "steps": [
      {
        "created_at": "2026-10-27T09:00:00Z",
        "escalation_policy_id": "00000000-0000-4000-8000-000000000002",
        "id": "00000000-0000-4000-8000-000000000004",
        "position": 0,
        "targets": [
          "00000000-0000-4000-8000-000000000001"
        ],
        "urgency": "high",
        "wait_seconds": 0
      }
    ],
    "updated_at": "2026-10-27T09:00:00Z"
  }
}
```

### 400

```json
{
  "error": {
    "code": "bad_request",
    "message": "The request is malformed or fails validation.",
    "request_id": "req_…"
  }
}
```

### 402

```json
{
  "error": {
    "code": "billing_action_unavailable",
    "message": "The Company's billing state does not allow this action.",
    "request_id": "req_…"
  }
}
```

### 403

```json
{
  "error": {
    "code": "permission_denied",
    "message": "The credentials do not permit this action.",
    "request_id": "req_…"
  }
}
```

### 429

```json
{
  "error": {
    "code": "quota_limit_reached",
    "message": "A usage or inventory limit of the Company was reached.",
    "request_id": "req_…"
  }
}
```

### 503

```json
{
  "error": {
    "code": "quota_gate_unavailable",
    "message": "The limit check itself failed, so the action was not carried out.",
    "request_id": "req_…"
  }
}
```
