Schedule a message
Schedule a send at a UTC instant or at each device’s local time.
Use a secret key from the app you want to send to. Choose either send_at or local_time;
they cannot be combined. These examples target all devices in the app.
Replace the example send_at with a future UTC instant within 30 days of your request,
and use a new Idempotency-Key for each new campaign.
Samples on this page show placeholders. Sign in to the dashboard in this browser and they fill in with your own organization, app and mobile key.
One instant, everywhere
curl -X POST https://api.carillon.dev/v1/messages \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-H "Idempotency-Key: 018f2c8a-6f3f-7a2b-9f1e-2c9b0f4d5a13" \-d '{ "audience": { "all": true }, "send_at": "2026-08-21T18:00:00Z", "payload": { "title": "Doors open", "body": "The sale starts now." }}'{
"id": "01937b30-0000-7000-8000-000000000000",
"status": "scheduled",
"mode": "live",
"created_at": "2026-08-20T09:20:00.000Z",
"deliveries": [],
"warnings": []
}deliveries is empty. A device may be opted out, replaced or gone by the time this fires, so the
identifiers are minted at fire time and the campaign is followed through its trace.
Must be in the future and at most 30 days ahead. An instant in the past answers
422 send_at_out_of_range rather than sending immediately.
Each recipient's own 10:00
curl -X POST https://api.carillon.dev/v1/messages \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-H "Idempotency-Key: 018f2c8a-6f3f-7a2b-9f1e-2c9b0f4d5a14" \-d '{ "audience": { "all": true }, "local_time": "10:00", "timezone_fallback": "Europe/Paris", "payload": { "title": "Good morning", "body": "Here is your day." }}'Devices are grouped into waves by the timezone they registered from. Each wave fires when 10:00 arrives there: today where it is still ahead, tomorrow where it has passed. Zones that share a send instant are grouped into one wave.
The trace reports the waves:
{
"id": "01937b31-0000-7000-8000-000000000000",
"status": "scheduled",
"schedule_type": "local_time",
"local_time": "10:00",
"timezone_fallback": "Europe/Paris",
"waves": { "count": 31, "next_fire_at": "2026-08-20T21:00:00.000Z" },
"outcomes": { "sent": 0, "failed": 0, "pending": 0, "total": 0 },
"deliveries": []
}timezone_fallback is required here. A device that never reported a zone has no local 10:00 for us
to find, and its wave is where those devices go, along with any zone that was not represented when
the waves were computed.
The daylight saving rules
| Case | Fires at |
|---|---|
| The wall time does not exist that day, the spring-forward gap | The first valid instant after the gap |
| The wall time happens twice, the autumn fall-back hour | Its first occurrence |
Send an IANA identifier, never an offset. Europe/Paris carries both transitions; +02:00 is
correct for only half the year.
Cancelling before it fires
curl -X POST https://api.carillon.dev/v1/messages/01937b30-0000-7000-8000-000000000000/cancel \-H "Authorization: Bearer carillon_sk_live_your_secret_key"{ "canceled": true, "already_sent": 0, "canceled_count": 0 }Both counts are zero for a campaign cancelled before it resolved, since no delivery had been written yet. Cancelling twice is safe and answers the same thing.