openapi: 3.2.0
info:
  title: Vatly API
  version: '1.0'
  description: |
    Vatly is a Merchant of Record billing platform for European SaaS companies.
    This API enables you to manage checkouts, customers, orders, subscriptions, refunds, and more.

    ## Authentication

    The Vatly API uses Bearer token authentication. All API requests must include an `Authorization` header:

    ```
    Authorization: Bearer {your_api_token}
    ```

    ### Live vs Test Mode

    API tokens determine the environment mode:
    - Tokens prefixed with `live_` access **production** data
    - Tokens prefixed with `test_` access **sandbox** data

    Resources created in one mode cannot be accessed or referenced from the other mode.
    For example, a checkout created with a test token cannot reference a customer created with a live token.

    ## Pagination

    List endpoints use cursor-based pagination with the following parameters:

    | Parameter | Type | Description |
    |-----------|------|-------------|
    | `limit` | integer | Number of results per page (default: 10, max: 100) |
    | `startingAfter` | string | Cursor ID to fetch results after |
    | `endingBefore` | string | Cursor ID to fetch results before |

    `startingAfter` and `endingBefore` are mutually exclusive.

    ## Money Format

    All monetary values are represented as objects with `value` (string) and `currency` (ISO 4217 code):

    ```json
    {
      "value": "99.99",
      "currency": "EUR"
    }
    ```

    Values are strings to preserve decimal precision.

    ## Errors

    The API returns standard HTTP status codes and JSON error responses:

    | Status | Description |
    |--------|-------------|
    | 400 | Bad Request - Invalid request format |
    | 401 | Unauthorized - Missing or invalid token |
    | 403 | Forbidden - Token invalid or insufficient permissions |
    | 404 | Not Found - Resource doesn't exist |
    | 422 | Unprocessable Entity - Validation failed |
    | 429 | Too Many Requests - Rate limit exceeded |
    | 500 | Internal Server Error |
  contact:
    name: Vatly Support
    url: https://vatly.com
    email: support@vatly.com
  license:
    name: Proprietary
    url: https://vatly.com/terms
servers:
  - url: https://api.vatly.com/v1
    description: Production API
  - url: https://api.staging.vatly.com/v1
    description: Staging API
security:
  - BearerAuth: []
tags:
  - name: Checkouts
    description: |
      Create and manage hosted checkout sessions.
      Checkouts allow customers to purchase one-off products and subscription plans through a hosted payment page.
  - name: Customers
    description: |
      Manage customer records.
      Customers are identified by email and can have multiple subscriptions and orders.
  - name: One-Off Products
    description: |
      View available one-off products.
      Products are configured in the Vatly dashboard and can be added to checkouts.
  - name: Subscription Plans
    description: |
      View available subscription plans.
      Plans define recurring billing intervals and pricing.
  - name: Orders
    description: |
      View orders and request invoice address updates.
      Orders are created when checkouts complete or subscriptions renew.
  - name: Refunds
    description: |
      Create and manage refunds for paid orders.
      Supports both partial (per-item) and full refunds.
  - name: Chargebacks
    description: |
      View chargebacks initiated by payment providers.
      Chargebacks are created automatically when a customer disputes a payment.
  - name: Subscriptions
    description: |
      Manage customer subscriptions.
      Update plans, quantities, billing details, or cancel subscriptions.
paths:
  /checkouts:
    get:
      operationId: listCheckouts
      summary: List all checkouts
      description: |
        Returns a paginated list of checkouts for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.
      tags:
        - Checkouts
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of checkouts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Checkout'
              example:
                - id: chk_abc123
                  resource: checkout
                  merchantId: mer_xyz789
                  orderId: null
                  testmode: false
                  redirectUrlSuccess: https://example.com/success
                  redirectUrlCanceled: https://example.com/canceled
                  metadata: {}
                  status: created
                  expiresAt: '2024-01-16T10:30:00Z'
                  createdAt: '2024-01-15T10:30:00Z'
                  links:
                    checkoutUrl:
                      href: https://checkout.vatly.com/chk_abc123
                      type: text/html
                    self:
                      href: https://api.vatly.com/v1/checkouts/chk_abc123
                      type: application/json
                    order: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
    post:
      operationId: createCheckout
      summary: Create a checkout
      description: |
        Creates a new hosted checkout session.

        The checkout URL in the response can be used to redirect customers to complete their purchase.
        Checkouts expire after a configurable period (default: 24 hours).

        **Products:**
        - Include one or more products using their `prod_` or `plan_` IDs
        - Mix one-off products and subscription plans in the same checkout
        - Override prices for specific products using the `price` field
        - Add trial periods to subscription plans using `trialDays`

        **Customer association:**
        - Optionally link to an existing customer using `customerId`
        - If not provided, a new customer is created based on checkout form input
      tags:
        - Checkouts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCheckoutRequest'
            examples:
              oneOffProduct:
                summary: Checkout with one-off product
                value:
                  redirectUrlSuccess: https://example.com/success
                  redirectUrlCanceled: https://example.com/canceled
                  products:
                    - id: prod_abc123
                      quantity: 1
              subscriptionWithTrial:
                summary: Subscription plan with trial
                value:
                  redirectUrlSuccess: https://example.com/success
                  redirectUrlCanceled: https://example.com/canceled
                  customerId: cus_existing123
                  products:
                    - id: plan_monthly
                      trialDays: 14
              customPrice:
                summary: Product with custom price
                value:
                  redirectUrlSuccess: https://example.com/success
                  redirectUrlCanceled: https://example.com/canceled
                  products:
                    - id: prod_abc123
                      quantity: 2
                      price:
                        value: '49.99'
                        currency: EUR
                  metadata:
                    campaign: summer-sale
      responses:
        '201':
          description: Checkout created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Checkout'
              example:
                id: chk_new123
                resource: checkout
                merchantId: mer_xyz789
                orderId: null
                testmode: false
                redirectUrlSuccess: https://example.com/success
                redirectUrlCanceled: https://example.com/canceled
                metadata:
                  campaign: summer-sale
                status: created
                expiresAt: '2024-01-16T10:30:00Z'
                createdAt: '2024-01-15T10:30:00Z'
                links:
                  checkoutUrl:
                    href: https://checkout.vatly.com/chk_new123
                    type: text/html
                  self:
                    href: https://api.vatly.com/v1/checkouts/chk_new123
                    type: application/json
                  order: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /checkouts/{checkoutId}:
    get:
      operationId: getCheckout
      summary: Get a checkout
      description: |
        Retrieves a specific checkout by its ID.
        Use this to check the status of a checkout or get the order ID after completion.
      tags:
        - Checkouts
      parameters:
        - $ref: '#/components/parameters/checkoutId'
      responses:
        '200':
          description: Checkout details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Checkout'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /customers:
    get:
      operationId: listCustomers
      summary: List all customers
      description: |
        Returns a paginated list of customers for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.
      tags:
        - Customers
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of customers
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
    post:
      operationId: createCustomer
      summary: Create a customer
      description: |
        Creates a new customer record.

        Customers are uniquely identified by email within each testmode.
        Creating a customer with an email that already exists will return a validation error.

        **Use cases:**
        - Pre-create customers before checkout
        - Link existing users from your system to Vatly
      tags:
        - Customers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCustomerRequest'
            example:
              email: customer@example.com
              metadata:
                userId: user_12345
      responses:
        '201':
          description: Customer created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
              example:
                id: cus_abc123
                resource: customer
                testmode: false
                email: customer@example.com
                createdAt: '2024-01-15T10:30:00Z'
                metadata:
                  userId: user_12345
                links:
                  self:
                    href: https://api.vatly.com/v1/customers/cus_abc123
                    type: application/json
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /customers/{customerId}:
    get:
      operationId: getCustomer
      summary: Get a customer
      description: Retrieves a specific customer by their ID.
      tags:
        - Customers
      parameters:
        - $ref: '#/components/parameters/customerId'
      responses:
        '200':
          description: Customer details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /customers/{customerId}/subscriptions:
    get:
      operationId: listCustomerSubscriptions
      summary: List customer subscriptions
      description: |
        Returns a paginated list of subscriptions for a specific customer.
        Includes subscriptions in all states (active, canceled, etc.).
      tags:
        - Customers
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/customerId'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of customer's subscriptions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /customers/{customerId}/subscriptions/{subscriptionId}:
    get:
      operationId: getCustomerSubscription
      summary: Get a customer subscription
      description: |
        Retrieves a specific subscription for a customer.
        The subscription must belong to the specified customer.
      tags:
        - Customers
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/customerId'
        - $ref: '#/components/parameters/subscriptionId'
      responses:
        '200':
          description: Subscription details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /one-off-products:
    get:
      operationId: listOneOffProducts
      summary: List all one-off products
      description: |
        Returns a paginated list of one-off products for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.

        Only products with `approved` status can be used in checkouts.
      tags:
        - One-Off Products
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of one-off products
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/OneOffProduct'
              example:
                - id: prod_abc123
                  resource: one_off_product
                  testmode: false
                  name: Premium License
                  description: Lifetime access to all premium features
                  basePrice:
                    value: '299.00'
                    currency: EUR
                  status: approved
                  createdAt: '2024-01-15T10:30:00Z'
                  links:
                    self:
                      href: https://api.vatly.com/v1/one-off-products/prod_abc123
                      type: application/json
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /one-off-products/{oneOffProductId}:
    get:
      operationId: getOneOffProduct
      summary: Get a one-off product
      description: Retrieves a specific one-off product by its ID.
      tags:
        - One-Off Products
      parameters:
        - $ref: '#/components/parameters/oneOffProductId'
      responses:
        '200':
          description: One-off product details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OneOffProduct'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /subscription-plans:
    get:
      operationId: listSubscriptionPlans
      summary: List all subscription plans
      description: |
        Returns a paginated list of subscription plans for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.

        Only plans with `approved` status can be used in checkouts.
      tags:
        - Subscription Plans
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of subscription plans
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SubscriptionPlan'
              example:
                - id: plan_monthly
                  resource: subscription_plan
                  testmode: false
                  name: Pro Monthly
                  description: Full access to all Pro features, billed monthly
                  basePrice:
                    value: '29.00'
                    currency: EUR
                  interval: month
                  intervalCount: 1
                  status: approved
                  createdAt: '2024-01-15T10:30:00Z'
                  links:
                    self:
                      href: https://api.vatly.com/v1/subscription-plans/plan_monthly
                      type: application/json
                - id: plan_yearly
                  resource: subscription_plan
                  testmode: false
                  name: Pro Yearly
                  description: Full access to all Pro features, billed yearly
                  basePrice:
                    value: '290.00'
                    currency: EUR
                  interval: year
                  intervalCount: 1
                  status: approved
                  createdAt: '2024-01-15T10:30:00Z'
                  links:
                    self:
                      href: https://api.vatly.com/v1/subscription-plans/plan_yearly
                      type: application/json
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /subscription-plans/{subscriptionPlanId}:
    get:
      operationId: getSubscriptionPlan
      summary: Get a subscription plan
      description: Retrieves a specific subscription plan by its ID.
      tags:
        - Subscription Plans
      parameters:
        - $ref: '#/components/parameters/subscriptionPlanId'
      responses:
        '200':
          description: Subscription plan details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionPlan'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /orders:
    get:
      operationId: listOrders
      summary: List all orders
      description: |
        Returns a paginated list of orders for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.
      tags:
        - Orders
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of orders
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /orders/{orderId}:
    get:
      operationId: getOrder
      summary: Get an order
      description: |
        Retrieves a specific order by its ID.
        Includes full order details with line items, totals, and billing information.
      tags:
        - Orders
      parameters:
        - $ref: '#/components/parameters/orderId'
      responses:
        '200':
          description: Order details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
              example:
                id: ord_abc123
                resource: order
                merchantId: mer_xyz789
                customerId: cus_def456
                testmode: false
                metadata: {}
                paymentMethod: ideal
                createdAt: '2024-01-15T10:30:00Z'
                status: paid
                invoiceNumber: INV-2024-0001
                total:
                  value: '35.09'
                  currency: EUR
                subtotal:
                  value: '29.00'
                  currency: EUR
                taxSummary:
                  value: '6.09'
                  currency: EUR
                lines:
                  - id: oli_abc123
                    resource: orderline
                    description: Pro Monthly Subscription
                    quantity: 1
                    basePrice:
                      value: '29.00'
                      currency: EUR
                    total:
                      value: '35.09'
                      currency: EUR
                    subtotal:
                      value: '29.00'
                      currency: EUR
                    taxes:
                      value: '6.09'
                      currency: EUR
                merchantDetails:
                  fullName: Vatly B.V.
                  companyName: Vatly
                  vatNumber: NL123456789B01
                  streetAndNumber: Keizersgracht 123
                  city: Amsterdam
                  postalCode: 1015 CJ
                  country: NL
                  email: billing@vatly.com
                customerDetails:
                  fullName: John Doe
                  companyName: Acme Corp
                  streetAndNumber: 123 Main Street
                  city: Berlin
                  postalCode: '10115'
                  country: DE
                  email: john@acme.com
                links:
                  self:
                    href: https://api.vatly.com/v1/orders/ord_abc123
                    type: application/json
                  customer:
                    href: https://api.vatly.com/v1/customers/cus_def456
                    type: application/json
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /orders/{orderId}/request-address-update-link:
    post:
      operationId: requestOrderAddressUpdateLink
      summary: Request address update link
      description: |
        Generates a signed URL that allows the customer to update their billing address for this order.

        The link is valid for a limited time (typically 24 hours) and can be sent to the customer
        to self-service update their invoice details.

        **Use cases:**
        - Customer needs to add/update VAT number
        - Customer needs to correct billing address
        - Customer needs to change company name on invoice
      tags:
        - Orders
      parameters:
        - $ref: '#/components/parameters/orderId'
      responses:
        '200':
          description: Address update link generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddressUpdateLink'
              example:
                href: https://vatly.com/invoices/ord_abc123/edit?signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                type: text/html
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /orders/{orderId}/refunds:
    get:
      operationId: listOrderRefunds
      summary: List order refunds
      description: |
        Returns a paginated list of refunds for a specific order.
        Includes both pending and completed refunds.
      tags:
        - Orders
        - Refunds
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of order refunds
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
    post:
      operationId: createOrderRefund
      summary: Create a partial refund
      description: |
        Creates a partial refund for specific items in an order.

        **Requirements:**
        - Order must have `paid` status
        - Order must have a payment processor attached (Mollie)
        - Refund amount per item cannot exceed remaining refundable amount

        **Notes:**
        - Amounts are specified before taxes; taxes are calculated automatically
        - Multiple partial refunds can be created for the same order
        - Refunds are processed asynchronously through the payment provider
      tags:
        - Orders
        - Refunds
      parameters:
        - $ref: '#/components/parameters/orderId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRefundRequest'
            examples:
              partialRefund:
                summary: Partial refund for one item
                value:
                  items:
                    - itemId: oli_abc123
                      amount:
                        value: '15.00'
                        currency: EUR
                      description: 50% refund for service issue
              multipleItems:
                summary: Refund multiple items
                value:
                  items:
                    - itemId: oli_abc123
                      amount:
                        value: '29.00'
                        currency: EUR
                    - itemId: oli_def456
                      amount:
                        value: '10.00'
                        currency: EUR
                  metadata:
                    ticketId: SUPPORT-12345
      responses:
        '201':
          description: Refund created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
              example:
                id: ref_new123
                resource: refund
                orderId: null
                merchantId: mer_abc123
                customerId: cus_xyz789
                testmode: false
                createdAt: '2024-01-15T10:30:00Z'
                status: pending
                originalOrderId: ord_original123
                total:
                  value: '18.15'
                  currency: EUR
                subtotal:
                  value: '15.00'
                  currency: EUR
                taxSummary:
                  value: '3.15'
                  currency: EUR
                lines:
                  - id: rli_abc123
                    resource: refundline
                    description: 50% refund for service issue
                    quantity: 1
                    basePrice:
                      value: '15.00'
                      currency: EUR
                    total:
                      value: '18.15'
                      currency: EUR
                    subtotal:
                      value: '15.00'
                      currency: EUR
                    taxes:
                      value: '3.15'
                      currency: EUR
                links:
                  self:
                    href: https://api.vatly.com/v1/refunds/ref_new123
                    type: application/json
                  originalOrder:
                    href: https://api.vatly.com/v1/orders/ord_original123
                    type: application/json
                  order: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: |
            Validation failed. Common errors:
            - Order is not in `paid` status
            - Refund amount exceeds remaining refundable amount
            - No payment processor attached to order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                notPaid:
                  summary: Order not paid
                  value:
                    message: The given data was invalid.
                    errors:
                      orderId:
                        - Order must be in paid status to create a refund.
                overRefund:
                  summary: Over-refunding
                  value:
                    message: The given data was invalid.
                    errors:
                      items.0.amount:
                        - 'Refund amount exceeds remaining refundable amount. Maximum: 15.00 EUR'
  /orders/{orderId}/refunds/full:
    post:
      operationId: createFullOrderRefund
      summary: Create a full refund
      description: |
        Creates a full refund for an order, refunding all items.

        **Requirements:**
        - Order must have `paid` status
        - Order must have a payment processor attached (Mollie)
        - Order must not be already fully refunded

        **Notes:**
        - This is a convenience endpoint; it automatically calculates the full refundable amount
        - If partial refunds have already been issued, only the remaining amount is refunded
      tags:
        - Orders
        - Refunds
      parameters:
        - $ref: '#/components/parameters/orderId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFullRefundRequest'
            example:
              metadata:
                reason: Customer requested cancellation
      responses:
        '201':
          description: Full refund created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /orders/{orderId}/refunds/{refundId}:
    get:
      operationId: getOrderRefund
      summary: Get an order refund
      description: |
        Retrieves a specific refund for an order.
        The refund must belong to the specified order.
      tags:
        - Orders
        - Refunds
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/refundId'
      responses:
        '200':
          description: Refund details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: cancelOrderRefund
      summary: Cancel a refund
      description: |
        Cancels a pending refund.

        **Requirements:**
        - Refund must be in `pending` status
        - Cannot cancel refunds that have already been processed

        **Notes:**
        - Once canceled, the refund cannot be restored
        - To refund again, create a new refund request
      tags:
        - Orders
        - Refunds
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/refundId'
      responses:
        '204':
          description: Refund canceled successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Cannot cancel refund (not in pending status)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message: Only pending refunds can be canceled.
  /orders/{orderId}/chargebacks:
    get:
      operationId: listOrderChargebacks
      summary: List order chargebacks
      description: |
        Returns a paginated list of chargebacks for a specific order.
        Most orders have at most one chargeback.
      tags:
        - Orders
        - Chargebacks
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of order chargebacks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Chargeback'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /orders/{orderId}/chargebacks/{chargebackId}:
    get:
      operationId: getOrderChargeback
      summary: Get an order chargeback
      description: |
        Retrieves a specific chargeback for an order.
        The chargeback must belong to the specified order.
      tags:
        - Orders
        - Chargebacks
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/chargebackId'
      responses:
        '200':
          description: Chargeback details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Chargeback'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /refunds:
    get:
      operationId: listRefunds
      summary: List all refunds
      description: |
        Returns a paginated list of all refunds for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.
      tags:
        - Refunds
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of refunds
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /refunds/{refundId}:
    get:
      operationId: getRefund
      summary: Get a refund
      description: Retrieves a specific refund by its ID.
      tags:
        - Refunds
      parameters:
        - $ref: '#/components/parameters/refundId'
      responses:
        '200':
          description: Refund details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /chargebacks:
    get:
      operationId: listChargebacks
      summary: List all chargebacks
      description: |
        Returns a paginated list of all chargebacks for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.

        **Notes:**
        - Chargebacks are created automatically by payment provider webhooks
        - They cannot be created manually through the API
      tags:
        - Chargebacks
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of chargebacks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Chargeback'
              example:
                - id: chargeback_abc123
                  resource: chargeback
                  merchantId: mer_xyz789
                  testmode: false
                  createdAt: '2024-01-15T10:30:00Z'
                  amount:
                    value: '35.09'
                    currency: EUR
                  settlementAmount:
                    value: '35.09'
                    currency: EUR
                  reason: fraud
                  originalOrderId: ord_original123
                  orderId: ord_chargeback456
                  links:
                    self:
                      href: https://api.vatly.com/v1/chargebacks/chargeback_abc123
                      type: application/json
                    originalOrder:
                      href: https://api.vatly.com/v1/orders/ord_original123
                      type: application/json
                    order:
                      href: https://api.vatly.com/v1/orders/ord_chargeback456
                      type: application/json
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /chargebacks/{chargebackId}:
    get:
      operationId: getChargeback
      summary: Get a chargeback
      description: Retrieves a specific chargeback by its ID.
      tags:
        - Chargebacks
      parameters:
        - $ref: '#/components/parameters/chargebackId'
      responses:
        '200':
          description: Chargeback details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Chargeback'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /subscriptions:
    get:
      operationId: listSubscriptions
      summary: List all subscriptions
      description: |
        Returns a paginated list of all subscriptions for the authenticated merchant.
        Results are filtered by the testmode determined from the API token.
        Includes subscriptions in all states (active, canceled, etc.).
      tags:
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/startingAfter'
        - $ref: '#/components/parameters/endingBefore'
      responses:
        '200':
          description: List of subscriptions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /subscriptions/{subscriptionId}:
    get:
      operationId: getSubscription
      summary: Get a subscription
      description: Retrieves a specific subscription by its ID.
      tags:
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/subscriptionId'
      responses:
        '200':
          description: Subscription details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
              example:
                id: sub_abc123
                resource: subscription
                customerId: cus_xyz789
                testmode: false
                name: Pro Monthly
                description: Full access to all Pro features
                billingAddress:
                  fullName: John Doe
                  companyName: Acme Corp
                  streetAndNumber: 123 Main Street
                  city: Berlin
                  postalCode: '10115'
                  country: DE
                basePrice:
                  value: '29.00'
                  currency: EUR
                quantity: 1
                interval: month
                intervalCount: 1
                status: active
                startedAt: '2024-01-15T10:30:00Z'
                endedAt: null
                cancelledAt: null
                renewedAt: '2024-02-15T10:30:00Z'
                renewedUntil: '2024-03-15T10:30:00Z'
                nextRenewalAt: '2024-03-15T10:30:00Z'
                trialUntil: null
                links:
                  self:
                    href: https://api.vatly.com/v1/subscriptions/sub_abc123
                    type: application/json
                  customer:
                    href: https://api.vatly.com/v1/customers/cus_xyz789
                    type: application/json
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateSubscription
      summary: Update a subscription
      description: |
        Updates a subscription's plan, quantity, or billing schedule.

        **Plan changes:**
        - Specify `subscriptionPlanId` to switch to a different plan
        - New plan must match the subscription's testmode

        **Quantity changes:**
        - Specify `quantity` to update the number of units (e.g., seats)

        **Proration:**
        - By default, changes are prorated
        - Set `prorate: false` to disable proration
        - Proration credits unused time and charges for new plan

        **Timing:**
        - By default, changes apply at the end of the current billing period
        - Set `applyImmediately: true` for immediate changes
        - Set `invoiceImmediately: true` to charge proration immediately

        **Trial/Anchor:**
        - Use `trialUntil` to extend or set a trial period
        - Use `anchor` to reset the billing anchor date
        - These options are mutually exclusive
      tags:
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/subscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionRequest'
            examples:
              changePlan:
                summary: Upgrade to yearly plan
                value:
                  subscriptionPlanId: plan_yearly
                  prorate: true
                  applyImmediately: true
              changeQuantity:
                summary: Add more seats
                value:
                  quantity: 10
                  applyImmediately: true
                  invoiceImmediately: true
              scheduledChange:
                summary: Schedule plan change for next cycle
                value:
                  subscriptionPlanId: plan_enterprise
                  applyImmediately: false
              extendTrial:
                summary: Extend trial period
                value:
                  trialUntil: '2024-02-28T00:00:00Z'
      responses:
        '200':
          description: Subscription updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: |
            Validation failed. Common errors:
            - Neither subscriptionPlanId nor quantity provided
            - Plan does not exist or is in wrong testmode
            - Both anchor and trialUntil specified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
    delete:
      operationId: cancelSubscription
      summary: Cancel a subscription
      description: |
        Cancels a subscription.

        **Cancellation modes:**
        - **Default (grace period):** Subscription remains active until the end of the current billing period, then ends
        - **Immediate:** Subscription ends immediately when `immediately=true` is specified

        **Notes:**
        - Canceled subscriptions cannot be reactivated through the API
        - Customers retain access until `renewedUntil` date (unless canceled immediately)
        - No refunds are issued automatically; use the Refunds API if needed
      tags:
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/subscriptionId'
        - name: immediately
          in: query
          description: |
            Cancel immediately instead of at period end.
            - `false` (default): Cancel at end of current billing period
            - `true`: Cancel immediately
          required: false
          schema:
            type: boolean
            default: false
      responses:
        '204':
          description: Subscription canceled successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Subscription cannot be canceled (already canceled or ended)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /subscriptions/{subscriptionId}/update-billing:
    patch:
      operationId: updateSubscriptionBilling
      summary: Update billing details (hosted)
      description: |
        Initiates a hosted billing update flow for the subscription.

        Returns a redirect URL where the customer can update their billing address,
        VAT number, and other invoice details.

        **Flow:**
        1. Call this endpoint with redirect URLs
        2. Redirect customer to the returned URL
        3. Customer updates their details on the hosted page
        4. Customer is redirected to your success/canceled URL

        **Use cases:**
        - Customer needs to update billing address
        - Customer needs to add/update VAT number
        - Self-service invoice detail management
      tags:
        - Subscriptions
      parameters:
        - $ref: '#/components/parameters/subscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionBillingRequest'
            example:
              redirectUrlSuccess: https://example.com/billing-updated
              redirectUrlCanceled: https://example.com/account/billing
              billingAddress:
                companyName: Acme Corp
                vatNumber: DE123456789
      responses:
        '200':
          description: Billing update flow initiated
          content:
            application/json:
              schema:
                type: object
                required:
                  - href
                  - type
                properties:
                  href:
                    type: string
                    format: uri
                    description: URL to redirect customer for billing update
                    example: https://vatly.com/subscriptions/sub_abc123/billing?token=xyz...
                  type:
                    type: string
                    const: text/html
                    example: text/html
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        API token authentication using Bearer scheme.

        Tokens are prefixed to indicate the environment mode:
        - `live_` - Production mode, accesses real data
        - `test_` - Sandbox mode, accesses test data

        Obtain API tokens from the Vatly dashboard under Settings > API Keys.

        **Example:**
        ```
        Authorization: Bearer test_abc123xyz...
        ```

        **Important:** Resources are isolated by mode. A checkout created with a test token
        cannot reference customers or products created with a live token, and vice versa.
      bearerFormat: '{mode}_{token}'
  parameters:
    limit:
      name: limit
      in: query
      description: Maximum number of results to return per page
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 10
      example: 25
    startingAfter:
      name: startingAfter
      in: query
      description: |
        Cursor for forward pagination.
        Returns results after this resource ID.
        Mutually exclusive with `endingBefore`.
      required: false
      schema:
        type: string
      example: chk_abc123
    endingBefore:
      name: endingBefore
      in: query
      description: |
        Cursor for backward pagination.
        Returns results before this resource ID.
        Mutually exclusive with `startingAfter`.
      required: false
      schema:
        type: string
      example: chk_xyz789
    checkoutId:
      name: checkoutId
      in: path
      description: Unique identifier of the checkout
      required: true
      schema:
        type: string
      example: chk_abc123def456
    customerId:
      name: customerId
      in: path
      description: Unique identifier of the customer
      required: true
      schema:
        type: string
      example: cus_abc123def456
    subscriptionId:
      name: subscriptionId
      in: path
      description: Unique identifier of the subscription
      required: true
      schema:
        type: string
      example: sub_abc123def456
    oneOffProductId:
      name: oneOffProductId
      in: path
      description: Unique identifier of the one-off product
      required: true
      schema:
        type: string
        pattern: ^prod_
      example: prod_abc123def456
    subscriptionPlanId:
      name: subscriptionPlanId
      in: path
      description: Unique identifier of the subscription plan
      required: true
      schema:
        type: string
        pattern: ^plan_
      example: plan_abc123def456
    orderId:
      name: orderId
      in: path
      description: Unique identifier of the order
      required: true
      schema:
        type: string
      example: ord_abc123def456
    refundId:
      name: refundId
      in: path
      description: Unique identifier of the refund
      required: true
      schema:
        type: string
      example: ref_abc123def456
    chargebackId:
      name: chargebackId
      in: path
      description: Unique identifier of the chargeback
      required: true
      schema:
        type: string
      example: chargeback_abc123def456
  responses:
    Unauthorized:
      description: Unauthorized - Authentication credentials were missing or invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Unauthenticated.
    Forbidden:
      description: |
        Forbidden - The API token is invalid or does not have permission for this operation.
        Common causes:
        - Token does not start with `live_` or `test_`
        - Token has been revoked
        - Attempting live operations on a non-live merchant account
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Auth token must start with live_ or test_.
    ValidationFailed:
      description: |
        Unprocessable Entity - The request was well-formed but contained validation errors.
        The `errors` object contains field-specific error messages.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
          example:
            message: The given data was invalid.
            errors:
              email:
                - The email field is required.
                - The email must be a valid email address.
              products.0.id:
                - The selected products.0.id is invalid.
    NotFound:
      description: Not Found - The requested resource does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Checkout not found.
    BadRequest:
      description: Bad Request - The request was malformed or invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Invalid request format
    TestmodeMismatch:
      description: |
        Unprocessable Entity - A referenced resource exists but was created in a different mode.
        Switch between live/test API tokens to access the correct resources.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
          example:
            message: The given data was invalid.
            errors:
              customerId:
                - Customer exists, but the wrong mode is used. Try switching live / test API keys.
    TooManyRequests:
      description: Too Many Requests - Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Too many requests. Please try again later.
      headers:
        Retry-After:
          description: Number of seconds to wait before retrying
          schema:
            type: integer
          example: 60
    InternalServerError:
      description: Internal Server Error - An unexpected error occurred
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: An unexpected error occurred. Please try again later.
  schemas:
    Money:
      type: object
      description: Monetary value with currency
      required:
        - value
        - currency
      properties:
        value:
          type: string
          description: |
            The amount as a decimal string.
            Uses string type to preserve precision for financial calculations.
          example: '99.99'
          pattern: ^\d+(\.\d{1,2})?$
        currency:
          type: string
          description: ISO 4217 currency code
          example: EUR
          minLength: 3
          maxLength: 3
    Link:
      type: object
      description: HATEOAS link to a related resource
      required:
        - href
        - type
      properties:
        href:
          type: string
          format: uri
          description: URL of the linked resource
          example: https://api.vatly.com/v1/orders/ord_abc123
        type:
          type: string
          description: |
            Content type of the linked resource.
            - `application/json` for API resources
            - `text/html` for hosted pages
          enum:
            - application/json
            - text/html
          example: application/json
    BillingAddress:
      type: object
      description: Customer or merchant billing address
      properties:
        fullName:
          type:
            - string
            - 'null'
          description: Full name of the person or contact
          example: John Doe
        companyName:
          type:
            - string
            - 'null'
          description: Company or organization name
          example: Acme Corp
        vatNumber:
          type:
            - string
            - 'null'
          description: VAT identification number for tax purposes
          example: NL123456789B01
        streetAndNumber:
          type:
            - string
            - 'null'
          description: Street address including building number
          example: 123 Main Street
        streetAdditional:
          type:
            - string
            - 'null'
          description: Additional address information (apartment, suite, etc.)
          example: Suite 456
        city:
          type:
            - string
            - 'null'
          description: City name
          example: Amsterdam
        region:
          type:
            - string
            - 'null'
          description: State, province, or region
          example: North Holland
        postalCode:
          type:
            - string
            - 'null'
          description: Postal or ZIP code
          example: 1012 AB
        country:
          type:
            - string
            - 'null'
          description: ISO 3166-1 alpha-2 country code
          example: NL
          minLength: 2
          maxLength: 2
    BillingDetails:
      type: object
      description: Complete billing details including address and contact information
      properties:
        fullName:
          type:
            - string
            - 'null'
          description: Full name of the person or contact
          example: John Doe
        companyName:
          type:
            - string
            - 'null'
          description: Company or organization name
          example: Acme Corp
        vatNumber:
          type:
            - string
            - 'null'
          description: VAT identification number
          example: NL123456789B01
        streetAndNumber:
          type:
            - string
            - 'null'
          description: Street address including building number
          example: 123 Main Street
        streetAdditional:
          type:
            - string
            - 'null'
          description: Additional address information
          example: Suite 456
        city:
          type:
            - string
            - 'null'
          description: City name
          example: Amsterdam
        region:
          type:
            - string
            - 'null'
          description: State, province, or region
          example: North Holland
        postalCode:
          type:
            - string
            - 'null'
          description: Postal or ZIP code
          example: 1012 AB
        country:
          type:
            - string
            - 'null'
          description: ISO 3166-1 alpha-2 country code
          example: NL
        email:
          type:
            - string
            - 'null'
          format: email
          description: Email address
          example: billing@acme.com
    Metadata:
      type:
        - object
        - 'null'
      description: |
        Arbitrary key-value metadata for your application.
        Up to 50 keys, with key names up to 40 characters and values up to 500 characters.
      additionalProperties:
        type: string
        maxLength: 500
      maxProperties: 50
      example:
        orderId: internal-12345
        campaign: summer-sale
    Error:
      type: object
      description: Standard error response
      required:
        - message
      properties:
        message:
          type: string
          description: Human-readable error message
          example: The requested resource was not found.
    ValidationError:
      type: object
      description: Validation error response with field-specific errors
      required:
        - message
        - errors
      properties:
        message:
          type: string
          description: General validation error message
          example: The given data was invalid.
        errors:
          type: object
          description: |
            Field-specific validation errors.
            Keys are field names (using dot notation for nested fields).
            Values are arrays of error messages.
          additionalProperties:
            type: array
            items:
              type: string
          example:
            email:
              - The email field is required.
            products.0.id:
              - The selected products.0.id is invalid.
    Checkout:
      type: object
      description: |
        A checkout session represents a hosted payment flow.
        Customers are redirected to the checkout URL to complete their purchase.
      required:
        - id
        - resource
        - merchantId
        - testmode
        - redirectUrlSuccess
        - redirectUrlCanceled
        - metadata
        - status
        - createdAt
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the checkout
          example: chk_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: checkout
          example: checkout
        merchantId:
          type: string
          description: ID of the merchant that owns this checkout
          example: mer_abc123
        orderId:
          type:
            - string
            - 'null'
          description: |
            ID of the order created from this checkout.
            Only present after checkout completion.
          example: ord_xyz789
        testmode:
          type: boolean
          description: Whether this checkout is in test mode
          example: false
        redirectUrlSuccess:
          type: string
          format: uri
          description: URL to redirect customers after successful payment
          example: https://example.com/success
        redirectUrlCanceled:
          type: string
          format: uri
          description: URL to redirect customers if they cancel the checkout
          example: https://example.com/canceled
        metadata:
          $ref: '#/components/schemas/Metadata'
        status:
          type: string
          description: |
            Current status of the checkout:
            - `created` - Checkout is active and awaiting payment
            - `paid` - Payment successful, order created
            - `canceled` - Checkout was canceled by the customer
            - `failed` - Payment failed
            - `expired` - Checkout expired without completion
          enum:
            - created
            - paid
            - canceled
            - failed
            - expired
          example: created
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When this checkout will expire (ISO 8601 format)
          example: '2024-01-15T14:30:00Z'
        createdAt:
          type: string
          format: date-time
          description: When this checkout was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - checkoutUrl
            - self
          properties:
            checkoutUrl:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Hosted checkout page URL (text/html)
            self:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to this checkout resource
            order:
              oneOf:
                - $ref: '#/components/schemas/Link'
                - type: 'null'
              description: Link to the created order (only after completion)
    CheckoutProduct:
      type: object
      description: A product to add to the checkout
      required:
        - id
      properties:
        id:
          type: string
          description: |
            Product identifier. Must be either:
            - A one-off product ID (starts with `prod_`)
            - A subscription plan ID (starts with `plan_`)
          pattern: ^(prod_|plan_)
          example: prod_abc123
        quantity:
          type: integer
          description: Number of units to purchase
          minimum: 1
          default: 1
          example: 2
        price:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: |
            Custom price override for this product.
            If not provided, the product's default price is used.
        trialDays:
          type:
            - integer
            - 'null'
          description: |
            Trial period in days for subscription plans.
            Only applicable when `id` references a subscription plan.
          minimum: 0
          example: 14
        metadata:
          $ref: '#/components/schemas/Metadata'
    CreateCheckoutRequest:
      type: object
      description: Request body for creating a new checkout
      required:
        - redirectUrlSuccess
        - redirectUrlCanceled
        - products
      properties:
        redirectUrlSuccess:
          type: string
          format: uri
          description: URL to redirect customers after successful payment
          example: https://example.com/success?session_id=chk_abc123
        redirectUrlCanceled:
          type: string
          format: uri
          description: URL to redirect customers if they cancel the checkout
          example: https://example.com/canceled
        customerId:
          type:
            - string
            - 'null'
          description: |
            Existing customer ID to associate with this checkout.
            If provided, the customer's email will be pre-filled.
            Must match the testmode of the API token.
          example: cus_abc123
        metadata:
          $ref: '#/components/schemas/Metadata'
        products:
          type: array
          description: Products to include in this checkout
          minItems: 1
          items:
            $ref: '#/components/schemas/CheckoutProduct'
          example:
            - id: prod_abc123
              quantity: 1
            - id: plan_xyz789
              trialDays: 14
    Customer:
      type: object
      description: |
        A customer represents a person or organization that can make purchases.
        Customers are uniquely identified by their email address within each testmode.
      required:
        - id
        - resource
        - testmode
        - email
        - createdAt
        - metadata
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the customer
          example: cus_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: customer
          example: customer
        testmode:
          type: boolean
          description: Whether this customer is in test mode
          example: false
        email:
          type: string
          format: email
          description: Customer's email address
          example: customer@example.com
        createdAt:
          type: string
          format: date-time
          description: When this customer was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        metadata:
          $ref: '#/components/schemas/Metadata'
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
          properties:
            self:
              $ref: '#/components/schemas/Link'
              description: Link to this customer resource
    CreateCustomerRequest:
      type: object
      description: Request body for creating a new customer
      required:
        - email
      properties:
        email:
          type: string
          format: email
          description: |
            Customer's email address.
            Must be unique within the merchant's account for the given testmode.
          example: customer@example.com
        metadata:
          $ref: '#/components/schemas/Metadata'
    OneOffProduct:
      type: object
      description: |
        A one-off product represents a single-purchase item.
        Products are configured in the Vatly dashboard and can be added to checkouts.
      required:
        - id
        - resource
        - testmode
        - name
        - description
        - basePrice
        - status
        - createdAt
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the product (always starts with `prod_`)
          pattern: ^prod_
          example: prod_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: one_off_product
          example: one_off_product
        testmode:
          type: boolean
          description: Whether this product is in test mode
          example: false
        name:
          type: string
          description: Display name of the product
          example: Premium License
        description:
          type: string
          description: Detailed description of the product
          example: Lifetime access to all premium features
        basePrice:
          $ref: '#/components/schemas/Money'
          description: Default price of the product (can be overridden in checkout)
        status:
          type: string
          description: |
            Current status of the product:
            - `approved` - Product is active and can be purchased
            - `draft` - Product is not yet available for purchase
            - `archived` - Product has been archived
          enum:
            - approved
            - draft
            - archived
          example: approved
        createdAt:
          type: string
          format: date-time
          description: When this product was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
          properties:
            self:
              $ref: '#/components/schemas/Link'
              description: Link to this product resource
    SubscriptionPlan:
      type: object
      description: |
        A subscription plan defines recurring billing terms.
        Plans are configured in the Vatly dashboard and can be added to checkouts.
      required:
        - id
        - resource
        - testmode
        - name
        - description
        - basePrice
        - interval
        - intervalCount
        - status
        - createdAt
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the plan (always starts with `plan_`)
          pattern: ^plan_
          example: plan_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: subscription_plan
          example: subscription_plan
        testmode:
          type: boolean
          description: Whether this plan is in test mode
          example: false
        name:
          type: string
          description: Display name of the plan
          example: Pro Monthly
        description:
          type: string
          description: Detailed description of the plan
          example: Full access to all Pro features, billed monthly
        basePrice:
          $ref: '#/components/schemas/Money'
          description: Price per billing interval
        interval:
          type: string
          description: |
            Billing interval unit:
            - `day` - Daily billing
            - `week` - Weekly billing
            - `month` - Monthly billing
            - `year` - Yearly billing
          enum:
            - day
            - week
            - month
            - year
          example: month
        intervalCount:
          type: integer
          description: |
            Number of interval units between billing cycles.
            For example, `interval: month` with `intervalCount: 3` bills every 3 months.
          minimum: 1
          example: 1
        status:
          type: string
          description: |
            Current status of the plan:
            - `approved` - Plan is active and can be subscribed to
            - `draft` - Plan is not yet available
            - `archived` - Plan has been archived
          enum:
            - approved
            - draft
            - archived
          example: approved
        createdAt:
          type: string
          format: date-time
          description: When this plan was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
          properties:
            self:
              $ref: '#/components/schemas/Link'
              description: Link to this plan resource
    Order:
      type: object
      description: |
        An order represents a completed purchase.
        Orders are created when checkouts complete or subscriptions renew.
      required:
        - id
        - resource
        - merchantId
        - customerId
        - testmode
        - metadata
        - createdAt
        - status
        - total
        - subtotal
        - taxSummary
        - lines
        - merchantDetails
        - customerDetails
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the order
          example: ord_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: order
          example: order
        merchantId:
          type: string
          description: ID of the merchant that owns this order
          example: mer_abc123
        customerId:
          type: string
          description: ID of the customer who made this purchase
          example: cus_xyz789
        testmode:
          type: boolean
          description: Whether this order is in test mode
          example: false
        metadata:
          $ref: '#/components/schemas/Metadata'
        paymentMethod:
          type:
            - string
            - 'null'
          description: |
            Payment method used for this order.
            Examples: `ideal`, `creditcard`, `bancontact`, `paypal`
          example: ideal
        createdAt:
          type: string
          format: date-time
          description: When this order was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        status:
          type: string
          description: |
            Current status of the order:
            - `pending` - Payment is being processed
            - `paid` - Payment successful
            - `failed` - Payment failed
          enum:
            - pending
            - paid
            - failed
          example: paid
        invoiceNumber:
          type:
            - string
            - 'null'
          description: Invoice number for this order (assigned after payment)
          example: INV-2024-0001
        total:
          $ref: '#/components/schemas/Money'
          description: Total amount including taxes
        subtotal:
          $ref: '#/components/schemas/Money'
          description: Subtotal amount before taxes
        taxSummary:
          $ref: '#/components/schemas/Money'
          description: Total tax amount
        lines:
          type: array
          description: Line items in this order
          items:
            $ref: '#/components/schemas/OrderLine'
        merchantDetails:
          $ref: '#/components/schemas/BillingDetails'
          description: Merchant billing details (seller)
        customerDetails:
          $ref: '#/components/schemas/BillingDetails'
          description: Customer billing details (buyer)
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
            - customer
          properties:
            self:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to this order resource
            customer:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to the customer who made this purchase
    OrderLine:
      type: object
      description: A line item in an order
      required:
        - id
        - resource
        - description
        - quantity
        - basePrice
        - total
        - subtotal
        - taxes
      properties:
        id:
          type: string
          description: Unique identifier for this line item
          example: oli_abc123
        resource:
          type: string
          description: Resource type identifier
          const: orderline
          example: orderline
        description:
          type: string
          description: Description of the item
          example: Pro Monthly Subscription
        quantity:
          type: integer
          description: Number of units
          minimum: 1
          example: 1
        basePrice:
          $ref: '#/components/schemas/Money'
          description: Price per unit before taxes
        total:
          $ref: '#/components/schemas/Money'
          description: Total price including taxes (basePrice * quantity + taxes)
        subtotal:
          $ref: '#/components/schemas/Money'
          description: Subtotal before taxes (basePrice * quantity)
        taxes:
          $ref: '#/components/schemas/Money'
          description: Tax amount for this line
    AddressUpdateLink:
      type: object
      description: A signed URL to update order billing address
      required:
        - href
        - type
      properties:
        href:
          type: string
          format: uri
          description: |
            Signed URL to the address update page.
            Valid for a limited time (typically 24 hours).
          example: https://vatly.com/invoices/ord_abc123/edit?signature=abc123...
        type:
          type: string
          description: Content type of the linked page
          const: text/html
          example: text/html
    Refund:
      type: object
      description: |
        A refund represents a partial or full reversal of an order payment.
        Refunds are processed through the original payment provider.
      required:
        - id
        - resource
        - merchantId
        - customerId
        - testmode
        - createdAt
        - status
        - originalOrderId
        - total
        - subtotal
        - taxSummary
        - lines
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the refund
          example: ref_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: refund
          example: refund
        orderId:
          type:
            - string
            - 'null'
          description: |
            ID of the credit note order created for this refund.
            Only present after the refund is processed.
          example: ord_credit123
        merchantId:
          type: string
          description: ID of the merchant
          example: mer_abc123
        customerId:
          type: string
          description: ID of the customer receiving the refund
          example: cus_xyz789
        testmode:
          type: boolean
          description: Whether this refund is in test mode
          example: false
        createdAt:
          type: string
          format: date-time
          description: When this refund was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        status:
          type: string
          description: |
            Current status of the refund:
            - `pending` - Refund is being processed
            - `completed` - Refund has been processed successfully
            - `failed` - Refund failed
            - `canceled` - Refund was canceled
          enum:
            - pending
            - completed
            - failed
            - canceled
          example: completed
        originalOrderId:
          type: string
          description: ID of the original order being refunded
          example: ord_original123
        total:
          $ref: '#/components/schemas/Money'
          description: Total refund amount including taxes
        subtotal:
          $ref: '#/components/schemas/Money'
          description: Refund subtotal before taxes
        taxSummary:
          $ref: '#/components/schemas/Money'
          description: Total tax amount being refunded
        lines:
          type: array
          description: Refund line items
          items:
            $ref: '#/components/schemas/RefundLine'
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
            - originalOrder
          properties:
            self:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to this refund resource
            originalOrder:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to the original order
            order:
              oneOf:
                - $ref: '#/components/schemas/Link'
                - type: 'null'
              description: Link to the credit note order (if created)
    RefundLine:
      type: object
      description: A line item in a refund
      required:
        - id
        - resource
        - description
        - quantity
        - basePrice
        - total
        - subtotal
        - taxes
      properties:
        id:
          type: string
          description: Unique identifier for this refund line
          example: rli_abc123
        resource:
          type: string
          description: Resource type identifier
          const: refundline
          example: refundline
        description:
          type: string
          description: Description of the refunded item
          example: Pro Monthly Subscription (Refund)
        quantity:
          type: integer
          description: Number of units being refunded
          minimum: 1
          example: 1
        basePrice:
          $ref: '#/components/schemas/Money'
          description: Refund amount per unit before taxes
        total:
          $ref: '#/components/schemas/Money'
          description: Total refund amount including taxes
        subtotal:
          $ref: '#/components/schemas/Money'
          description: Refund subtotal before taxes
        taxes:
          $ref: '#/components/schemas/Money'
          description: Tax amount being refunded
    CreateRefundRequest:
      type: object
      description: Request body for creating a partial refund
      required:
        - items
      properties:
        items:
          type: array
          description: Items to refund
          minItems: 1
          items:
            $ref: '#/components/schemas/RefundItem'
          example:
            - itemId: oli_abc123
              amount:
                value: '15.00'
                currency: EUR
              description: Partial refund
        metadata:
          $ref: '#/components/schemas/Metadata'
    CreateFullRefundRequest:
      type: object
      description: Request body for creating a full refund
      properties:
        metadata:
          $ref: '#/components/schemas/Metadata'
    Chargeback:
      type: object
      description: |
        A chargeback represents a payment dispute initiated by a customer through their bank or card issuer.
        Chargebacks are created automatically when payment providers notify Vatly of a dispute.
      required:
        - id
        - resource
        - merchantId
        - testmode
        - createdAt
        - amount
        - settlementAmount
        - reason
        - originalOrderId
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the chargeback
          example: chargeback_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: chargeback
          example: chargeback
        merchantId:
          type: string
          description: ID of the merchant
          example: mer_abc123
        testmode:
          type: boolean
          description: Whether this chargeback is in test mode
          example: false
        createdAt:
          type: string
          format: date-time
          description: When this chargeback was created (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        amount:
          $ref: '#/components/schemas/Money'
          description: Amount of the chargeback
        settlementAmount:
          $ref: '#/components/schemas/Money'
          description: |
            Amount that was deducted from the merchant's settlement.
            May differ from `amount` due to currency conversion or fees.
        reason:
          type: string
          description: |
            Reason code or description for the chargeback.
            Common reasons include:
            - `fraud` - Unauthorized transaction
            - `product_not_received` - Customer claims non-delivery
            - `product_unacceptable` - Customer claims product was defective
            - `duplicate` - Customer claims duplicate charge
            - `subscription_canceled` - Customer claims subscription was canceled
          example: fraud
        originalOrderId:
          type: string
          description: ID of the original order that was charged back
          example: ord_original123
        orderId:
          type:
            - string
            - 'null'
          description: |
            ID of the credit note order created for this chargeback.
            Only present after the chargeback is processed.
          example: ord_chargeback123
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
            - originalOrder
          properties:
            self:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to this chargeback resource
            originalOrder:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to the original order
            order:
              oneOf:
                - $ref: '#/components/schemas/Link'
                - type: 'null'
              description: Link to the credit note order (if created)
    Subscription:
      type: object
      description: |
        A subscription represents a recurring billing relationship with a customer.
        Subscriptions automatically renew according to their billing interval.
      required:
        - id
        - resource
        - customerId
        - testmode
        - name
        - description
        - billingAddress
        - basePrice
        - quantity
        - interval
        - intervalCount
        - status
        - links
      properties:
        id:
          type: string
          description: Unique identifier for the subscription
          example: sub_abc123def456
        resource:
          type: string
          description: Resource type identifier
          const: subscription
          example: subscription
        customerId:
          type: string
          description: ID of the customer who owns this subscription
          example: cus_xyz789
        testmode:
          type: boolean
          description: Whether this subscription is in test mode
          example: false
        name:
          type: string
          description: Name of the subscription (from the plan)
          example: Pro Monthly
        description:
          type: string
          description: Description of the subscription
          example: Full access to all Pro features
        billingAddress:
          $ref: '#/components/schemas/BillingAddress'
          description: Customer's billing address for this subscription
        basePrice:
          $ref: '#/components/schemas/Money'
          description: Price per billing cycle before taxes
        quantity:
          type: integer
          description: Number of subscription units (e.g., seats)
          minimum: 1
          example: 1
        interval:
          type: string
          description: Billing interval unit
          enum:
            - day
            - week
            - month
            - year
          example: month
        intervalCount:
          type: integer
          description: Number of interval units between billing cycles
          minimum: 1
          example: 1
        status:
          type: string
          description: |
            Current status of the subscription:
            - `active` - Subscription is active and will renew
            - `created` - Subscription has been created but not yet started
            - `trial` - Subscription is in trial period
            - `on_grace_period` - Subscription is canceled but still active until period ends
            - `paused` - Subscription is temporarily paused
            - `canceled` - Subscription has been canceled
          enum:
            - active
            - created
            - trial
            - on_grace_period
            - paused
            - canceled
          example: active
        startedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When the subscription started (ISO 8601 format)
          example: '2024-01-15T10:30:00Z'
        endedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When the subscription ended (ISO 8601 format)
        cancelledAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When the subscription was canceled (ISO 8601 format)
        renewedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When the subscription was last renewed (ISO 8601 format)
          example: '2024-02-15T10:30:00Z'
        renewedUntil:
          type:
            - string
            - 'null'
          format: date-time
          description: Current billing period end date (ISO 8601 format)
          example: '2024-03-15T10:30:00Z'
        nextRenewalAt:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            When the next renewal will be attempted (ISO 8601 format).
            Null if subscription is canceled or ended.
          example: '2024-03-15T10:30:00Z'
        trialUntil:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            When the trial period ends (ISO 8601 format).
            Null if not in trial or trial has ended.
        links:
          type: object
          description: HATEOAS links to related resources
          required:
            - self
            - customer
          properties:
            self:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to this subscription resource
            customer:
              allOf:
                - $ref: '#/components/schemas/Link'
              description: Link to the customer who owns this subscription
    UpdateSubscriptionRequest:
      type: object
      description: |
        Request body for updating a subscription.
        At least one of `subscriptionPlanId` or `quantity` must be provided.
      properties:
        subscriptionPlanId:
          type: string
          description: |
            ID of the new subscription plan.
            Must match the testmode of the current subscription.
          pattern: ^plan_
          example: plan_yearly
        quantity:
          type: integer
          description: New quantity (e.g., number of seats)
          minimum: 1
          example: 5
        prorate:
          type: boolean
          description: |
            Whether to prorate charges for the partial billing period.
            If true, the customer is credited for unused time on the old plan
            and charged for remaining time on the new plan.
          default: true
          example: true
        applyImmediately:
          type: boolean
          description: |
            Whether to apply changes immediately or at the end of the current billing period.
            - `true`: Changes take effect immediately (with proration if enabled)
            - `false`: Changes apply when the current period ends
          default: false
          example: true
        invoiceImmediately:
          type: boolean
          description: |
            Whether to generate and charge an invoice immediately for proration.
            Only applies when `applyImmediately` and `prorate` are both true.
          default: false
          example: false
        anchor:
          type:
            - string
            - 'null'
          format: date
          description: |
            Reset the billing anchor to this date.
            Cannot be combined with `trialUntil`.
          example: '2024-02-01'
        trialUntil:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            Extend or set a trial period until this date (ISO 8601 format).
            Cannot be combined with `anchor`.
          example: '2024-02-15T00:00:00Z'
    UpdateSubscriptionBillingRequest:
      type: object
      description: Request body for updating subscription billing details via hosted flow
      required:
        - redirectUrlSuccess
        - redirectUrlCanceled
      properties:
        redirectUrlSuccess:
          type: string
          format: uri
          description: URL to redirect after successful billing update
          example: https://example.com/billing-updated
        redirectUrlCanceled:
          type: string
          format: uri
          description: URL to redirect if customer cancels the update
          example: https://example.com/billing-canceled
        billingAddress:
          oneOf:
            - $ref: '#/components/schemas/BillingAddress'
            - type: 'null'
          description: |
            Pre-fill billing address fields.
            Customer can modify these values in the hosted form.
    RefundItem:
      type: object
      description: An item to refund from an order
      required:
        - itemId
        - amount
      properties:
        itemId:
          type: string
          description: ID of the order line item to refund
          example: oli_abc123
        amount:
          $ref: '#/components/schemas/Money'
          description: |
            Amount to refund for this item (before taxes).
            Cannot exceed the remaining refundable amount for the item.
        description:
          type:
            - string
            - 'null'
          description: Custom description for this refund line
          example: Partial refund for service issue
        descriptionAdditionalLine:
          type:
            - string
            - 'null'
          description: Additional description line for the refund
          example: 'Ticket #12345'
