# Send notification

> POST /api/v1/notifications sends a push to one audience of one app, now or at a set time.

URL: https://docs.xulo.io/api/send-notification

```http
POST https://api.xulo.io/api/v1/notifications
```

Sends a push to one app. Without `send_after` the send starts at once and finishes in the background. With `send_after` it sends when that time arrives. There is no approval step.

## Request

Headers:

| Header | Value |
| --- | --- |
| `Authorization` | `Key` followed by the app's REST API key. See [Authentication](https://docs.xulo.io/api/authentication). |
| `Content-Type` | `application/json` |
| `Idempotency-Key` | Optional. See [Idempotency](https://docs.xulo.io/api/idempotency). |

Body:

| Field | Type | Notes |
| --- | --- | --- |
| `app_id` | string | Required. The App ID. It must belong to the key's app. |
| `contents` | object | Required. The message per language code, such as `{"en": "Hello", "tr": "Merhaba"}`. Up to 2,000 characters. |
| `headings` | object | The title per language code. Up to 120 characters. |
| `included_segments` | array | One audience name. See [Targeting](https://docs.xulo.io/api/targeting). |
| `tag` | object | A tag to match. See [Targeting](https://docs.xulo.io/api/targeting). |
| `include_subscription_ids` | array | Up to 500 Subscription IDs. See [Targeting](https://docs.xulo.io/api/targeting). |
| `include_external_ids` | array | Up to 500 External IDs. See [Targeting](https://docs.xulo.io/api/targeting). |
| `send_after` | string | A future time. See [Scheduling](https://docs.xulo.io/api/scheduling). |
| `delayed_option` | string | `timezone`, to send at the same clock time on each device. See [Scheduling](https://docs.xulo.io/api/scheduling). |
| `url` | string | The link opened on tap: https, or an address your app handles such as `exampleapp://offer/1`. Up to 500 characters. `app_url` is accepted too. If both are sent, they must be the same. |
| `big_picture` | string | An https image address. `ios_attachments` with an `id` is accepted too. |
| `data` | object | A dictionary your app reads on tap. Values are sent as text. |
| `buttons` | array | At most one button, `{"id": "track", "text": "Track"}`. Id and text up to 40 characters each. |
| `bundle_id` | string | Optional. If present, it must be the app's bundle id. |
| `throttle_rate_per_minute` | integer | Optional, from 1 to 100,000. The most devices a minute a segment or tag send reaches. See [Throttling a send](#throttling-a-send). |
| `idempotency_key` | string | Optional. The same as the `Idempotency-Key` header. If both are sent, they must be the same. See [Idempotency](https://docs.xulo.io/api/idempotency). |

Name exactly one target: `included_segments`, `tag`, `include_subscription_ids` or `include_external_ids`. None, or more than one, returns 422.

The default language is `en` when `contents` has it, otherwise the first language in `contents`. Each device gets its own language when there is a match. See [Notifications](https://docs.xulo.io/concepts/notifications#languages).

## Example

**cURL**

```bash
curl https://api.xulo.io/api/v1/notifications \
  -H "Authorization: Key $XULO_REST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "YOUR_APP_ID",
    "headings": {"en": "Your order shipped"},
    "contents": {"en": "It arrives tomorrow.", "tr": "Yarın teslim edilecek."},
    "include_external_ids": ["USER_ID"],
    "url": "exampleapp://orders/1",
    "data": {"order": "1"}
  }'
```
**PHP**

```php
<?php

$payload = [
    'app_id' => 'YOUR_APP_ID',
    'headings' => ['en' => 'Your order shipped'],
    'contents' => ['en' => 'It arrives tomorrow.', 'tr' => 'Yarın teslim edilecek.'],
    'include_external_ids' => ['USER_ID'],
    'url' => 'exampleapp://orders/1',
    'data' => ['order' => '1'],
];

$curl = curl_init('https://api.xulo.io/api/v1/notifications');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Key '.getenv('XULO_REST_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);

$result = json_decode(curl_exec($curl), true);
```
**Laravel**

```php
use Illuminate\Support\Facades\Http;

$result = Http::withHeaders([
        'Authorization' => 'Key '.config('services.xulo.rest_key'),
    ])
    ->post('https://api.xulo.io/api/v1/notifications', [
        'app_id' => 'YOUR_APP_ID',
        'headings' => ['en' => 'Your order shipped'],
        'contents' => ['en' => 'It arrives tomorrow.', 'tr' => 'Yarın teslim edilecek.'],
        'include_external_ids' => ['USER_ID'],
        'url' => 'exampleapp://orders/1',
        'data' => ['order' => '1'],
    ])
    ->throw()
    ->json();
```
**Node**

```js
const response = await fetch("https://api.xulo.io/api/v1/notifications", {
  method: "POST",
  headers: {
    Authorization: `Key ${process.env.XULO_REST_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    app_id: "YOUR_APP_ID",
    headings: { en: "Your order shipped" },
    contents: { en: "It arrives tomorrow.", tr: "Yarın teslim edilecek." },
    include_external_ids: ["USER_ID"],
    url: "exampleapp://orders/1",
    data: { order: "1" },
  }),
});

const result = await response.json();
```
**Python**

```python
import os
import requests

response = requests.post(
    "https://api.xulo.io/api/v1/notifications",
    headers={"Authorization": f"Key {os.environ['XULO_REST_KEY']}"},
    json={
        "app_id": "YOUR_APP_ID",
        "headings": {"en": "Your order shipped"},
        "contents": {"en": "It arrives tomorrow.", "tr": "Yarın teslim edilecek."},
        "include_external_ids": ["USER_ID"],
        "url": "exampleapp://orders/1",
        "data": {"order": "1"},
    },
    timeout=10,
)
response.raise_for_status()
result = response.json()
```

In Laravel, `config('services.xulo.rest_key')` stands for wherever your app keeps the key.

## Response

Every call answers before Apple is called. The status code depends on the target.

### A segment or a tag

A send to `included_segments` or `tag` with someone in its audience returns `202 Accepted`. The send is recorded, and Xulo sends it in the background:

```json
{
  "id": 43,
  "status": "queued",
  "sent": 0,
  "failed": 0,
  "skipped": 0,
  "estimate": 1200,
  "message": "Queued for 1200 devices. It sends in the background."
}
```

Xulo splits the audience into chunks of 500 devices and sends them one after another. Read the progress with [Notification stats](https://docs.xulo.io/api/notification-stats): `status` moves from `queued` to `sending`, then to `approved`, or to `draft` if Apple accepted none of the devices.

One send reaches at most 1,000,000 devices. A larger audience returns 422 with "This send reaches 1,200,000 devices. One send reaches at most 1,000,000." and nothing is sent.

### A list of ids

A send to `include_subscription_ids` or `include_external_ids` returns `200`:

```json
{
  "id": 42,
  "status": "sending",
  "sent": 0,
  "failed": 0,
  "skipped": 0,
  "estimate": 1,
  "message": "Sending to 1 device."
}
```

### Fields

| Field | Meaning |
| --- | --- |
| `id` | The id of this send. Use it with [Notification stats](https://docs.xulo.io/api/notification-stats). |
| `status` | `queued`, `sending`, `approved`, `scheduled` or `draft`. See below. |
| `sent` | Devices Apple accepted when the call answered. `0` while the send is `queued` or `sending`. |
| `failed` | Devices Apple refused. |
| `skipped` | Devices not sent to, such as a Subscription ID that matched no subscribed install. |
| `estimate` | Devices in the audience. |
| `message` | A sentence that says what happened. |

| `status` | Meaning |
| --- | --- |
| `queued` | A segment or tag send, accepted with `202`. Xulo sends it in the background in chunks of 500. |
| `sending` | A list of ids, accepted. Xulo sends it in the background, 500 devices at a time, from a job that runs every minute. Read the counts with [Notification stats](https://docs.xulo.io/api/notification-stats). |
| `approved` | Handled now. With an empty audience, `sent` is `0` and the message says the send reached no one. |
| `scheduled` | Waiting for `send_after`. `sent` is `0`. A send with `delayed_option: "timezone"` stays `scheduled` while each time zone goes out in the background in chunks. It does not use the list job above. See [Scheduling](https://docs.xulo.io/api/scheduling). |
| `draft` | Not sent. The app has no Apple key or certificate, or Apple accepted none of the devices. The message says which. |

Check `status` and `sent`, not only the HTTP status. A call that reached nobody still returns `200`. A `queued` or `sending` call has not reached anyone yet.

## Throttling a send

`throttle_rate_per_minute` spreads a segment or tag send over time. With `"throttle_rate_per_minute": 1000`, the chunks are spaced so that about 1,000 devices get it each minute. Without it, the chunks go out one after another with no pause.

It applies to a segment or tag send made now or at a set time. A list of ids and a send with `delayed_option: "timezone"` do not use it. A value that is not a whole number from 1 to 100,000 returns 422.

## Retrying safely

Send an `Idempotency-Key` header, or `idempotency_key` in the body, and a retry of the same call does not send twice. A repeat within 24 hours returns the first response with the header `Idempotent-Replay: true`. A repeat while the first call is still being handled returns `409`. The same key with a different body returns 422. See [Idempotency](https://docs.xulo.io/api/idempotency).

## Delivery paused

If the app has an invoice past its due date, the call returns `402` with "Delivery is paused until the invoice for this app is paid." Nothing is recorded or sent.

If that happens while a segment or tag send is going out, the devices not reached yet are never sent, not even after the invoice is paid. The send ends as `approved` when some devices got it, or as `draft` when none did. See [Notification stats](https://docs.xulo.io/api/notification-stats#delivery-paused-during-a-send).

Errors are listed on [Errors](https://docs.xulo.io/api/errors).
