> ## Documentation Index
> Fetch the complete documentation index at: https://vexioncards.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Retrieve a notification

> Returns the notification object - the **single source of truth** for broadcast state: lifecycle `status` (queued/scheduled/processing/completed/partial/failed/cancelled), aggregate per-channel `stats`, and the lifecycle timestamps (`schedule_at`, `sent_at`, `completed_at`).

Need per-recipient granularity ("did customer X get it on which device")? That's `GET /v1/notifications/{id}/deliveries` - an unbounded row collection that cannot live inline on this DTO.

Polling this endpoint is fine for short-lived broadcasts; for high-volume monitoring prefer the `notification.delivery_updated` webhook. The DTO always shows the **unrendered template** - per-recipient rendered text is not retrievable.

Scope: `notifications:read`.



## OpenAPI

````yaml /openapi.json get /v1/notifications/{id}
openapi: 3.1.0
info:
  title: VEXiON cards Public API
  description: >-
    ## Rate limiting


    Two independent layers, checked in this order:


    1. **Per-IP throttle (pre-authentication).** Applied to every `/v1` request
    BEFORE the API key is checked, keyed on the caller's IP address:
       - **600 requests/minute** per IP - a flood ceiling, `429 rate_limit_error` / `rate_limited`.
       - **20 failed authentications per 15 minutes** per IP - a key-guessing ceiling. Only responses that are `401` (missing/invalid/revoked key) count against this ceiling; a `403` from a valid key that simply lacks a scope does not. `429 rate_limit_error` / `too_many_failed_attempts`.

       Both ceilings send `Retry-After` (seconds).
    2. **Per-key, per-bucket limiter (post-authentication).** Documented per
    endpoint (see each operation's `429` response and the `rate_limit` field on
    the API Key resource) - e.g. 60/min default, 120/min for transactions, plus
    the notification-broadcast and lookup-filter buckets described on their own
    endpoints.


    ### Rate-limit response headers


    Every rate-limited response carries the legacy `X-RateLimit-Limit` /
    `X-RateLimit-Remaining` / `X-RateLimit-Reset` family (the last is an **epoch
    timestamp**) AND the IETF standards-track headers, side by side:


    - `RateLimit-Policy: "<bucket>";q=<quota>;w=<window seconds>` - the static
    policy for the bucket that was hit.

    - `RateLimit: "<bucket>";r=<remaining>;t=<seconds until reset>` - live
    state. **`t` is a DURATION (seconds remaining), not the epoch timestamp
    `X-RateLimit-Reset` carries - the two are never the same value.**
  version: 1.0.0
servers:
  - url: https://api.vexioncards.one
    description: Production
  - url: https://migration.api.vexioncards.one
    description: Sandbox
security:
  - apiKey: []
tags:
  - name: Customers
  - name: Cards
  - name: Transactions
  - name: Notifications
  - name: Locations
  - name: Products
  - name: API keys
  - name: Organization
  - name: Webhook endpoints
  - name: Events
  - name: Custom fields
  - name: Team members
  - name: Icons
paths:
  /v1/notifications/{id}:
    get:
      tags:
        - Notifications
      summary: Retrieve a notification
      description: >-
        Returns the notification object - the **single source of truth** for
        broadcast state: lifecycle `status`
        (queued/scheduled/processing/completed/partial/failed/cancelled),
        aggregate per-channel `stats`, and the lifecycle timestamps
        (`schedule_at`, `sent_at`, `completed_at`).


        Need per-recipient granularity ("did customer X get it on which
        device")? That's `GET /v1/notifications/{id}/deliveries` - an unbounded
        row collection that cannot live inline on this DTO.


        Polling this endpoint is fine for short-lived broadcasts; for
        high-volume monitoring prefer the `notification.delivery_updated`
        webhook. The DTO always shows the **unrendered template** -
        per-recipient rendered text is not retrievable.


        Scope: `notifications:read`.
      operationId: getNotification
      parameters:
        - name: id
          in: path
          description: Notification identifier (`ntf_` prefix).
          required: true
          example: ntf_01J9P0G7K9C0RYHXJ2K7A4N5MZ
          schema:
            type: string
            pattern: ^ntf_[A-Za-z0-9]+$
      responses:
        '200':
          description: Notification.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Notification'
              examples:
                Completed broadcast:
                  summary: Fan-out finished
                  value:
                    id: ntf_01J9P0G7K9C0RYHXJ2K7A4N5MZ
                    object: notification
                    title:
                      en: Summer special menu
                    body:
                      en: Try the new lime lemonade for 2.50 EUR.
                    audience:
                      card_ids:
                        - crd_sophcrprtvpmmnwxah2lomnkee
                    status: completed
                    schedule_at: null
                    sent_at: 1782134401
                    stats:
                      total: 121
                      audience_resolved_count: 121
                      apple_wallet:
                        total: 98
                        delivered: 96
                        failed: 2
                      google_wallet:
                        total: 23
                        delivered: 22
                        failed: 1
                      delivered: 118
                      failed: 3
                    created_at: 1782134400
                    completed_at: 1782134522
                    request_id: req_01J9P0G7K9C0RYHXJ2K7A4N5MZ
                Still processing:
                  summary: Mid fan-out
                  value:
                    id: ntf_01J9P0G7K9C0RYHXJ2K7A4N5MZ
                    object: notification
                    title:
                      en: Summer special menu
                    body:
                      en: Try the new lime lemonade for 2.50 EUR.
                    audience:
                      card_ids:
                        - crd_sophcrprtvpmmnwxah2lomnkee
                    status: processing
                    schedule_at: null
                    sent_at: 1782134401
                    stats:
                      total: 121
                      audience_resolved_count: 121
                      apple_wallet:
                        total: 98
                        delivered: 97
                        failed: 1
                      google_wallet:
                        total: 23
                        delivered: 23
                        failed: 0
                      delivered: 120
                      failed: 1
                    created_at: 1782134400
                    completed_at: null
                    request_id: req_01J9P0G7K9C0RYHXJ2K7A4N5MZ
          headers: {}
        '400':
          description: Malformed body or missing required field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '401':
          description: Missing, invalid, or revoked API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '403':
          description: API key lacks the required scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: Resource not found in your organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '429':
          description: Per-minute or per-day rate limit exceeded. Respect Retry-After.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Server error. Retry-safe on idempotent calls.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      deprecated: false
components:
  schemas:
    Notification:
      type: object
      description: >-
        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.
      required:
        - id
        - object
        - title
        - body
        - status
        - stats
        - created_at
      properties:
        id:
          type: string
          pattern: ^ntf_[A-Za-z0-9]+$
          description: Notification identifier. Opaque, `ntf_` prefix. Immutable.
          example: ntf_01J9P0G7K9C0RYHXJ2K7A4N5MZ
        object:
          type: string
          const: notification
          description: Resource type discriminator. Always 'notification'.
          example: notification
        title:
          type: object
          description: >-
            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).
          minProperties: 1
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 60
          example:
            en: Summer special menu
        body:
          type: object
          description: >-
            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).
          minProperties: 1
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 240
          example:
            en: Try the new lime lemonade for 2.50 EUR.
        audience:
          anyOf:
            - $ref: '#/components/schemas/NotificationAudience'
            - type: 'null'
          description: >-
            The audience filter this broadcast was sent with. Null/empty =
            broadcast to all active customers.
        status:
          type: string
          enum:
            - queued
            - scheduled
            - processing
            - completed
            - partial
            - failed
            - cancelled
          description: >-
            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.
          example: completed
        schedule_at:
          type:
            - integer
            - 'null'
          description: >-
            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:
          type:
            - integer
            - 'null'
          description: >-
            Unix seconds when fan-out started - set on EVERY send (immediate and
            scheduled). Null only before dispatch.
          example: 1782134401
        stats:
          $ref: '#/components/schemas/NotificationStats'
        created_at:
          type: integer
          description: Unix seconds. Time the broadcast was accepted.
          example: 1782134400
        completed_at:
          type:
            - integer
            - 'null'
          description: >-
            Unix seconds. Populated when status reaches `completed`, `partial`,
            or `failed`. Null while queued/processing.
          example: 1782134522
        request_id:
          type: string
          description: >-
            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
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
          properties:
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - permission_error
                - not_found_error
                - rate_limit_error
                - idempotency_error
                - api_error
            code:
              type: string
            message:
              type: string
            retry_after:
              type: integer
              description: >-
                Seconds to wait before retrying. Present on rate-limit (429) and
                idempotency-key-in-use (409) errors; mirrors the `Retry-After`
                HTTP header. Absent on error types that carry no retry guidance.
              example: 5
            param:
              type:
                - string
                - 'null'
            request_id:
              type: string
            doc_url:
              type: string
              format: uri
            field_errors:
              type: array
              description: >-
                Per-field errors on a 422. At most 100 entries: a request with
                more errors gets the first 100.
              items:
                type: object
                properties:
                  param:
                    type: string
                  code:
                    type: string
                  message:
                    type: string
                  expected_currency:
                    type: string
                    description: >-
                      On `currency_mismatch`: the business currency the request
                      must use. Absent on every other field error.
          description: >-
            The error payload: type (category), code (machine-readable), message
            (human-readable), retry_after (seconds, on
            rate-limit/idempotency-in-use errors only), param (offending field),
            request_id, doc_url, field_errors[].
      description: >-
        Standard error envelope. Branch on `error.code`; include
        `error.request_id` in support requests. See the Errors guide.
    NotificationAudience:
      type: object
      description: >-
        Selects which customers receive the broadcast. Four optional id-array
        filters that compose as a logical **INTERSECTION (AND)** - never a
        union. Send an empty object `{}` (or omit `audience` entirely) to
        broadcast to every active customer in the calling organization that has
        at least one installed wallet pass.


        If you need a UNION, send separate broadcasts.
      properties:
        card_ids:
          type: array
          items:
            type: string
            pattern: ^crd_[A-Za-z0-9]+$
          minItems: 1
          maxItems: 50
          description: >-
            Limits the audience to customers holding one of the listed cards.
            1-50 `crd_` ids. Ids of another organization return `404
            not_found_error`.
          example:
            - crd_sophcrprtvpmmnwxah2lomnkee
        customer_ids:
          type: array
          items:
            type: string
            pattern: ^cus_[A-Za-z0-9]+$
          minItems: 1
          maxItems: 1000
          description: >-
            Direct cohort targeting. 1-1000 `cus_` ids. All ids must belong to
            the calling organization - an id of another organization fails the
            broadcast with `400 audience_invalid`. Ids resolving to a customer
            with no installed wallet pass are silently skipped (counted under
            neither delivered nor failed).
        external_ids:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 1000
          description: >-
            Merchant-side identifiers, 1-1000. Each resolves to a single `cus_`
            within the calling organization. Unknown external ids are silently
            skipped. If two customers share the same `external_id`
            (merchant-side data error), the broadcast fails with `422
            external_id_collision` so you can clean up before double-delivering.
          example:
            - loyalty-789
            - MEMBER-A23B-9942
        location_ids:
          type: array
          items:
            type: string
            pattern: ^loc_[A-Za-z0-9]+$
          minItems: 1
          maxItems: 25
          description: >-
            Target customers ATTRIBUTED to these locations - the location where
            they signed up or last visited. Combines with other audience keys as
            an intersection.
      example:
        card_ids:
          - crd_sophcrprtvpmmnwxah2lomnkee
    NotificationStats:
      type: object
      description: >-
        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`.
      properties:
        total:
          type: integer
          description: Total resolved recipients across all channels.
          example: 142
        audience_resolved_count:
          type: integer
          description: >-
            Recipients that matched the audience after filtering (install filter
            + location attribution). Compare against your expected cohort size.
          example: 142
        apple_wallet:
          type: object
          description: >-
            Apple Wallet channel - APNs silent push; the device refetches the
            pass and iOS renders the changeMessage banner.
          properties:
            total:
              type: integer
              description: Recipients on this channel.
            delivered:
              type: integer
              description: Device-confirmed deliveries (pass re-fetch / API accept).
            failed:
              type: integer
              description: Terminal failures (stale tokens, uninstalled passes).
          example:
            total: 98
            delivered: 96
            failed: 2
        google_wallet:
          type: object
          description: Google Wallet channel - server-side object update + message.
          properties:
            total:
              type: integer
              description: Recipients on this channel.
            delivered:
              type: integer
              description: Device-confirmed deliveries (pass re-fetch / API accept).
            failed:
              type: integer
              description: Terminal failures.
          example:
            total: 23
            delivered: 22
            failed: 1
        delivered:
          type: integer
          description: Sum of delivered across channels.
          example: 138
        failed:
          type: integer
          description: Sum of failed across channels.
          example: 4
      example:
        total: 121
        audience_resolved_count: 121
        apple_wallet:
          total: 98
          delivered: 96
          failed: 2
        google_wallet:
          total: 23
          delivered: 22
          failed: 1
        delivered: 118
        failed: 3
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        Your secret API key as a Bearer token: `Authorization: Bearer
        vxn_sk_...`. Keys are created and scoped in the dashboard - see
        [Authentication](/api-reference/introduction#authentication).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.