API errors
Error response format, codes, and corrective actions.
Every failure is RFC 9457 Problem Details, with
Content-Type: application/problem+json. Validation failures, unknown routes and unhandled
exceptions all use the same shape.
{
"type": "https://carillon.dev/errors/payload_too_large",
"title": "Payload exceeds the provider limit",
"status": 422,
"code": "payload_too_large",
"docs": "https://app.carillon.dev/docs/errors#payload_too_large",
"detail": "The rendered payload is 4312 bytes; APNs allows 4096.",
"actual_bytes": 4312,
"limit_bytes": 4096
}| Field | Use |
|---|---|
code | Switch on this. It is stable, and every value appears below |
title | Names the class of problem |
detail | Describes this occurrence, and says what to do next |
| Extensions | The numbers you need to act, such as actual_bytes against limit_bytes |
Sending
empty_payload
422. The notification would show nothing.
Send at least a title, a body or a raw apns override. A notification with none of the three
arrives on the handset showing nothing at all.
payload_too_large
422. The rendered payload exceeds the provider limit, which is 4096 bytes for APNs.
Shorten the title, the body or your custom data. Where localizations are involved, the heaviest translation is the one that counts, not the base text.
reserved_data_key
422. The payload claims the carillon data key.
Rename your key. Every payload carries a carillon key holding the delivery identifier that open
tracking needs, and a payload that sets it itself is refused rather than silently overwritten.
unknown_devices
422. Some devices are not registered in this app.
Register these devices before sending to them. An id belonging to another app is unknown here.
unknown_audience
422. No such saved audience in this app.
List your saved audiences and send the id of the one you meant, or send device_ids instead. An
audience belonging to another app is unknown here.
no_valid_credential
422. No provider will accept this app's credential, and this campaign could not produce a single
notification.
The detail names which credential, when the provider last refused it, and what to do. A campaign
with one working platform and one broken one is accepted instead, and the broken half arrives as a
warnings entry on the 202.
delivery_rate_too_high
422. The rate asked for is above your app's ceiling.
Lower it, or omit it to use the ceiling. A rate above the ceiling is refused rather than capped.
campaign_not_paused
409. Resume was asked for on a campaign that is not paused.
Scheduling
send_at_out_of_range
422. The instant is in the past, or more than 30 days ahead.
Send an instant in the future, or omit send_at to send now. An instant in the past is refused
rather than sent immediately. For a later send, create the campaign when it is within the 30-day window.
missing_timezone_fallback
422. A local_time campaign carries no timezone_fallback.
Add one, an IANA zone such as Europe/Paris. A device that never reported its timezone has no local
10:00, and the fallback is the wave it goes in.
unknown_timezone
422. That is not a timezone the tz database knows.
Send an identifier such as Europe/Paris, never an offset like +02:00. An offset does not carry
the daylight saving transitions.
Idempotency and deduplication
idempotency_key_reused
422. This Idempotency-Key was used with a different body.
Use a new key for this request, or send the body the key was first used with. The original answer is not returned for a different body: it names identifiers belonging to another notification.
idempotency_key_in_flight
409, with Retry-After. An earlier request under this key is still being processed.
Retry after the delay.
duplicate_dedup_key
409, with the campaign_id that holds the key.
Read that campaign to see what became of it, or send this under a different dedup_key if it is a
different notification. Deduplication records are retained for 30 days. Idempotency-Key replays
the original response during its 24-hour retention window.
Devices and imports
empty_import
422. The body is empty.
Send the CSV, starting with a header row that names at least token and platform.
request_too_large
413. The request body exceeds the route's limit: 1 MiB on POST /v1/messages, 64 KiB on
POST /v1/devices. The limit_bytes extension carries the ceiling.
Send fewer external_ids per request, or a smaller payload. Two thousand identifiers of 256
characters fit under the limit with room for the payload.
import_too_large
413. The file is larger than one request may carry.
Split it and import the parts in any order: a token already imported is skipped, so overlapping files are safe.
unsupported_media_type
415. The body is not a CSV.
Send the CSV as the request body with Content-Type: text/csv. This endpoint takes the file itself,
not a JSON envelope around it.
Audiences
duplicate_audience_name
409. An audience of this app already goes by that name.
Give this one a different name, or delete the one holding it.
Authentication and authorization
unauthorized
401. No valid API key was supplied.
Send your key as Authorization: Bearer carillon_sk_live_…. Secret keys are shown once when
created; a lost one is replaced, never recovered.
On the management section the message differs: send your session token as Authorization: Bearer ….
Sign in on the dashboard to obtain one, or run carillon login. API keys do not open the management
section.
forbidden
403. This key may not perform that action.
A mobile key cannot send, list or read a trace. It ships inside your mobile app and can be read by anyone who has the binary. Use a secret key from your backend.
not_found
404. No such resource, in this app.
A resource that belongs to another app answers the same way as one that does not exist.
rate_limited
429. Too many requests.
Wait the number of seconds the detail names, then retry. See rate limits.
Invitations
invitation_expired
410. Ask whoever invited you to send a new one; it takes them one click.
invitation_not_pending
409. That invitation has already been used or withdrawn. Sign in if you have an account, or ask
for a new invitation.
account_already_exists
409. That address already has an account. Sign in with it, then open the link again to join.
Errors on our side
internal_error
500. Something went wrong on our side.
A thrown message is never returned. It is logged and replaced by a generic body, since exception messages can quote the input. Retry, and send us the request if it persists.
mail_not_sent
502. The invitation was created but the email did not go out. Send it again from the pending list.
mail_not_configured
503. This environment cannot send email until a verified sender domain is configured for it.
invalid_request
400. The request could not be understood. The detail names the field.