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. |
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. |
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 be_your_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: {
"Content-Type": "application/json",
Authorization: "Bearer be_your_api_key",
},
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 campaign = await response.json();
console.log(campaign.id, campaign.status);
import requests
response = requests.post(
"https://api.arsel.sa/v1/push/campaigns",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer be_your_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"],
},
)
campaign = response.json()
print(campaign["id"], campaign["status"])
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer be_your_api_key");
var payload = new
{
name = "Flash sale — 6h only",
title = "Doors close at midnight",
body = "Everything 40% off for the next six hours.",
list_ids = new[] { "0192a1b2-c3d4-7e5f-9000-abcdef123456" }
};
var json = System.Text.Json.JsonSerializer.Serialize(payload);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.arsel.sa/v1/push/campaigns", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
<?php
$ch = curl_init("https://api.arsel.sa/v1/push/campaigns");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer be_your_api_key"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, 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"]
]));
echo curl_exec($ch);
curl_close($ch);
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 or missing API key"
}