Create Campaign
Create a push campaign as a draft. Creating never sends — use Send Campaign when you are ready.
Most teams build campaigns in the Arsel Dashboard. Use the API when campaigns are generated programmatically.
Endpoint
POST /push/campaigns
Returns: 201 Created
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <your-api-key> | Yes |
Content-Type | application/json | Yes |
Body Parameters
Required
| Parameter | Type | Description |
|---|---|---|
name | string | Internal campaign name. Max 100 characters. Not shown to recipients. |
title | string | Notification title. 1–200 characters. |
body | string | Notification body. 1–1000 characters. |
Content
| Parameter | Type | Description |
|---|---|---|
description | string | Internal note. |
image_url | string | Large image. HTTPS only. |
icon_url | string | Small icon. HTTPS only. |
deep_link | string | Opened on a body tap. Max 2048 characters. |
android_channel_id | string | Android notification channel id. Max 128 characters. |
action_buttons | object[] | Up to 3 tappable buttons. |
data_payload | object | Flat string→string map. Keys starting arsel_, google, or gcm are rejected. |
Each action_buttons entry:
| Field | Type | Required | Description |
|---|---|---|---|
action_id | string | Yes | Stable id, 1–64 characters. Echoed back on the click engagement so you can tell buttons apart. |
label | string | Yes | Button text, 1–64 characters. |
deep_link | string | No | Opened when this button is tapped. Max 2048 characters. |
Audience
| Parameter | Type | Description |
|---|---|---|
list_ids | string[] | List IDs to target. |
tag_ids | string[] | Tag IDs to target. |
segment_ids | string[] | Segment IDs to target; see List Segments. |
target_platforms | string[] | Restrict to android, ios, and/or web. Omit for all. |
smart_sending_enabled | boolean | Skip contacts who recently received a push, respecting your frequency cap. |
Delivery
| Parameter | Type | Description |
|---|---|---|
ttl_seconds | integer | How long delivery may be retried, 60–2419200 (28 days). |
priority | string | high or normal. |
throttle_minutes | integer | Spread the send over N minutes. |
throttle_minutes is forThrottling does not protect Arsel — per-organization fair scheduling already does that. It protects your backend from your entire audience opening the app at once and hitting your API simultaneously. Set it based on your own capacity.
Push content is static. {{firstName}} and other merge variables are not interpolated — they would render literally on the device. Personalize by segmenting the audience instead.
{
"name": "Flash sale — 6h only",
"title": "Doors close at midnight",
"body": "Everything 40% off for the next six hours.",
"image_url": "https://cdn.example.com/sale.png",
"deep_link": "myapp://sale",
"list_ids": ["0192a1b2-c3d4-7e5f-9000-abcdef123456"],
"target_platforms": ["android", "ios"],
"ttl_seconds": 21600,
"throttle_minutes": 15,
"smart_sending_enabled": true
}
Response
Returns the full campaign, with status: "Draft".
{
"id": "0192a1b2-c3d4-7e5f-9000-abcdef123456",
"name": "Flash sale — 6h only",
"description": null,
"title": "Doors close at midnight",
"body": "Everything 40% off for the next six hours.",
"image_url": "https://cdn.example.com/sale.png",
"icon_url": null,
"deep_link": "myapp://sale",
"android_channel_id": null,
"ttl_seconds": 21600,
"priority": "high",
"action_buttons": null,
"data_payload": null,
"target_platforms": ["android", "ios"],
"throttle_minutes": 15,
"status": "Draft",
"smart_sending_enabled": true,
"scheduled_at": null,
"sent_at": null,
"created_at": "2026-08-10T12:00:00.000Z",
"updated_at": "2026-08-10T12:00:00.000Z"
}
Campaign status
| Status | Meaning |
|---|---|
Draft | Created, not yet sent |
Scheduled | Queued for a future time |
Queued | Accepted for immediate fan-out |
Sending | Fan-out in progress |
Paused | Temporarily halted |
Sent | Fan-out complete |
Failed | Fan-out failed |
Cancelled | Cancelled before completion |
Archived | Archived |
Examples
- cURL
- JavaScript
- Python
- C#
- PHP
curl -X POST "https://api.arsel.sa/v1/push/campaigns" \
-H "Authorization: Bearer $ARSEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Flash sale — 6h only",
"title": "Doors close at midnight",
"body": "Everything 40% off for the next six hours.",
"list_ids": ["0192a1b2-c3d4-7e5f-9000-abcdef123456"]
}'
const response = await fetch("https://api.arsel.sa/v1/push/campaigns", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ARSEL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Flash sale — 6h only",
title: "Doors close at midnight",
body: "Everything 40% off for the next six hours.",
list_ids: ["0192a1b2-c3d4-7e5f-9000-abcdef123456"],
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(`${data.name}: ${data.message}`);
}
console.log(data);
import os
import requests
response = requests.post(
"https://api.arsel.sa/v1/push/campaigns",
headers={"Authorization": f"Bearer {os.environ['ARSEL_API_KEY']}"},
json={
"name": "Flash sale — 6h only",
"title": "Doors close at midnight",
"body": "Everything 40% off for the next six hours.",
"list_ids": ["0192a1b2-c3d4-7e5f-9000-abcdef123456"],
},
)
response.raise_for_status()
print(response.json())
using System.Net.Http.Headers;
using System.Text;
var client = new HttpClient(); // share one instance across requests
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("ARSEL_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.arsel.sa/v1/push/campaigns")
{
Content = new StringContent(
"""
{
"name": "Flash sale — 6h only",
"title": "Doors close at midnight",
"body": "Everything 40% off for the next six hours.",
"list_ids": ["0192a1b2-c3d4-7e5f-9000-abcdef123456"]
}
""",
Encoding.UTF8,
"application/json"),
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)response.StatusCode}: {body}");
Console.WriteLine(body);
<?php
$ch = curl_init("https://api.arsel.sa/v1/push/campaigns");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ARSEL_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "Flash sale — 6h only",
"title" => "Doors close at midnight",
"body" => "Everything 40% off for the next six hours.",
"list_ids" => ["0192a1b2-c3d4-7e5f-9000-abcdef123456"],
]),
]);
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) >= 400) {
throw new Exception("{$data['name']}: {$data['message']}");
}
print_r($data);
Error Responses
- 422 Validation
- 401 Unauthorized
Returned for a title over 200 characters, a non-HTTPS image_url, more than 3 action_buttons, a reserved data_payload key, or a ttl_seconds outside 60–2419200.
{
"status_code": 422,
"name": "validation_error",
"message": "Validation failed"
}
{
"status_code": 401,
"name": "unauthorized",
"message": "Invalid API Key"
}