openapi: 3.1.0
info:
  title: VGS Cards API PSP Tokens Extension
  version: 0.4.0
  summary: Proposed extension of the VGS Cards API to support PSP tokens as a first-class card capability.
  description: |
    This specification proposes a Token Sync extension to the VGS Cards API.

    It preserves the existing `/cards`, `GET /cards/{card_id}`, and `DELETE /cards/{card_id}`
    resource model, and adds `POST /cards/{card_id}/psp-tokens` for PSP token synchronization.

    In this model:

    - `psp-tokens` is enabled by default when a card is created unless explicitly disabled
    - card responses can include PSP token state under the card's `included` collection
    - deleting a card also removes associated PSP tokens and emits a webhook event
    - `POST /cards/{card_id}/psp-tokens` synchronizes all configured PSP tokens by default,
      or a targeted subset when providers are supplied
servers:
  - url: https://sandbox.vgsapi.com
    description: Sandbox environment
  - url: https://vgsapi.com
    description: Production environment
tags:
  - name: cards
  - name: psp-tokens
security:
  - bearerAuth: []
paths:
  /cards:
    post:
      tags:
        - cards
      summary: Create a card with PSP tokens enabled by default.
      description: |
        Proposed extension of the existing Create Card API.

        This operation preserves the current VGS card-creation workflow while enabling the
        `psp-tokens` capability by default. If PSP provisioning completes within the synchronous
        response window, the returned card object can already contain Braintree, Adyen, and
        Stripe token state in its `included` collection.
      operationId: createCard
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateCardRequest'
            examples:
              defaultPspTokens:
                summary: Create a card with PSP tokens defaulted on
                value:
                  data:
                    type: cards
                    attributes:
                      pan: "4111111111111111"
                      cvc: "123"
                      exp_month: 12
                      exp_year: 2028
                      cardholder_name: John Doe
                    meta:
                      customer_id: merchant-customer-123
      responses:
        '201':
          description: Card created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CardResponse'
              examples:
                cardCreatedWithPspTokens:
                  value:
                    data:
                      id: CRD12345
                      type: cards
                      attributes:
                        pan_alias: tok_live_abcdefg
                        cvc_alias: tok_live_vwxyz
                        token_type: pan
                        bin: "411111"
                        first8: "41111111"
                        last4: "1111"
                        exp_month: 12
                        exp_year: 2028
                        cardholder_name: John Doe
                        network_transaction_id: 0163125478900412
                      capabilities:
                        - psp-tokens
                      included:
                        - type: psp-tokens
                          attributes:
                            braintree:
                              attributes:
                                token: bt_tok_primary_123
                                state: active
                                created_at: "2026-04-13T10:00:00Z"
                                updated_at: "2026-04-13T10:00:00Z"
                                last_sync_status: provisioned
                            adyen:
                              attributes:
                                token: ady_tok_789
                                state: active
                                created_at: "2026-04-13T10:00:01Z"
                                updated_at: "2026-04-13T10:00:01Z"
                                last_sync_status: provisioned
                            stripe:
                              attributes:
                                token: pm_1RtExampleStripe
                                state: active
                                created_at: "2026-04-13T10:00:02Z"
                                updated_at: "2026-04-13T10:00:02Z"
                                last_sync_status: provisioned
        '303':
          description: Card already exists.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '400':
          description: The request was invalid.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '401':
          description: No credentials were provided, or the provided credentials were expired.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '403':
          description: Valid credentials were provided, but they do not permit access to this resource.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '422':
          description: The server was unable to process the request because it contains invalid data.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '500':
          description: Internal Server Error.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
  /cards/{card_id}:
    get:
      tags:
        - cards
      summary: Get a card, including PSP token capability state.
      description: |
        Proposed extension of the existing Get Card API.

        This operation returns the standard card object and may include PSP provider
        state in the `included` collection. Clients can request `psp-tokens` in the `include`
        query parameter alongside existing related resources.
      operationId: getCard
      parameters:
        - $ref: '#/components/parameters/CardId'
        - name: include
          in: query
          required: false
          description: Comma-delimited list of related resources to include.
          schema:
            type: string
            example: psp-tokens,network-tokens,card-update-subscriptions
      responses:
        '200':
          description: Item requested by ID.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CardResponse'
              examples:
                getCardWithPspTokens:
                  value:
                    data:
                      id: CRD12345
                      type: cards
                      attributes:
                        pan_alias: tok_live_abcdefg
                        cvc_alias: tok_live_vwxyz
                        token_type: pan
                        bin: "411111"
                        first8: "41111111"
                        last4: "1111"
                        exp_month: 12
                        exp_year: 2028
                        cardholder_name: John Doe
                      capabilities:
                        - psp-tokens
                        - card_update_subscriptions
                      included:
                        - type: card_update_subscriptions
                          id: CUSgkqZl3zRgXU3TFmtcDduFt
                          attributes:
                            created_at: "2026-04-13T09:00:00Z"
                            updated_at: "2026-04-13T09:00:00Z"
                            state: enrolled
                        - type: psp-tokens
                          attributes:
                            braintree:
                              attributes:
                                token: bt_tok_primary_123
                                network_transaction_id: 0163125478900412
                                state: active
                                created_at: "2026-04-13T10:00:00Z"
                                updated_at: "2026-04-13T10:00:00Z"
                                last_sync_status: provisioned
                            adyen:
                              attributes:
                                token: ady_tok_789
                                state: active
                                created_at: "2026-04-13T10:00:01Z"
                                updated_at: "2026-04-13T10:00:01Z"
                                last_sync_status: provisioned
                            stripe:
                              attributes:
                                token: pm_1RtExampleStripe
                                state: active
                                created_at: "2026-04-13T10:00:02Z"
                                updated_at: "2026-04-13T10:00:02Z"
                                last_sync_status: provisioned
        '401':
          description: No credentials were provided, or the provided credentials were expired.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '403':
          description: Valid credentials were provided, but they do not permit access to this resource.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '404':
          description: Resource not found.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '500':
          description: Internal Server Error.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '503':
          description: Service Unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
    delete:
      tags:
        - cards
      summary: Delete a card and automatically remove associated PSP tokens.
      description: |
        Proposed extension of the existing Delete Card API.

        In addition to removing the card object and existing associated services such as
        Network Tokens and Account Updater, this proposal also removes all PSP tokens tracked
        under `psp-tokens`. After the delete completes, VGS emits a webhook event in the same
        general notification style used by Account Updater events.
      operationId: deleteCard
      parameters:
        - $ref: '#/components/parameters/CardId'
      responses:
        '204':
          description: |
            Card deleted. Associated PSP tokens are deleted asynchronously as part
            of the same lifecycle operation, and a deletion webhook is emitted.
        '401':
          description: No credentials were provided, or the provided credentials were expired.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '403':
          description: Valid credentials were provided, but they do not permit access to this resource.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '404':
          description: Resource not found.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '500':
          description: Internal Server Error.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '503':
          description: Service Unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
  /cards/{card_id}/psp-tokens:
    post:
      tags:
        - psp-tokens
      summary: Synchronize PSP tokens for a card.
      description: |
        Proposed new PSP Tokens endpoint for the Token Sync feature.

        By default, this operation reconciles every configured PSP token for the card:

        - missing PSP tokens are created
        - existing PSP tokens are refreshed with the latest available card data
        - provider-level sync state is updated in the card's `psp-tokens` resource

        Clients can optionally pass a list of providers to limit the operation to one PSP
        or a subset of PSPs.
      operationId: syncPspTokens
      parameters:
        - $ref: '#/components/parameters/CardId'
      requestBody:
        required: false
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/SyncPspTokensRequest'
            examples:
              syncAllProviders:
                value:
                  data:
                    attributes:
                      mode: all
              syncSingleProvider:
                value:
                  data:
                    attributes:
                      mode: selected
                      providers:
                        - stripe
      responses:
        '200':
          description: PSP token synchronization completed synchronously.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/SyncPspTokensResponse'
              examples:
                syncCompleted:
                  value:
                    data:
                      id: PTJ_12345
                      attributes:
                        card_id: CRD12345
                        mode: all
                        status: completed
                        synchronized_providers:
                          - braintree
                          - adyen
                          - stripe
                        results:
                          braintree:
                            action: updated
                            token: bt_tok_primary_123
                            status: active
                          adyen:
                            action: created
                            token: ady_tok_789
                            status: active
                          stripe:
                            action: created
                            token: pm_1RtExampleStripe
                            status: active
                        created_at: "2026-04-13T10:35:00Z"
                        updated_at: "2026-04-13T10:35:02Z"
        '202':
          description: PSP token synchronization accepted for asynchronous processing.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/SyncPspTokensResponse'
              examples:
                syncAccepted:
                  value:
                    data:
                      type: psp_token_jobs
                      id: PTJ_12345
                      attributes:
                        card_id: CRD12345
                        mode: selected
                        status: queued
                        synchronized_providers:
                          - stripe
                        created_at: "2026-04-13T10:35:00Z"
                        updated_at: "2026-04-13T10:35:00Z"
        '400':
          description: The request was invalid.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '401':
          description: No credentials were provided, or the provided credentials were expired.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '403':
          description: Valid credentials were provided, but they do not permit access to this resource.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '404':
          description: Resource not found.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '424':
          description: Underlying provider or orchestration dependency is unavailable.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
        '500':
          description: Internal Server Error.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorDocument'
webhooks:
  pspTokenUpdated:
    post:
      summary: Proposed webhook emitted when a PSP token is updated in place.
      description: |
        This proposed webhook follows the same rough notification shape used by VGS Account
        Updater Events, but communicates that an existing PSP token was updated to reflect
        new card state such as an expiration-date change or PAN refresh.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PspTokenLifecycleWebhook'
            examples:
              pspTokenUpdatedWebhook:
                value:
                  description: PSP token updated
                  details:
                    card_id: CRD12345
                    card_last4: "1111"
                    card_first8: "41111111"
                    card_bin: "411111"
                    card_fingerprint: gpwktMGS8NtJervwhyZCEmTMfrrQMXX6vaoyrQmRsT6s
                    occurred_at: "2026-04-13T10:40:00Z"
                    provider: braintree
                    outcome: updated
                    reason: expiration_date_changed
                    token:
                      current: bt_tok_primary_123
                    network_transaction_id: 0163125478900412
                  event: cmp.psp_token.updated
                  fingerprint: 0734f9289326955d9b323c26b281fd8112f78ef56acd09a3dc2b8c7a9a13c948
                  grouping: every_single
                  id: bba88542-cfb1-42f8-8f63-aba60d62917c
                  integration_id: IN6VgSvLVRV2j2iuxugi4mh7
                  occurrence: 1
                  org_id: AC21kskfJCLyrkVjmAT6gU9X
                  producer:
                    application_name: token-sync-api
                    application_protocol: http
                  scope: vault
                  summary: PSP token for provider "braintree" was updated for card "CRD12345"
                  tenant: ACT9eeBkBsxXe9wWJkQdpG2kC
                  timestamp: "2026-04-13T10:40:01.809Z"
      responses:
        '200':
          description: Return 200 to acknowledge receipt.
  pspTokenReplaced:
    post:
      summary: Proposed webhook emitted when a PSP token is replaced with a new token.
      description: |
        This proposed webhook is used when the provider does not support in-place token
        updates and VGS creates a replacement token instead.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PspTokenLifecycleWebhook'
            examples:
              pspTokenReplacedWebhook:
                value:
                  description: PSP token replaced
                  details:
                    card_id: CRD12345
                    card_last4: "4242"
                    card_first8: "42424242"
                    card_bin: "424242"
                    card_fingerprint: gpwktMGS8NtJervwhyZCEmTMfrrQMXX6vaoyrQmRsT6s
                    occurred_at: "2026-04-13T10:45:00Z"
                    provider: stripe
                    outcome: replaced
                    reason: pan_changed
                    token:
                      previous: pm_1OldStripeToken
                      current: pm_1RtExampleStripe
                    network_transaction_id: 0163125478900412
                  event: cmp.psp_token.replaced
                  fingerprint: 5f54f9289326955d9b323c26b281fd8112f78ef56acd09a3dc2b8c7a9a13c977
                  grouping: every_single
                  id: 5bb88542-cfb1-42f8-8f63-aba60d62917c
                  integration_id: IN6VgSvLVRV2j2iuxugi4mh7
                  occurrence: 1
                  org_id: AC21kskfJCLyrkVjmAT6gU9X
                  producer:
                    application_name: token-sync-api
                    application_protocol: http
                  scope: vault
                  summary: PSP token for provider "stripe" was replaced for card "CRD12345"
                  tenant: ACT9eeBkBsxXe9wWJkQdpG2kC
                  timestamp: "2026-04-13T10:45:01.809Z"
      responses:
        '200':
          description: Return 200 to acknowledge receipt.
  pspTokenDeleted:
    post:
      summary: Proposed webhook emitted when a PSP token is deleted or retired.
      description: |
        This proposed webhook is used when a PSP token is deleted, disabled, or retired, for
        example because the underlying card account moved to `closed`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PspTokenLifecycleWebhook'
            examples:
              pspTokenDeletedWebhook:
                value:
                  description: PSP token deleted
                  details:
                    card_id: CRD12345
                    card_last4: "1111"
                    card_first8: "41111111"
                    card_bin: "411111"
                    card_fingerprint: gpwktMGS8NtJervwhyZCEmTMfrrQMXX6vaoyrQmRsT6s
                    occurred_at: "2026-04-13T10:50:00Z"
                    provider: adyen
                    outcome: deleted
                    reason: card_closed
                    token:
                      previous: ady_tok_789
                    network_transaction_id: 0163125478900412
                  event: cmp.psp_token.deleted
                  fingerprint: 9a54f9289326955d9b323c26b281fd8112f78ef56acd09a3dc2b8c7a9a13c912
                  grouping: every_single
                  id: 9bb88542-cfb1-42f8-8f63-aba60d62917c
                  integration_id: IN6VgSvLVRV2j2iuxugi4mh7
                  occurrence: 1
                  org_id: AC21kskfJCLyrkVjmAT6gU9X
                  producer:
                    application_name: token-sync-api
                    application_protocol: http
                  scope: vault
                  summary: PSP token for provider "adyen" was deleted for card "CRD_merchant_12345"
                  tenant: ACT9eeBkBsxXe9wWJkQdpG2kC
                  timestamp: "2026-04-13T10:50:01.809Z"
      responses:
        '200':
          description: Return 200 to acknowledge receipt.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    CardId:
      name: card_id
      in: path
      required: true
      description: Unique VGS card identifier.
      schema:
        type: string
        example: CRD12345
  schemas:
    CreateCardRequest:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
          properties:
            type:
              type: string
              enum:
                - cards
            attributes:
              $ref: '#/components/schemas/CardCreateAttributes'
            capabilities:
              type: array
              description: |
                Optional card capabilities. In this proposed extension, `psp-tokens` is the
                default when the field is omitted.
              items:
                type: string
              default:
                - psp-tokens
              example:
                - psp-tokens
                - card_update_subscriptions
            meta:
              type: object
              additionalProperties:
                type: string
    CardCreateAttributes:
      type: object
      properties:
        pan:
          type: string
          example: "4111111111111111"
        cvc:
          type: string
          example: "123"
        exp_month:
          type: integer
          example: 12
        exp_year:
          type: integer
          example: 2028
        cardholder_name:
          type: string
          example: John Doe
    CardResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/CardObject'
        metadata:
          type: object
          additionalProperties: true
        jsonapi:
          type: object
          additionalProperties: true
    CardObject:
      type: object
      required:
        - id
        - type
        - attributes
      properties:
        id:
          type: string
          example: CRD12345
        type:
          type: string
          enum:
            - cards
        attributes:
          $ref: '#/components/schemas/CardAttributes'
        capabilities:
          type: array
          items:
            type: string
        included:
          type: array
          items:
            oneOf:
              - $ref: '#/components/schemas/CardUpdateSubscriptionResource'
              - $ref: '#/components/schemas/PspTokensResource'
              - $ref: '#/components/schemas/NetworkTokenResource'
    CardAttributes:
      type: object
      properties:
        pan_alias:
          type: string
          example: tok_live_abcdefg
        cvc_alias:
          type: string
          example: tok_live_vwxyz
        token_type:
          type: string
          example: pan
        bin:
          type: string
          example: "411111"
        first8:
          type: string
          example: "41111111"
        last4:
          type: string
          example: "1111"
        exp_month:
          type: integer
          example: 12
        exp_year:
          type: integer
          example: 2028
        cardholder_name:
          type: string
          example: John Doe
        card_fingerprint:
          type: string
          example: gpwktMGS8NtJervwhyZCEmTMfrrQMXX6vaoyrQmRsT6s
    CardUpdateSubscriptionResource:
      type: object
      properties:
        type:
          type: string
          enum:
            - card_update_subscriptions
        id:
          type: string
          example: CUSgkqZl3zRgXU3TFmtcDduFt
        attributes:
          type: object
          properties:
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
            state:
              type: string
              example: enrolled
    PspTokensResource:
      type: object
      properties:
        type:
          type: string
          enum:
            - psp-tokens
        attributes:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/PspTokenProvider'
    PspTokenProvider:
      type: object
      properties:
        attributes:
          type: object
          properties:
            token:
              type: string
              example: bt_tok_primary_123
            network_transaction_id:
              type: string
              example: 0163125478900412
            state:
              type: string
              example: active
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
            last_sync_status:
              type: string
              example: provisioned
    NetworkTokenResource:
      type: object
      properties:
        type:
          type: string
          enum:
            - network-tokens
        attributes:
          type: object
          additionalProperties: true
    SyncPspTokensRequest:
      type: object
      properties:
        data:
          type: object
          properties:
            type:
              type: string
              enum:
                - psp_token_jobs
            attributes:
              type: object
              properties:
                mode:
                  type: string
                  enum:
                    - all
                    - selected
                  default: all
                providers:
                  type: array
                  description: Optional list of PSP providers to synchronize.
                  items:
                    type: string
                    example: stripe
    SyncPspTokensResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            type:
              type: string
              enum:
                - psp_token_jobs
            id:
              type: string
              example: PTJ_12345
            attributes:
              type: object
              properties:
                card_id:
                  type: string
                  example: CRD12345
                mode:
                  type: string
                  example: all
                status:
                  type: string
                  enum:
                    - queued
                    - processing
                    - completed
                    - failed
                synchronized_providers:
                  type: array
                  items:
                    type: string
                results:
                  type: object
                  additionalProperties:
                    $ref: '#/components/schemas/PspTokenJobResult'
                created_at:
                  type: string
                  format: date-time
                updated_at:
                  type: string
                  format: date-time
    PspTokenJobResult:
      type: object
      properties:
        action:
          type: string
          enum:
            - created
            - updated
            - unchanged
            - failed
        token:
          type: string
          example: bt_tok_primary_123
        network_transaction_id:
          type: string
          example: 0163125478900412
        status:
          type: string
          example: active
        error:
          type: string
    PspTokenLifecycleWebhook:
      type: object
      required:
        - description
        - details
        - event
        - fingerprint
        - grouping
        - id
        - integration_id
        - occurrence
        - org_id
        - producer
        - scope
        - summary
        - tenant
        - timestamp
      properties:
        description:
          type: string
          example: PSP token updated
        details:
          type: object
          required:
            - card_id
            - occurred_at
            - provider
            - outcome
          properties:
            card_id:
              type: string
              example: CRD12345
            card_last4:
              type: string
              example: "1111"
            card_first8:
              type: string
              example: "41111111"
            card_bin:
              type: string
              example: "411111"
            card_fingerprint:
              type: string
              example: gpwktMGS8NtJervwhyZCEmTMfrrQMXX6vaoyrQmRsT6s
            occurred_at:
              type: string
              format: date-time
            provider:
              type: string
              example: braintree
            outcome:
              type: string
              enum:
                - updated
                - replaced
                - deleted
            reason:
              type: string
              example: expiration_date_changed
            token:
              type: object
              properties:
                previous:
                  type: string
                  example: pm_1OldStripeToken
                current:
                  type: string
                  example: bt_tok_primary_123
            network_transaction_id:
              type: string
              example: 0163125478900412
        event:
          type: string
          example: cmp.psp_token.updated
        fingerprint:
          type: string
        grouping:
          type: string
          example: every_single
        id:
          type: string
        integration_id:
          type: string
        occurrence:
          type: integer
          example: 1
        org_id:
          type: string
        producer:
          type: object
          properties:
            application_name:
              type: string
              example: token-sync-api
            application_protocol:
              type: string
              example: http
        scope:
          type: string
          example: vault
        summary:
          type: string
        tenant:
          type: string
        timestamp:
          type: string
          format: date-time
    ErrorDocument:
      type: object
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              status:
                type: string
              code:
                type: string
              title:
                type: string
              detail:
                type: string
