openapi: 3.1.0
info:
  title: WalletPassBuilder API
  version: "1.0.0"
  description: |
    Create, read, update, and archive Apple Wallet and Google Wallet passes,
    check pass-credit balance, and send notifications.

    An account can have several workspaces. Send a workspace's id in the
    `X-Workspace-Id` header to say which one a request is for; without it,
    requests that create things use the account's default workspace (chosen
    in the dashboard under Settings > Workspace or API).

    Full documentation with examples: https://walletpassbuilder.com/api-docs
  contact:
    name: WalletPassBuilder Support
    email: support@walletpassbuilder.com
    url: https://walletpassbuilder.com/api-docs
servers:
  - url: https://walletpassbuilder.com/api/v1

security:
  - bearerAuth: []

paths:
  /passes:
    get:
      operationId: listPasses
      summary: List passes
      description: >-
        Every pass the account owns, newest first. Send `X-Workspace-Id` to
        list one workspace's passes; without it, every workspace's.
      parameters:
        - $ref: "#/components/parameters/WorkspaceId"
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [live, archived]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  passes:
                    type: array
                    items:
                      $ref: "#/components/schemas/PassListItem"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: createPass
      summary: Create a pass
      description: >-
        Creates and signs a new pass. Costs one pass credit for a static
        pass, or one credit per row for a dynamic one. The pass is created
        in the workspace named by `X-Workspace-Id`, or the account's default
        workspace if none is sent.
      parameters:
        - $ref: "#/components/parameters/WorkspaceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [passTemplate]
              properties:
                passTemplate:
                  $ref: "#/components/schemas/PassTemplate"
                passData:
                  type: array
                  items:
                    $ref: "#/components/schemas/PassDataRow"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishedPass"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/CreditLimitExceeded"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"

  /passes/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      operationId: getPass
      summary: Get a pass
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PassListItem"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    patch:
      operationId: updatePass
      summary: Update a pass
      description: >-
        Re-signs an existing pass with a new design. Keeps the same id and
        share link; every device that already saved it is pushed the
        update. Omit `passData` to leave them unchanged.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [passTemplate]
              properties:
                passTemplate:
                  $ref: "#/components/schemas/PassTemplate"
                passData:
                  type: array
                  items:
                    $ref: "#/components/schemas/PassDataRow"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishedPass"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/CreditLimitExceeded"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      operationId: deletePass
      summary: Delete (archive) a pass
      description: >-
        Archives the pass. It drops off the live list, its share link stops
        working, and every saved copy is marked expired in the person's
        wallet (voided on Apple Wallet, moved to Expired passes on Google
        Wallet). Neither wallet lets an issuer remove a pass from someone's
        phone, so it stays there until they delete it. Reversible: un-archiving
        in the dashboard restores the saved copies. Preserves history rather
        than hard-deleting.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  passId:
                    type: string
                  status:
                    type: string
                    enum: [archived]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /credits:
    get:
      operationId: getCredits
      summary: Get pass-credit balance
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PassCreditsSummary"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /passes/{id}/notifications:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      operationId: sendNotification
      summary: Send or schedule a notification
      description: >-
        Sends immediately, or with `scheduledAt` (an ISO 8601 timestamp in
        the future) queues it to go out later.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                subject:
                  type: string
                  maxLength: 35
                message:
                  type: string
                  maxLength: 500
                scheduledAt:
                  type: string
                  format: date-time
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  notification:
                    $ref: "#/components/schemas/NotificationSendResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"

  /notifications:
    get:
      operationId: listNotifications
      summary: List notifications
      description: >-
        Every notification - sent, scheduled, or failed - across every pass
        the account owns, newest first. Send `X-Workspace-Id` to see only one
        workspace's.
      parameters:
        - $ref: "#/components/parameters/WorkspaceId"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  notifications:
                    type: array
                    items:
                      $ref: "#/components/schemas/Notification"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /notifications/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    delete:
      operationId: cancelNotification
      summary: Cancel a scheduled notification
      parameters:
        - name: passId
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  status:
                    type: string
                    enum: [canceled]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"

  /events:
    post:
      operationId: sendEvent
      summary: Send a custom event
      description: >-
        Sends a named custom event, matching it against every active
        automation listening for that event name and queuing a run for
        each. Purely a trigger: this returns as soon as matching
        automations are queued, not once they've finished running - check
        an automation's Runs tab in the dashboard to see how each one
        went. With `X-Workspace-Id`, only automations in that workspace are
        matched; without it, automations in every workspace are.
      parameters:
        - $ref: "#/components/parameters/WorkspaceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  maxLength: 100
                  description: >-
                    A caller-chosen event identifier, e.g. "order.shipped".
                    Matched exactly against each automation's trigger.
                data:
                  type: object
                  description: >-
                    Any JSON payload. Automation conditions can read any
                    field of it by dot-path, e.g. `event.data.tier`.
                  additionalProperties: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  triggered:
                    type: integer
                    description: How many automation runs were queued.
                  runIds:
                    type: array
                    items:
                      type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"

components:
  parameters:
    WorkspaceId:
      name: X-Workspace-Id
      in: header
      required: false
      description: >-
        Which workspace this request is for. Find ids in the dashboard under
        API or Settings > Workspace. An id that isn't one of the account's
        workspaces returns `invalid_request`.
      schema:
        type: string

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: wpb_live_<key>
      description: Generate a key from the dashboard at /api-keys.

  responses:
    Unauthorized:
      description: Missing, invalid, or revoked API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: No matching resource for this account
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    InvalidRequest:
      description: The request body or a parameter is missing or malformed
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    CreditLimitExceeded:
      description: This would exceed the account's pass-credit balance
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RateLimited:
      description: Too many requests - see the Retry-After header
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the rate limit window resets.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - invalid_request
                - not_found
                - credit_limit_exceeded
                - rate_limited
                - internal_error
            message:
              type: string

    PassDataRow:
      type: object
      description: One recipient's data for a dynamic (personalized) pass.
      required: [values]
      properties:
        id:
          type: string
        values:
          type: object
          additionalProperties:
            type: string

    PassTemplate:
      type: object
      description: >-
        A pass's full design. See
        https://walletpassbuilder.com/api-docs#passes for a complete
        example; only the commonly-used fields are typed here. passType,
        cardTitle, logoText, backgroundColor, and assets.logo are required
        - everything else defaults sensibly if omitted.
      required: [passType, cardTitle, logoText, backgroundColor, assets]
      properties:
        passType:
          type: string
          enum: [generic, loyalty]
          description: >-
            generic covers gift cards, tickets, and membership cards;
            loyalty adds a stamp counter. Every pass renders with the same
            visual layout regardless of type.
        logoText:
          type: string
        cardTitle:
          type: string
        backgroundColor:
          type: string
          description: Shared by both wallets.
        backSections:
          type: array
          description: Extra rows shown on the back of the pass.
          items:
            $ref: "#/components/schemas/BackSection"
        apple:
          $ref: "#/components/schemas/PassApple"
        assets:
          $ref: "#/components/schemas/PassAssets"
        rows:
          type: array
          description: >-
            One entry per row of fields. Only the first entry is rendered
            today - more may be supported later, so extra entries are
            accepted but ignored, not rejected.
          items:
            $ref: "#/components/schemas/PassRow"
        scan:
          $ref: "#/components/schemas/PassScan"
        notification:
          $ref: "#/components/schemas/PassNotification"
      additionalProperties: true

    BackSection:
      type: object
      properties:
        id:
          type: string
          description: >-
            "__notification" is reserved - a Send Notification call upserts
            that one section, so using it yourself means a future
            notification will overwrite it.
        type:
          type: string
          enum: [Text, Link, Phone]
          description: Link opens value as a URL, Phone dials it, Text just displays it.
        label:
          type: string
        value:
          type: string

    PassApple:
      type: object
      description: >-
        Fields that only apply to Apple Wallet - there's no Google Wallet
        equivalent for any of these today.
      properties:
        textColor:
          type: string
          description: Primary text color.
        labelColor:
          type: string
          description: Field label color.
        topRightLabel:
          type: string
        topRightValue:
          type: string

    PassAssets:
      type: object
      required: [logo]
      properties:
        logo:
          type: string
          description: Small square logo image URL.
        rectLogo:
          type: string
          nullable: true
        appleHero:
          type: string
          nullable: true
          description: Apple Wallet hero/strip image URL.
        googleHero:
          type: string
          nullable: true
          description: Google Wallet hero image URL.
        appleThumbnail:
          type: string
          nullable: true

    PassRowField:
      type: object
      properties:
        label:
          type: string
        value:
          type: string

    PassRow:
      type: object
      description: The three data fields on the pass face.
      properties:
        left:
          $ref: "#/components/schemas/PassRowField"
        middle:
          $ref: "#/components/schemas/PassRowField"
        right:
          $ref: "#/components/schemas/PassRowField"

    PassScan:
      type: object
      properties:
        hidden:
          type: boolean
          description: Hides the barcode/QR block entirely.
        type:
          type: string
          enum: [barcode, qr]
        value:
          type: string
          description: The encoded value. Set this unless hidden is true.
        label:
          type: string
          description: Text shown under the scan code.

    PassNotification:
      type: object
      description: >-
        Automatic lock-screen triggers - Apple Wallet only today, no
        Google Wallet equivalent yet.
      properties:
        triggers:
          type: array
          description: >-
            Which triggers are on for this pass. Each one also needs its
            own field(s) below to actually fire.
          items:
            type: string
            enum: [eventStart, beforeExpiry, nearLocation, nearBeacon]
        eventStartDate:
          type: string
          format: date-time
          description: Required if triggers includes "eventStart".
        scheduledExpiry:
          type: string
          format: date-time
          description: >-
            Required if triggers includes "beforeExpiry" (surfaces the
            pass 48 hours prior). Whether or not that trigger is on, the pass
            is marked expired at this time on both wallets (Apple Wallet's
            expiration date, Google Wallet's valid-until time).
        locations:
          type: array
          description: Required if triggers includes "nearLocation". Up to 10.
          items:
            $ref: "#/components/schemas/NotifyLocation"
        beacons:
          type: array
          description: Required if triggers includes "nearBeacon". Up to 10.
          items:
            $ref: "#/components/schemas/NotifyBeacon"

    NotifyLocation:
      type: object
      properties:
        id:
          type: string
        country:
          type: string
        address:
          type: string
        latitude:
          type: string
        longitude:
          type: string

    NotifyBeacon:
      type: object
      properties:
        id:
          type: string
        uuid:
          type: string
        major:
          type: string
        minor:
          type: string
        message:
          type: string

    PublishedPass:
      type: object
      description: >-
        passUrl alone covers both wallets - it auto-detects the visitor's
        platform and shows the right "Add to Wallet" button, so no
        Apple/Google-specific link is returned separately.
      properties:
        passId:
          type: string
        passUrl:
          type: string
        workspaceId:
          type: string
          description: The workspace the pass was created in.

    PassListItem:
      type: object
      properties:
        passId:
          type: string
        passTemplate:
          $ref: "#/components/schemas/PassTemplate"
        passData:
          type: array
          items:
            $ref: "#/components/schemas/PassDataRow"
        status:
          type: string
          enum: [live, archived]
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        passUrl:
          type: string

    PassCreditsSummary:
      type: object
      properties:
        balance:
          type: integer
        consumed:
          type: integer
        remaining:
          type: integer
        remainingPct:
          type: number
        low:
          type: boolean

    NotificationSendResult:
      type: object
      description: >-
        What a send/schedule call returns - just the handle needed to
        cancel it (id) and the delivery state. passId, passTitle, passUrl,
        subject, and message are all either the pass id you just called or
        values you just sent in this same request, so they aren't echoed
        back.
      properties:
        id:
          type: string
        status:
          type: string
          enum: [scheduled, sending, sent, failed, canceled]
        scheduledAt:
          type: string
          format: date-time
          nullable: true
        sentAt:
          type: string
          format: date-time
          nullable: true
        error:
          type: string
        results:
          type: object
          properties:
            applePushed:
              type: boolean
            googleMessaged:
              type: boolean

    Notification:
      type: object
      description: The full notification record - returned by List Notifications, which spans every pass the account owns.
      properties:
        id:
          type: string
        passId:
          type: string
        passTitle:
          type: string
        passUrl:
          type: string
        subject:
          type: string
        message:
          type: string
        source:
          type: string
          enum: [immediate, scheduled]
        scheduledAt:
          type: string
          format: date-time
          nullable: true
        status:
          type: string
          enum: [scheduled, sending, sent, failed, canceled]
        createdAt:
          type: string
          format: date-time
        sentAt:
          type: string
          format: date-time
          nullable: true
        error:
          type: string
        results:
          type: object
          properties:
            applePushed:
              type: boolean
            googleMessaged:
              type: boolean
