# Create Message (POST)

Send a push notification to your devices.

## Endpoint

```
POST https://api.pocketalert.app/v1/messages
```

## Authentication

> **Info: Required**
>
> Include one of these headers in your request:
> - `Token: <your-api-key>` — Get it from [API Keys](https://pocketalert.app/account/apikeys)
> - `Authorization: Bearer <jwt-token>` — From [Login](https://info.pocketalert.app/api/user/login.md)

## Request

### Headers

| Header | Required | Description |
|--------|:--------:|-------------|
| `Content-Type` | ✅ | `application/json` |
| `Token` | ✅ | Your API key |
| `Authorization` | ✅ | `Bearer <jwt-token>` |

<small>* One of `Token` or `Authorization` is required</small>

### Body Parameters

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `title` | string | ✅ | Notification title |
| `message` | string | ✅ | Notification body text |
| `application_id` | string | ❌ | Application TID to categorize the message |
| `device_id` | string | ❌ | Specific device TID, omit for all devices |
| `level` | string \| int | ❌ | Priority level controlling how the push is delivered. Accepts a name or an int (`-2`..`2`). `priority` is accepted as a synonym. Defaults to the application's default level, then `default`. |
| `actions` | array | ❌ | Up to 3 action buttons shown on the notification. See [Action buttons](#action-buttons). |
| `send_at` | string \| int | ❌ | Deliver at a specific time instead of immediately. RFC3339, Unix timestamp, or `YYYY-MM-DD HH:MM`. See [Scheduled delivery](#scheduled-delivery). |
| `delay` | string | ❌ | Deliver after a relative delay — `30m`, `2h`, or bare seconds. Mutually exclusive with `send_at`. |

#### Action buttons

Each notification can carry up to **3** action buttons. `actions` is an array of objects:

| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| `type` | string | ✅ | `view` (open a URL), `http` (fire an HTTP request), or `copy` (copy text to clipboard) |
| `label` | string | ✅ | Button text. Shown on Android; on iOS a generic label per type is shown (Open / Run / Copy) |
| `value` | string | ✅ | Depends on `type` — see below |

`value` by type:

- **`view`** — the URL to open, e.g. `"https://pocketalert.app"`.
- **`http`** — a JSON string describing the request: `{"url":"https://…","method":"POST","headers":{…},"body":"…"}`. Only `url` is required; `method` defaults to `GET`. Fires silently in the background without opening the app.
- **`copy`** — the literal text placed on the clipboard.

> **Tip: Human-in-the-loop**
>
> Two `http` actions (e.g. **Approve** / **Decline**) make a one-tap control panel — ideal for approving LLM-agent actions straight from the notification.

See [Action buttons](https://info.pocketalert.app/features/action-buttons.md) for limits, validation and webhook templates.

#### Priority levels

| Level | Aliases | Behavior |
|-------|---------|----------|
| `silent` | `min`, `-2` | Delivered to the tray only — no sound or vibration |
| `low` | `-1` | Quiet, no interruption |
| `default` | `normal`, `0` (or omitted) | Standard banner + sound |
| `high` | `1` | Time-sensitive — breaks through Focus / scheduled summary |
| `critical` | `max`, `urgent`, `2` | Wakes the device through the silent switch and Do Not Disturb |

> **Warning**
>
> `critical` is a paid-plan feature. On free plans it is automatically downgraded to `high`. Invalid values (unknown name or out-of-range int) return `400`.

See [Priority levels](https://info.pocketalert.app/features/priority-levels.md) for how each level behaves on iOS and Android.

#### Scheduled delivery

Add `send_at` or `delay` — never both — and the message is stored encrypted until its delivery time instead of being sent immediately.

| Parameter | Accepts | Examples |
|-----------|---------|----------|
| `send_at` | RFC3339, Unix timestamp, or `YYYY-MM-DD HH:MM` | `2026-08-15T09:00:00Z`, `1786867200` |
| `delay` | Go-style duration or bare seconds | `30m`, `2h`, `3600` |

A time without an offset is read in your account timezone. The horizon is 30 days, and a past `send_at` returns `400` (with 60 seconds of tolerance for clock skew).

Scheduled requests return `201` with `"scheduled": true`, `status`, `deliver_at` and `deliver_at_utc` instead of a delivered message. List them with `GET /v1/messages/scheduled` and cancel with `DELETE /v1/messages/scheduled/{tid}` — see [Scheduled delivery](https://info.pocketalert.app/features/scheduled-delivery.md).

> **Warning**
>
> Scheduled delivery is a paid-plan feature. Free-plan requests using `send_at` or `delay` return `403`.

### Example Request

**cURL**

```bash
curl -X POST "https://api.pocketalert.app/v1/messages" \
  -H "Token: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Server Alert",
    "message": "CPU usage exceeded 90%",
    "application_id": "qm47b9pzxzxg",
    "level": "critical",
    "actions": [
      { "type": "view", "label": "Dashboard", "value": "https://status.example.com" },
      { "type": "http", "label": "Restart", "value": "{\"url\":\"https://ops.example.com/restart\",\"method\":\"POST\"}" },
      { "type": "copy", "label": "Copy ID", "value": "incident-4821" }
    ]
  }'
```

**JavaScript**

```javascript
const response = await fetch('https://api.pocketalert.app/v1/messages', {
  method: 'POST',
  headers: {
    'Token': 'your-api-key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Server Alert',
    message: 'CPU usage exceeded 90%',
    application_id: 'qm47b9pzxzxg'
  })
});

const data = await response.json();
console.log(data);
```

**Python**

```python
import requests

response = requests.post(
    'https://api.pocketalert.app/v1/messages',
    headers={
        'Token': 'your-api-key',
        'Content-Type': 'application/json'
    },
    json={
        'title': 'Server Alert',
        'message': 'CPU usage exceeded 90%',
        'application_id': 'qm47b9pzxzxg'
    }
)

print(response.json())
```

**PHP**

```php
$response = Http::withHeaders([
    'Token' => 'your-api-key',
])->post('https://api.pocketalert.app/v1/messages', [
    'title' => 'Server Alert',
    'message' => 'CPU usage exceeded 90%',
    'application_id' => 'qm47b9pzxzxg',
]);

return $response->json();
```

## Response

### Success Response

> **Tip: 201 Created**
>
> Message created successfully

| Field | Type | Description |
|-------|------|-------------|
| `tid` | string | Unique message identifier |
| `title` | string | Message title |
| `message` | string | Message body |
| `application` | string | Application name (if specified) |
| `device` | string | Target device name |
| `actions` | array | Action buttons echoed back (only when provided) |
| `created_at` | string | Creation timestamp |

```json
{
  "tid": "jb4xw9elz28g",
  "title": "Server Alert",
  "message": "CPU usage exceeded 90%",
  "application": "Monitoring",
  "device": "iPhone",
  "actions": [
    { "type": "view", "label": "Dashboard", "value": "https://status.example.com" }
  ],
  "created_at": "18.01.2026 15:35:35"
}
```

### Quota Headers

Responses carry today's [daily limit](https://info.pocketalert.app/features/daily-limits.md) usage (omitted on unlimited plans):

| Header | Description |
|--------|-------------|
| `X-PA-Daily-Limit` | Messages allowed per day on your plan |
| `X-PA-Daily-Remaining` | Messages left today, after this one |
| `X-PA-Daily-Reset` | Unix timestamp (seconds) of the next reset, 00:00 UTC |

### Error Responses

| Status | Description |
|:------:|-------------|
| `401` | Unauthorized — Invalid or missing token |
| `422` | Validation Error — Missing required fields |
| `429` | Daily limit reached — the message was not stored or sent. Wait for `Retry-After` seconds or upgrade. See [Daily message limits](https://info.pocketalert.app/features/daily-limits.md). |

```json
{
  "error": "Validation failed",
  "details": {
    "title": ["The title field is required"]
  }
}
```

#### Daily limit reached

When today's quota is used up, the message is rejected — not queued — until 00:00 UTC. `Retry-After` holds the seconds left until the reset.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 30142
X-PA-Daily-Limit: 50
X-PA-Daily-Remaining: 0
X-PA-Daily-Reset: 1790035200
```

```json
{
  "error": "Daily message limit reached (50/50). New messages are rejected until 00:00 UTC. Upgrade your plan to raise the limit.",
  "limit": 50,
  "used": 50,
  "resets_at": "2026-09-22T00:00:00Z",
  "upgrade_url": "https://pocketalert.app/pricing"
}
```

Deleting messages does not free up quota. Check today's usage with [`GET /v1/messages/usage`](https://info.pocketalert.app/api/messages/usage.md).
