openapi: 3.1.0
info:
  title: digid pay API
  version: 0.1.0-contract
  description: >
    The digid pay merchant API. Token-only: card data never enters digid pay
    systems. Card capture happens inside digid pay Secure Fields (hosted
    tokenization) and every charge path consumes a token.
    Base URL: https://api.digid.cc/v1
    This file is the contract source of truth until the facade publishes
    api.digid.cc/openapi.json.
  contact:
    name: digid pay
    email: contact@digid.cc
  license:
    name: Proprietary — digid pay merchant documentation
servers:
  - url: https://api.digid.cc/v1
    description: Production API
  - url: https://api.digid.cc/v1
    description: Sandbox (same host, sandbox-scoped keys)
security:
  - bearerAuth: []
paths:
  /payment_intents:
    post:
      operationId: createPaymentIntent
      summary: Create a PaymentIntent
      description: >
        Creates a PaymentIntent in `requires_payment_method`. Confirm it with a
        payment token captured from digid pay Secure Fields. Idempotent under
        the `Idempotency-Key` header.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentIntentCreate'
      responses:
        '200':
          description: PaymentIntent created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntent'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/CardError'
        '429':
          $ref: '#/components/responses/RateLimited'
    get:
      operationId: listPaymentIntents
      summary: List PaymentIntents
      description: Returns a paginated list of PaymentIntents for the merchant.
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/StartingAfter'
        - name: status
          in: query
          description: Filter by PaymentIntent status.
          required: false
          schema:
            $ref: '#/components/schemas/PaymentIntentStatus'
      responses:
        '200':
          description: A list of PaymentIntents
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PaymentIntent'
                  has_more:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
  /payment_intents/{payment_intent}:
    get:
      operationId: retrievePaymentIntent
      summary: Retrieve a PaymentIntent
      parameters:
        - $ref: '#/components/parameters/PaymentIntentId'
      responses:
        '200':
          description: The PaymentIntent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /payment_intents/{payment_intent}/confirm:
    post:
      operationId: confirmPaymentIntent
      summary: Confirm a PaymentIntent with a payment token
      description: >
        Confirms the PaymentIntent with a payment token returned by digid pay
        Secure Fields. May transition to `requires_action` for an SCA
        challenge or `succeeded`. Idempotent under the `Idempotency-Key`
        header.
      parameters:
        - $ref: '#/components/parameters/PaymentIntentId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payment_method]
              properties:
                payment_method:
                  type: string
                  description: A token returned by digid pay Secure Fields.
                  example: pmt_test_1Bx...token
      responses:
        '200':
          description: The confirmed PaymentIntent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntent'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/CardError'
        '429':
          $ref: '#/components/responses/RateLimited'
  /payment_intents/{payment_intent}/cancel:
    post:
      operationId: cancelPaymentIntent
      summary: Cancel a PaymentIntent
      description: >
        Cancels a PaymentIntent that is not yet terminal. Only allowed from
        `requires_payment_method`, `requires_action` or `requires_approval`.
        Idempotent under the `Idempotency-Key` header.
      parameters:
        - $ref: '#/components/parameters/PaymentIntentId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: The cancelled PaymentIntent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntent'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /payment_intents/{payment_intent}/approve:
    post:
      operationId: approvePaymentIntent
      summary: Approve an agent-initiated PaymentIntent
      description: >
        Approves a PaymentIntent in `requires_approval` (agent-initiated
        payments, FR-14). Approval moves the intent to `processing`. Only a
        designated merchant approver may approve.
      parameters:
        - $ref: '#/components/parameters/PaymentIntentId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: The approved PaymentIntent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntent'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /refunds:
    post:
      operationId: createRefund
      summary: Create a refund
      description: >
        Refunds a successful PaymentIntent in full or in part. Amount must be
        in the original currency's minor units and no greater than the
        original capture. Idempotent under the `Idempotency-Key` header.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundCreate'
      responses:
        '200':
          description: Refund created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    get:
      operationId: listRefunds
      summary: List refunds
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/StartingAfter'
        - name: payment_intent
          in: query
          required: false
          description: Filter refunds by PaymentIntent id.
          schema:
            type: string
      responses:
        '200':
          description: A list of refunds
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Refund'
                  has_more:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
  /refunds/{refund}:
    get:
      operationId: retrieveRefund
      summary: Retrieve a refund
      parameters:
        - $ref: '#/components/parameters/RefundId'
      responses:
        '200':
          description: The refund
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /keys:
    get:
      operationId: listKeys
      summary: List API keys
      description: Lists the merchant's key metadata. Secret values are never returned.
      responses:
        '200':
          description: A list of API keys
          content:
            application/json:
              schema:
                type: object
                required: [object, data]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createApiKey
      summary: Create an API key pair
      description: >
        Creates a publishable/secret key pair for a mode. The secret value is
        returned exactly once, at creation. Stored hashed-at-rest.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label, mode]
              properties:
                label:
                  type: string
                  description: Human-readable label shown in the dashboard.
                mode:
                  type: string
                  enum: [sandbox, live]
                  description: Sandbox keys exercise test rails only.
                scopes:
                  type: array
                  items:
                    type: string
                  description: Optional scope labels restricting the key.
      responses:
        '200':
          description: Key pair created (secret shown once)
          content:
            application/json:
              schema:
                type: object
                required: [publishable_key, secret_key]
                properties:
                  publishable_key:
                    type: string
                  secret_key:
                    type: string
                    description: Shown only once.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /keys/{key_id}:
    delete:
      operationId: revokeApiKey
      summary: Revoke an API key
      description: Revokes a key immediately. Revoked keys fail within seconds.
      parameters:
        - $ref: '#/components/parameters/KeyId'
      responses:
        '204':
          description: Revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /keys/{key_id}/rotate:
    post:
      operationId: rotateApiKey
      summary: Rotate an API key pair
      description: >
        Rotates a key pair, issuing a new secret and retiring the old one
        after a short overlap. Old secret stops working on rotation.
      parameters:
        - $ref: '#/components/parameters/KeyId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: New secret key (shown once)
          content:
            application/json:
              schema:
                type: object
                required: [secret_key]
                properties:
                  secret_key:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /webhook_endpoints:
    get:
      operationId: listWebhookEndpoints
      summary: List webhook endpoints
      responses:
        '200':
          description: A list of webhook endpoints
          content:
            application/json:
              schema:
                type: object
                required: [object, data]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEndpoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createWebhookEndpoint
      summary: Register a webhook endpoint
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                  description: HTTPS endpoint receiving signed events.
                events:
                  type: array
                  items:
                    $ref: '#/components/schemas/WebhookEventType'
                description:
                  type: string
                enabled:
                  type: boolean
                  default: true
      responses:
        '201':
          description: Webhook endpoint registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /webhook_endpoints/{webhook_endpoint}:
    get:
      operationId: retrieveWebhookEndpoint
      summary: Retrieve a webhook endpoint
      parameters:
        - $ref: '#/components/parameters/WebhookEndpointId'
      responses:
        '200':
          description: The webhook endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: updateWebhookEndpoint
      summary: Update a webhook endpoint
      parameters:
        - $ref: '#/components/parameters/WebhookEndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    $ref: '#/components/schemas/WebhookEventType'
                description:
                  type: string
                enabled:
                  type: boolean
      responses:
        '200':
          description: Updated webhook endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteWebhookEndpoint
      summary: Delete a webhook endpoint
      parameters:
        - $ref: '#/components/parameters/WebhookEndpointId'
      responses:
        '204':
          description: Deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /webhook_endpoints/{webhook_endpoint}/deliveries:
    get:
      operationId: listWebhookDeliveries
      summary: List webhook deliveries
      description: Delivery log for an endpoint, retained 12 months.
      parameters:
        - $ref: '#/components/parameters/WebhookEndpointId'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/StartingAfter'
      responses:
        '200':
          description: A list of deliveries
          content:
            application/json:
              schema:
                type: object
                required: [object, data]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookDelivery'
                  has_more:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
  /webhook_endpoints/{webhook_endpoint}/deliveries/{delivery}/replay:
    post:
      operationId: replayWebhookDelivery
      summary: Replay a webhook delivery
      parameters:
        - $ref: '#/components/parameters/WebhookEndpointId'
        - name: delivery
          in: path
          required: true
          schema:
            type: string
      responses:
        '202':
          description: Replay scheduled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        Use `Authorization: Bearer <secret_key>` for server-side calls and
        `<publishable_key>` where a client-safe key is documented. Secret keys
        are rejected from browser contexts.
  parameters:
    PaymentIntentId:
      name: payment_intent
      in: path
      required: true
      schema:
        type: string
        example: pi_1Ab...
    RefundId:
      name: refund
      in: path
      required: true
      schema:
        type: string
        example: re_1Cd...
    KeyId:
      name: key_id
      in: path
      required: true
      schema:
        type: string
    WebhookEndpointId:
      name: webhook_endpoint
      in: path
      required: true
      schema:
        type: string
        example: we_1Ef...
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Client-supplied idempotency key. Replays with the same key return the original result.
      schema:
        type: string
    Limit:
      name: limit
      in: query
      description: Max results per page (1–100).
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    StartingAfter:
      name: starting_after
      in: query
      description: Opaque cursor from the previous page.
      required: false
      schema:
        type: string
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    CardError:
      description: The card issuer declined or the token could not be charged
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource not found (cross-tenant lookups also return 404)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [type, code, message]
          properties:
            type:
              type: string
              description: High-level error class.
              enum:
                - invalid_request_error
                - card_error
                - authentication_error
                - idempotency_error
                - rate_limit_error
                - approval_error
                - api_error
            code:
              type: string
              description: Machine-readable digid pay code.
              example: card_declined
            message:
              type: string
            param:
              type: string
              description: Request parameter the error relates to, if any.
              nullable: true
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: integer
          description: Amount in the currency's minor units.
          example: 2490
        currency:
          type: string
          description: ISO 4217 currency code.
          example: eur
    PaymentIntentCreate:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: integer
          description: Amount in the currency's minor units.
          example: 2490
        currency:
          type: string
          description: ISO 4217 currency code.
          example: eur
        merchant_reference:
          type: string
          description: Your order or cart reference.
        description:
          type: string
        metadata:
          type: object
          additionalProperties:
            type: string
        capture_method:
          type: string
          enum: [automatic, manual]
          default: automatic
          description: Manual capture is reserved for the upcoming capture contract; only automatic is enabled at launch.
    PaymentIntent:
      type: object
      required: [id, object, amount, currency, status, livemode]
      properties:
        id:
          type: string
          example: pi_1Ab...
        object:
          type: string
          const: payment_intent
        amount:
          type: integer
          description: Minor units.
        currency:
          type: string
        status:
          $ref: '#/components/schemas/PaymentIntentStatus'
        capture_method:
          type: string
          enum: [automatic, manual]
        merchant_reference:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        metadata:
          type: object
          additionalProperties:
            type: string
        livemode:
          type: boolean
          description: false in sandbox.
        client_secret:
          type: string
          description: Present when a payment method must still be attached/confirmed.
        next_action:
          type: object
          nullable: true
          description: Present when status is requires_action.
          properties:
            type:
              type: string
              enum: [redirect_to_url, challenge]
            redirect_to_url:
              type: string
              nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PaymentIntentStatus:
      type: string
      description: |
        requires_payment_method → processing → succeeded | failed | cancelled.
        requires_action pauses for an SCA challenge; requires_approval pauses
        agent-initiated payments until a human approves.
      enum:
        - requires_payment_method
        - processing
        - requires_action
        - requires_approval
        - succeeded
        - failed
        - cancelled
    RefundCreate:
      type: object
      required: [payment_intent]
      properties:
        payment_intent:
          type: string
          description: The PaymentIntent to refund.
        amount:
          type: integer
          description: Minor units. Omit for a full refund.
        reason:
          type: string
          enum: [duplicate, fraudulent, requested_by_customer]
          description: Optional, informational.
        metadata:
          type: object
          additionalProperties:
            type: string
    Refund:
      type: object
      required: [id, object, amount, currency, payment_intent, status]
      properties:
        id:
          type: string
          example: re_1Cd...
        object:
          type: string
          const: refund
        amount:
          type: integer
          description: Minor units.
        currency:
          type: string
        payment_intent:
          type: string
        reason:
          type: string
          nullable: true
        status:
          type: string
          enum: [pending, succeeded, failed]
        metadata:
          type: object
          additionalProperties:
            type: string
        created_at:
          type: string
          format: date-time
    ApiKey:
      type: object
      required: [id, object, label, mode, livemode, created_at]
      properties:
        id:
          type: string
          example: key_1Gh...
        object:
          type: string
          const: api_key
        label:
          type: string
        mode:
          type: string
          enum: [sandbox, live]
        livemode:
          type: boolean
        scopes:
          type: array
          items:
            type: string
        prefix:
          type: string
          description: Short visible prefix (e.g. pk_sandbox_...) for identification.
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    WebhookEndpoint:
      type: object
      required: [id, object, url, events, enabled, secret]
      properties:
        id:
          type: string
          example: we_1Ef...
        object:
          type: string
          const: webhook_endpoint
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        description:
          type: string
          nullable: true
        enabled:
          type: boolean
        secret:
          type: string
          description: |
            Your HMAC signing secret, shown once at creation. Verify the
            Digidpay-Signature header on every received event.
        created_at:
          type: string
          format: date-time
    WebhookDelivery:
      type: object
      required: [id, object, event_id, url, status, created_at]
      properties:
        id:
          type: string
        object:
          type: string
          const: webhook_delivery
        event_id:
          type: string
          description: Identifier of the event payload delivered.
        event_type:
          $ref: '#/components/schemas/WebhookEventType'
        url:
          type: string
        status:
          type: string
          enum: [pending, delivered, failed]
        attempts:
          type: integer
          description: Delivery attempts so far.
        created_at:
          type: string
          format: date-time
    WebhookEventType:
      type: string
      enum:
        - payment_intent.processing
        - payment_intent.succeeded
        - payment_intent.payment_failed
        - payment_intent.cancelled
        - payment_intent.requires_action
        - payment_intent.requires_approval
        - refund.created
        - refund.succeeded
        - refund.failed
