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

# Change the plan or the quantity

> Change the plan, the quantity (seats, licences), or both.

**Increase** → charged now on the subscription's card (prorated; full new price if the cadence changes) and applied once paid · `202`, `applied: false`. Under 1.00 the difference is waived.

**Decrease** → applied at the period end, no charge.

A quantity-only change keeps the subscriber's unit price. Licence activation limits follow the quantity.

**`actionUrl`** → returned when the customer must act (3-D Secure, card to re-enter, crypto deposit). Suby-hosted page, full page, popup or iframe: it posts `{ source: "suby", type: "payment_action.completed", paymentId, status }` to its opener, or redirects to `successUrl` / `cancelUrl`.

`subscription.updated` fires once applied. Price it first with `change-plan/preview`.




## OpenAPI

````yaml /v3-beta/api-reference/openapi.yaml post /v3/subscriptions/{id}/change-plan
openapi: 3.1.0
info:
  title: Suby.fi Merchant API
  version: 3.0.0-beta
  description: >
    The **v3** public merchant API for Suby.fi. RESTful (plural,
    resource-oriented

    endpoints under the `/v3` prefix). Authenticate every request

    with your secret API key in the `X-Suby-Api-Key` header.


    - `sk_live_…` keys operate in **production** (real funds).

    - `sk_sandbox_…` keys operate in a fully simulated **sandbox** (test cards,
      Base Sepolia crypto, no real money). The environment is derived from the
      key prefix.
  contact:
    email: contact@suby.fi
    url: https://suby.fi
servers:
  - url: https://api.beta.suby.fi
    description: Production + sandbox (environment selected by API key prefix)
security:
  - ApiKeyAuth: []
tags:
  - name: Products
    description: Manage your product catalog (one-time and recurring).
  - name: Checkout
    description: >-
      Hosted checkout · signed session tokens (`cs_…`). Every card Suby holds
      was collected here, which is what keeps card data out of your systems.
  - name: Payments
    description: >-
      Read payments and receipts, refund them, and debit a stored instrument
      off-session (pay-as-you-go). Collecting a card happens on the hosted
      checkout, never here.
  - name: Subscriptions
    description: >-
      Drive a live subscription · read it, change its plan, end it.
      Subscriptions are opened by a checkout session, not by this API.
  - name: Discount Codes
    description: Promo codes, redeemed at checkout.
  - name: Crypto
    description: >-
      The headless rail · quote, charge, and settle on-chain from inside your
      own site. No card data is involved, so none of the constraints that keep
      card collection on a Suby page apply here.
  - name: Customers
    description: First-class customer records with billing address.
  - name: Payment Methods
    description: >-
      Read and detach the cards a customer has stored. They are created by a
      checkout session (`savePaymentMethod`, or `mode=setup`) · the only place a
      card is collected.
  - name: Licence Key
    description: >-
      The licence server · validate a key, take a seat, free one. Authenticated
      with your API key like everything else, and scoped to the licences you
      issued, so your backend relays the check rather than your customer's
      software calling it directly.
paths:
  /v3/subscriptions/{id}/change-plan:
    post:
      tags:
        - Subscriptions
      summary: Change the plan or the quantity
      description: >
        Change the plan, the quantity (seats, licences), or both.


        **Increase** → charged now on the subscription's card (prorated; full
        new price if the cadence changes) and applied once paid · `202`,
        `applied: false`. Under 1.00 the difference is waived.


        **Decrease** → applied at the period end, no charge.


        A quantity-only change keeps the subscriber's unit price. Licence
        activation limits follow the quantity.


        **`actionUrl`** → returned when the customer must act (3-D Secure, card
        to re-enter, crypto deposit). Suby-hosted page, full page, popup or
        iframe: it posts `{ source: "suby", type: "payment_action.completed",
        paymentId, status }` to its opener, or redirects to `successUrl` /
        `cancelUrl`.


        `subscription.updated` fires once applied. Price it first with
        `change-plan/preview`.
      operationId: changeSubscriptionPlan
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/PlanChangeTarget'
                - type: object
                  properties:
                    useCustomerPaymentMethod:
                      type: boolean
                      default: true
                      description: >-
                        Charge the subscription's saved card. `false`, or a
                        `method` + `card`, uses a fresh card.
                    method:
                      type: string
                      enum:
                        - CARD
                        - APPLE_PAY
                        - GOOGLE_PAY
                      description: Fresh card only.
                    card:
                      type: object
                      properties:
                        tokenizedInstrument:
                          type: string
                          description: Fresh card token.
                    successUrl:
                      type: string
                      format: uri
                      description: >-
                        Redirect after `actionUrl` succeeds (full page).
                        Omitted: a confirmation page.
                    cancelUrl:
                      type: string
                      format: uri
                      description: Redirect after `actionUrl` fails.
      responses:
        '202':
          description: Change accepted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - properties:
                      data:
                        type: object
                        properties:
                          subscription:
                            $ref: '#/components/schemas/Subscription'
                          scheduledChange:
                            $ref: '#/components/schemas/ScheduledChange'
                          applied:
                            type: boolean
                            description: >-
                              `true` when applied immediately (nothing to
                              collect).
                          payment:
                            allOf:
                              - $ref: '#/components/schemas/Payment'
                            description: The card charge for the difference.
                          actionUrl:
                            type: string
                            format: uri
                            description: >-
                              Page where the customer completes the payment (3-D
                              Secure, card to re-enter, crypto deposit). Valid
                              30 minutes.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: >-
            Subscription or target plan not found (`SUBSCRIPTION_NOT_FOUND`,
            `TARGET_PRODUCT_NOT_FOUND`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            `SAME_PLAN`, `PLAN_CHANGE_IN_PROGRESS` (an earlier increase is still
            being paid)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: >-
            `SUBSCRIPTION_NOT_MODIFIABLE`, `TARGET_PRODUCT_NOT_RECURRING`,
            `PLAN_CHANGE_CURRENCY_MISMATCH`, `QUANTITY_OUT_OF_RANGE`,
            `UPGRADE_REQUIRES_CARD`, `PLAN_CHANGE_DECLINED`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  parameters:
    SubscriptionId:
      name: id
      in: path
      required: true
      schema:
        type: string
        example: sub_abc123
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Optional key (≤255 chars, e.g. a UUID v4) making this POST safe to retry
        · the response is cached 24h and a retry with the SAME key replays it
        instead of re-executing. The same key with a different body → `422
        IDEMPOTENCY_KEY_CONFLICT`; a retry while the first is in flight → `409`.
      schema:
        type: string
        maxLength: 255
        example: 5f3b9c2e-1a4d-4f2b-9c31-7e2a1b6d8c04
  schemas:
    PlanChangeTarget:
      type: object
      description: '`productId`, `quantity`, or both.'
      properties:
        productId:
          type: string
          description: Target plan. Omitted = current plan.
          example: pro_9m2k1x8s7d6f
        quantity:
          type: integer
          minimum: 1
          maximum: 999
          description: Units to hold afterwards (absolute, not a delta).
          example: 3
        effective:
          type: string
          enum:
            - auto
            - now
            - period_end
          default: auto
          description: '`auto`: increase now, decrease at period end.'
        proration:
          type: string
          enum:
            - delta
            - none
          default: delta
          description: '`delta` charges the prorated difference; `none` charges nothing.'
    SuccessEnvelope:
      type: object
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
        data:
          description: Endpoint-specific payload.
    Subscription:
      type: object
      properties:
        id:
          type: string
          example: sub_abc123
        organizationId:
          type: string
        customerId:
          type: string
        productId:
          type: string
        status:
          $ref: '#/components/schemas/SubscriptionStatus'
        currentCycle:
          type: integer
        totalCycles:
          type: integer
          nullable: true
        trialEndAt:
          type: string
          format: date-time
          nullable: true
        cancelAtPeriodEnd:
          type: boolean
        currentCycleDueAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            Period-end / access-expiry clock for BOTH rails (replaces the
            removed `expiresAt`): card subs renew off-session at this date,
            crypto subs renew by email around it. Null once no further renewal
            is scheduled (final cycle reached or terminated).
        renewalAttempt:
          type: integer
          description: >-
            Attempts made for the current cycle: 0 while healthy, then 1..N
            while dunning (card smart-retry). N depends on the plan's cadence ·
            2 for weekly/daily, 4 for monthly/yearly, so a chain always finishes
            inside one billing period.
        nextRenewalAttemptAt:
          type: string
          format: date-time
          nullable: true
          description: Scheduled next retry while PAST_DUE (card back-off).
        lastDeclineCategory:
          type: string
          nullable: true
          enum:
            - SOFT
            - HARD
            - AUTHENTICATION_REQUIRED
          description: >-
            Category of the most recent decline: SOFT (retried), HARD (ends the
            subscription), or AUTHENTICATION_REQUIRED (the issuer wants the
            cardholder to confirm the card · retried on the same schedule, but
            only the customer can clear it).
        priceCents:
          type: string
          nullable: true
          description: >-
            The price the customer AGREED to, frozen at subscribe time · in
            minor units. This is what every renewal bills, NOT the plan's
            current `priceCents`: editing a product's price never re-prices the
            people already subscribed to it, so the two diverge by design. Null
            only on rows created before the freeze existed.
          example: '1999'
        currency:
          type: string
          nullable: true
          description: Currency of the agreed price. Frozen with it.
          example: EUR
        quantity:
          type: integer
          description: >-
            Units held (seats, licences). `priceCents` is the whole cycle (unit
            × quantity).
          example: 3
        taxInclusive:
          type: boolean
          description: >-
            Whether the agreed price already includes VAT (extract from it) or
            excludes it (add on top). Frozen with the price and read by every
            renewal. Inert unless Suby is merchant of record for your account.
        purchaseAsBusiness:
          type: boolean
          description: >-
            B2B reverse charge · the customer subscribed as a business. Frozen
            at subscribe time, applied to every later charge.
        externalRef:
          type: string
          nullable: true
          description: Merchant business reference set at create.
        endedAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the subscription left the active set for good. Null while it is
            still running. Separate from `updatedAt`, which moves on any write ·
            a churn date read off `updatedAt` re-dates itself every time
            anything else on the row changes.
        endReason:
          type: string
          nullable: true
          enum:
            - CANCELED_BY_CUSTOMER
            - PAYMENT_FAILED
            - COMPLETED
            - NEVER_ACTIVATED
          description: >-
            Why it ended. `CANCELED_BY_CUSTOMER` = voluntary churn;
            `PAYMENT_FAILED` = the retry chain ran out (involuntary);
            `COMPLETED` = the last agreed cycle was reached, which is not churn;
            `NEVER_ACTIVATED` = the first cycle never confirmed. Null on rows
            that ended before this was recorded.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        product:
          $ref: '#/components/schemas/SubscriptionProduct'
        customer:
          type: object
          description: Present only when `?expand=customer` asked for it.
          properties:
            id:
              type: string
              example: cus_abc123
            email:
              type: string
              format: email
    ScheduledChange:
      type: object
      description: >-
        A plan or quantity change · pending while `appliedAt` and `canceledAt`
        are null.
      properties:
        id:
          type: string
          example: ssc_abc123
        type:
          type: string
          enum:
            - UPGRADE
            - DOWNGRADE
            - INSTRUMENT_CHANGE
        targetPriceId:
          type: string
          nullable: true
          description: Target plan (the current one on a quantity-only change).
          example: pro_9m2k1x8s7d6f
        targetQuantity:
          type: integer
          nullable: true
          description: Units held once applied.
        targetProduct:
          allOf:
            - $ref: '#/components/schemas/SubscriptionProduct'
          nullable: true
          description: >-
            The target plan itself, resolved from `targetPriceId` so you do not
            have to fetch it. Null when the change names no target, or when the
            product has since been deleted — a change is kept as billing history
            and outlives the plan it pointed at.
        scheduledFor:
          type: string
          format: date-time
          description: >-
            When the change applies: `now` for an immediate upgrade, the period
            end for a downgrade.
        appliedAt:
          type: string
          format: date-time
          nullable: true
          description: Null while pending; set once the plan swap has been applied.
        canceledAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            Set when the change was called off before it took effect (`POST
            /cancel-plan-change`, or superseded by a newer scheduled change). It
            will never apply. A change is PENDING while `appliedAt` and
            `canceledAt` are both null.
    Payment:
      type: object
      description: >
        Public payment shape (`PaymentPublic`).


        **The response is scoped to its own rail.** `rail` says which: on
        `crypto` the

        card-only fields (`declineCode`, `declineCategory`, `declineAdvice`,

        `declineNetworkCode`, `threeDSecureResult`) are ABSENT, and on `card`
        the

        chain fields (`tokenAmount`, `tokenFeeAmount`, `quoteFiatAmountCents`,

        `quoteFiatCurrency`, `cryptoSettlementMode`, `settlementChainId`,

        `settlementAsset`, `settlementRecipient`, `depositAddress`,

        `settlementTxHash`, `lifiTxHash`, `lifiSubstatus`) are. Absent, not null
        · a

        field that could only ever read `null` on your rail is not "unknown
        yet", and

        branching on it is branching on a case that cannot happen.
      properties:
        rail:
          type: string
          enum:
            - card
            - crypto
          description: >-
            Card or chain · which half of this shape the payment fills in. Read
            it instead of inferring the rail from a null field.
        id:
          type: string
          example: pay_abc123
        organizationId:
          type: string
        customerId:
          type: string
          nullable: true
        productId:
          type: string
          nullable: true
        subscriptionId:
          type: string
          nullable: true
        status:
          $ref: '#/components/schemas/PaymentStatus'
        paymentMethodCategory:
          type: string
          nullable: true
          enum:
            - CARD
            - AMEX
            - COMMERCIAL_CARD
            - APPLE_PAY
            - GOOGLE_PAY
            - KLARNA
            - IDEAL
            - BANCONTACT
            - TWINT
            - BLIK
            - AFFIRM
            - ALMA
            - BILLIE
            - SCALAPAY
            - MULTIBANCO
            - PAYPAL
            - SEPA_DIRECT_DEBIT
            - ACH_DIRECT_DEBIT
            - STABLECOIN
            - VOLATILE_CRYPTO
          description: >-
            The method the NETWORK reported, resolved at settlement · not the
            one the payer clicked. Null until then, so absence means "not
            settled yet", never "a card". It names the payer's instrument
            (Klarna, iDEAL, Apple Pay), never the acquirer that processed it.
        declineCode:
          type: string
          nullable: true
          description: >-
            Normalized decline reason for a FAILED charge (Stripe-parity
            `decline_code`), e.g. `insufficient_funds`, `not_supported`,
            `stolen_card`, `expired_card`. Null on non-declined / non-card
            payments.
        declineCategory:
          type: string
          nullable: true
          enum:
            - SOFT
            - HARD
            - AUTHENTICATION_REQUIRED
          description: >-
            Retryability of the decline: SOFT = transient (a retry may succeed),
            HARD = terminal (the credential is unusable),
            AUTHENTICATION_REQUIRED = the card is fine but the issuer wants the
            cardholder to authenticate, which no retry can supply. Null when not
            a decline.
        declineAdvice:
          type: string
          nullable: true
          enum:
            - TRY_AGAIN_LATER
            - DO_NOT_TRY_AGAIN
            - CONFIRM_CARD_DATA
          description: >-
            The card network's own instruction on what to do next. Distinct from
            `declineCategory`, which is our reading of the reason: the same
            reason can come back TRY_AGAIN_LATER on one charge and
            DO_NOT_TRY_AGAIN on another, and only this field says which. Null
            when the network returned no advice.
        declineNetworkCode:
          type: string
          nullable: true
          description: >-
            The card network's own decline code, verbatim (e.g. Visa `1A`). Not
            normalized on purpose · it is the identifier the cardholder's bank
            recognises. Null when the network returned none.
        threeDSecureResult:
          type: string
          nullable: true
          enum:
            - AUTHENTICATED
            - ATTEMPT_ACKNOWLEDGED
            - FAILED
            - NOT_SUPPORTED
            - PROCESSING_ERROR
            - NOT_REQUESTED
          description: >-
            How the 3-D Secure step ended for this charge. A refusal that
            follows a FAILED or PROCESSING_ERROR authentication is not the same
            event as an issuer refusing the funds, and no decline code separates
            them. Null when the charge never reached an authentication step.
        displayName:
          type: string
          nullable: true
        displayDescription:
          type: string
          nullable: true
        displayImageUrl:
          type: string
          nullable: true
        priceCents:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true
        tokenAmount:
          type: string
          nullable: true
        tokenFeeAmount:
          type: string
          nullable: true
        quoteFiatAmountCents:
          type: integer
          nullable: true
        quoteFiatCurrency:
          type: string
          nullable: true
        grossAmountCents:
          type: integer
          nullable: true
        platformFeeCents:
          type: integer
          nullable: true
        merchantNetCents:
          type: integer
          nullable: true
        vatAmountCents:
          type: integer
          nullable: true
          description: >-
            The VAT portion INCLUDED in `grossAmountCents`. Null whenever
            `vatRateBps` is · same three conditions.
        vatRateBps:
          type: integer
          nullable: true
          description: >-
            Applied VAT rate in basis points (2000 = 20%). Non-null only on a
            CARD charge, for a merchant Suby is the seller of record for, billed
            in the EU · null everywhere else. A crypto payment is never taxed
            whatever the merchant's mode: the payer sends an amount sized from
            the merchant's price and nothing is added on top.
        taxInclusive:
          type: boolean
          description: >-
            true = price is VAT-inclusive (gross = price); false = exclusive
            (gross = price + VAT). The behavior the charge was priced with,
            snapshotted · it records how the price was READ, so it stays
            meaningful on a charge that collected no VAT (crypto, non-MoR),
            where gross = price either way.
        fxSurchargeCents:
          type: integer
          nullable: true
          description: >-
            Currency-conversion surcharge, INCLUDED in platformFeeCents (never a
            second deduction). Billed only when the acquirer settled this charge
            in a currency it was not priced in, and only on a fee plan that
            passes it through. Null = not billed.
        internationalCardSurchargeCents:
          type: integer
          nullable: true
          description: >-
            Surcharge for a card issued outside your own region, INCLUDED in
            platformFeeCents. Resolved at capture from the card's issuing
            country. Null = not billed.
        rollingReserveCents:
          type: integer
          nullable: true
          description: >-
            Held back at capture and released on maturity · NOT a fee and NOT
            deducted from merchantNetCents. It is your own money kept aside to
            cover a refund or a chargeback, and it is the difference between
            merchantNetCents and what the balance was credited. Null when your
            account carries no reserve.
        refundedAmountCents:
          type: integer
          nullable: true
        refundedAt:
          type: string
          format: date-time
          nullable: true
        cryptoSettlementMode:
          type: string
          nullable: true
        settlementChainId:
          type: integer
          nullable: true
        settlementAsset:
          type: string
          nullable: true
        settlementRecipient:
          type: string
          nullable: true
        depositAddress:
          type: string
          nullable: true
        settlementTxHash:
          type: string
          nullable: true
          description: Ours · the settlement transfer. Null until the chain produces it.
        lifiTxHash:
          type: string
          nullable: true
          description: >-
            The bridge's transaction, on a payment we convert cross-chain. Null
            on a same-chain settlement, and until the bridge fills.
        lifiSubstatus:
          type: string
          nullable: true
          description: >-
            Where that bridge transfer currently stands, as the bridge reports
            it. Null when no bridge is involved.
        successUrl:
          type: string
          nullable: true
        cancelUrl:
          type: string
          nullable: true
        externalRef:
          type: string
          nullable: true
        paymentReceivedAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the payer's crypto deposit landed at the address we gave them.
            It does NOT mean the sale is confirmed: on-chain funds still have to
            clear the settlement step (the SubyPayment split, or the bridge
            transaction) before we announce anything. Null on card charges.
        paymentSettledAt:
          type: string
          format: date-time
          nullable: true
          description: When the merchant's share reached its destination on-chain.
        paymentConfirmedAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the sale was confirmed to both sides · the instant
            `payment.succeeded` was emitted, the access granted, the receipt
            sent and the payer shown a success screen. On a crypto payment this
            is a few seconds after `paymentReceivedAt`: the settlement step that
            proves the deposit spendable has to clear first, and if it takes
            longer than ~35s we confirm anyway rather than hold the payer on our
            settlement. Null on card charges, where the capture (COMPLETED) is
            itself the confirmation.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            When an unfinished intent stops being payable. Set for card/APM
            (bounded to the checkout-session window, else 30 min) and crypto
            (per-chain deposit window · 60 min on Bitcoin). Past this, an
            abandoned PENDING intent is swept to EXPIRED. On a crypto deposit
            the sweep waits out a per-chain grace first (~20 min on Bitcoin, ~3
            elsewhere): a transfer broadcast just inside the window is only
            visible to us just outside it, and one that arrives in that grace
            still settles. So `EXPIRED` lands later than this timestamp, and a
            deposit already seen is never expired by the clock at all · it is
            only waiting on confirmations. Null only for legacy rows.
        statusTimeline:
          type: array
          description: >-
            Append-only status history · every status the payment moved through,
            oldest→newest. The last entry is where it currently stands /
            stopped. Populated on detail + list reads; empty for legacy rows
            created before the feature.
          items:
            type: object
            properties:
              status:
                type: string
                description: The status the payment transitioned into.
                enum:
                  - PENDING
                  - AUTHORIZED
                  - PENDING_3DS
                  - PENDING_REDIRECT
                  - BRIDGING
                  - RECEIVED
                  - PARTIAL
                  - COMPLETED
                  - FAILED
                  - DECLINED
                  - REFUNDED
                  - PARTIALLY_REFUNDED
                  - CANCELED
                  - EXPIRED
              at:
                type: string
                format: date-time
                description: When the transition happened.
        items:
          type: array
          description: >-
            What was bought, line by line, frozen at create time. A payment link
            charges ONE total against N products, so `productId` alone cannot
            describe the order · read this instead. Absent on payments created
            before the field existed and on charges whose caller priced no
            lines. Amounts are in the payment's `currency`; a discount applies
            to the total, so the lines do not necessarily sum to
            `grossAmountCents`.
          items:
            type: object
            properties:
              productId:
                type: string
                nullable: true
                description: >-
                  Catalogue product · null on an ad-hoc line (a price and a
                  label, no catalogue entry).
              name:
                type: string
                description: Product name as displayed at checkout, frozen at that moment.
              quantity:
                type: integer
              unitPriceCents:
                type: integer
        asset:
          type: object
          nullable: true
          description: >-
            What the PAYER sent · their chain and their token. Populated on
            crypto reads. Distinct from `settlementAsset` / `settlementChain`,
            which say where the funds are HEADED: on an account that converges
            every token to one stablecoin the two sides differ on almost every
            payment.
          properties:
            chainId:
              type: integer
            chainName:
              type: string
              nullable: true
              description: >-
                The chain's proper noun, e.g. `BNB Smart Chain`. Data, never
                localized.
            chainLogoUrl:
              type: string
              nullable: true
            symbol:
              type: string
            decimals:
              type: integer
            address:
              type: string
              description: Token contract / SPL mint, or `NATIVE`.
            logoUrl:
              type: string
              nullable: true
        settlementChain:
          type: object
          nullable: true
          description: >-
            The chain named by `settlementChainId` · where the funds converge.
            Read together with `settlementAsset`. Null on card charges.
          properties:
            chainId:
              type: integer
            name:
              type: string
              nullable: true
              description: The chain's proper noun, e.g. `Polygon`. Data, never localized.
            logoUrl:
              type: string
              nullable: true
        deposit:
          type: object
          nullable: true
          description: >-
            Where the payer's deposit stands · **`GET /v3/payments/{id}` only**,
            and only while a crypto deposit is being watched. The list read does
            not carry it: it is what you poll ONE payment with, and paging it in
            would parse three chain snapshots per row to answer a question
            nobody asks of a page.
          properties:
            expected:
              type: string
            received:
              type: string
            remaining:
              type: string
            confirmations:
              type: object
              nullable: true
              properties:
                observed:
                  type: integer
                required:
                  type: integer
                detectedAt:
                  type: string
                  format: date-time
            warnings:
              type: array
              items:
                type: object
                properties:
                  code:
                    type: string
                    enum:
                      - INSUFFICIENT_DEPOSIT_AMOUNT
                      - RBF_DETECTED
                      - NETWORK_FEE_SURCHARGE
                  message:
                    type: string
                  data:
                    type: object
                    additionalProperties: true
    Error:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: Machine-readable error code.
          example: NOT_FOUND
        message:
          type: string
          example: Resource not found
        data:
          description: Optional error detail.
    SubscriptionStatus:
      type: string
      enum:
        - INCOMPLETE
        - TRIALING
        - PENDING_3DS
        - PENDING_REDIRECT
        - ACTIVE
        - PAST_DUE
        - PAUSED
        - CANCELED
        - EXPIRED
    SubscriptionProduct:
      type: object
      description: >
        The plan behind the subscription. Present only when `?expand=product`
        asked for it.


        Its `priceCents` is the plan's price TODAY, which is not necessarily
        what this

        subscriber is billed · read `priceCents` on the subscription itself for
        that.
      properties:
        id:
          type: string
          example: pro_abc123
        name:
          type: string
        description:
          type: string
          nullable: true
        imageUrl:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - ACTIVE
            - ARCHIVED
          description: >-
            An `ARCHIVED` plan is closed to new subscribers and keeps billing
            the ones it already has.
        priceCents:
          type: string
          nullable: true
          description: The plan's CURRENT price, in minor units.
        currency:
          type: string
          nullable: true
        recurringInterval:
          type: string
          nullable: true
          enum:
            - DAY
            - WEEK
            - MONTH
            - YEAR
        recurringIntervalCount:
          type: integer
          nullable: true
        recurringCycleCount:
          type: integer
          nullable: true
          description: Agreed number of cycles · null = until canceled.
        trialDurationCount:
          type: integer
          nullable: true
        trialDurationUnit:
          type: string
          nullable: true
          enum:
            - DAY
            - WEEK
            - MONTH
            - YEAR
        adjustableQuantity:
          type: boolean
          description: Whether the subscriber may change the quantity themselves.
        adjustableMin:
          type: integer
          nullable: true
        adjustableMax:
          type: integer
          nullable: true
    PaymentStatus:
      type: string
      enum:
        - PENDING
        - AUTHORIZED
        - PENDING_3DS
        - PENDING_REDIRECT
        - BRIDGING
        - RECEIVED
        - PARTIAL
        - COMPLETED
        - FAILED
        - DECLINED
        - REFUNDED
        - PARTIALLY_REFUNDED
        - CANCELED
        - EXPIRED
  responses:
    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error: UNAUTHORIZED
            message: Invalid or missing API key
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Suby-Api-Key
      description: Secret API key. `sk_live_…` (production) or `sk_sandbox_…` (sandbox).

````

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