Skip to main content
POST

Authorizations

Authorization
string
header
required

Your secret API key as a Bearer token: Authorization: Bearer vxn_sk_.... Keys are created and scoped in the dashboard - see Authentication.

Headers

Idempotency-Key
string
required

Stable opaque string up to 255 chars. Same key + same body within 24h replays cached response.

Maximum string length: 255

Body

application/json

Send a broadcast. title + body are required localized objects; audience defaults to all active customers when omitted. Templates are validated at write time - syntactically invalid templates (unbalanced braces, unknown filters, forbidden roots like metadata) fail the whole broadcast with 422 template_invalid.

title
object
required

Localized title keyed by language code (en, sk, cs, de). At least one locale required. Per-locale value is plain text, 1-60 characters (Apple Wallet's lock-screen banner truncates around 60). Template variables are NOT supported in the title: it is a single string shared across all recipients, so per-recipient tokens cannot render there and any {{...}} in the title is rejected with 422. Put template variables in the body instead.

Example:
body
object
required

Localized body keyed by language code (en, sk, cs, de). At least one locale required. Per-locale value is plain text, 1-240 characters (worst-case room after banner chrome on Apple + Google). Template variables are supported. Card-mechanics variables ({{card.balance}}, {{stamps_remaining}}, ...) require a card-scoped audience (card_ids).

Example:
audience
object | null

Audience filter. Omit or send {} to broadcast to every active customer. Filters compose as INTERSECTION (AND).

Example:
schedule_at
integer | null

Optional future send time (unix seconds). Bounds: must be in the future, at most 90 days ahead (422 schedule_at_out_of_range). Omit for immediate dispatch. Scheduled broadcasts have status: "scheduled" and can be cancelled until they fire. Rate limits are checked at ACCEPT time and again at FIRE time.

Example:

null

Response

Accepted; audience materialized and queued.

A broadcast push notification. Created via POST /v1/notifications, delivered asynchronously to wallet passes (Apple Wallet + Google Wallet). The stored DTO always shows the UNRENDERED template - per-recipient rendered text is not retrievable after the fact.

id
string
required

Notification identifier. Opaque, ntf_ prefix. Immutable.

Pattern: ^ntf_[A-Za-z0-9]+$
Example:

"ntf_01J9P0G7K9C0RYHXJ2K7A4N5MZ"

object
string
required

Resource type discriminator. Always 'notification'.

Allowed value: "notification"
Example:

"notification"

title
object
required

Localized title keyed by language code (en, sk, cs, de). At least one locale required. Per-locale value is plain text, 1-60 characters (Apple Wallet's lock-screen banner truncates around 60). Template variables are supported. Card-mechanics variables ({{card.balance}}, {{stamps_remaining}}, ...) require a card-scoped audience (card_ids).

Example:
body
object
required

Localized body keyed by language code (en, sk, cs, de). At least one locale required. Per-locale value is plain text, 1-240 characters (worst-case room after banner chrome on Apple + Google). Template variables are supported. Card-mechanics variables ({{card.balance}}, {{stamps_remaining}}, ...) require a card-scoped audience (card_ids).

Example:
status
enum<string>
required

Broadcast lifecycle:

  • queued - accepted, fan-out imminent
  • scheduled - accepted with a future schedule_at; cancellable until it fires
  • processing - worker batches draining
  • completed - every per-recipient attempt finished (provider ack'd or retries exhausted; does NOT mean the human saw it)
  • partial - finished with failure rate above the warning threshold (~5%)
  • failed - catastrophic failure before deliveries (rare; safe to retry with a fresh Idempotency-Key). Reserved: NOT emitted by v1 today (see note below).
  • cancelled - cancelled while queued/scheduled

failed is declared but NOT emitted by v1 today. status is DERIVED per read from four raw signals (schedule flag + fire time, fan-out start, drain completion, delivery counters); nothing records a crash that happened before any delivery was attempted, so no code path can produce failed. GET /v1/notifications?status=failed is accepted and returns an empty page rather than an error, so branching over the full enum stays safe. The other six are all live, including cancelled, which is backed by a real cancelledAt timestamp set by DELETE /v1/notifications/{id} and takes precedence over every other signal.

Available options:
queued,
scheduled,
processing,
completed,
partial,
failed,
cancelled
Example:

"completed"

stats
object
required

Aggregate delivery counters, updated as the asynchronous fan-out drains (queue → worker batches → per-device dispatch). Starts at zero on accept. For per-recipient rows, use GET /v1/notifications/{id}/deliveries.

Example:
created_at
integer
required

Unix seconds. Time the broadcast was accepted.

Example:

1782134400

audience
object | null

The audience filter this broadcast was sent with. Null/empty = broadcast to all active customers.

Example:
schedule_at
integer | null

Unix seconds when a scheduled broadcast fires. Null for immediate sends. While in the future, the broadcast is normally status: "scheduled" and cancellable via DELETE. EXCEPTION: if the audience resolved to zero recipients at accept time there is nothing to deliver now or later, so the broadcast is accepted as already finished - status is completed with every counter at 0 and audience_resolved_count: 0, it never appears under ?status=scheduled, and DELETE returns 409 broadcast_not_cancellable even though this value is still in the future. Poll on status, not on schedule_at.

Example:

null

sent_at
integer | null

Unix seconds when fan-out started - set on EVERY send (immediate and scheduled). Null only before dispatch.

Example:

1782134401

completed_at
integer | null

Unix seconds. Populated when status reaches completed, partial, or failed. Null while queued/processing.

Example:

1782134522

request_id
string

The request id of the request that ACCEPTED this broadcast. Include it in support requests: it identifies the exchange where the fan-out actually happened. On an idempotent replay (a repeat call with the same Idempotency-Key, answered with Idempotent-Replayed: true) this deliberately differs from the Vexion-Request-Id response header: the body keeps the original accept id, while the header carries the id of the replay exchange you are holding.

Example:

"req_01J9P0G7K9C0RYHXJ2K7A4N5MZ"