> ## 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.

# List subscriptions

> There is no `POST /v3/subscriptions`. A subscription opens from a checkout
session in `mode: "subscription"` on a `billingMode: subscription` product.
Starting one here meant sending a card token in the request body, which is
not a shape this API accepts.

These routes drive a subscription that already exists.




## OpenAPI

````yaml /v3-beta/api-reference/openapi.yaml get /v3/subscriptions
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:
    get:
      tags:
        - Subscriptions
      summary: List subscriptions
      description: >
        There is no `POST /v3/subscriptions`. A subscription opens from a
        checkout

        session in `mode: "subscription"` on a `billingMode: subscription`
        product.

        Starting one here meant sending a card token in the request body, which
        is

        not a shape this API accepts.


        These routes drive a subscription that already exists.
      operationId: listSubscriptions
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: customerId
          in: query
          schema:
            type: string
            example: cus_abc123
        - name: productId
          in: query
          schema:
            type: string
            example: pro_abc123
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/SubscriptionStatus'
        - $ref: '#/components/parameters/SubscriptionExpand'
      responses:
        '200':
          description: Subscriptions retrieved
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              $ref: '#/components/schemas/Subscription'
                          pagination:
                            $ref: '#/components/schemas/CursorPagination'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'
components:
  parameters:
    Limit:
      name: limit
      in: query
      description: Page size (1–100).
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Cursor:
      name: cursor
      in: query
      description: Opaque pagination cursor from a previous `pagination.nextCursor`.
      schema:
        type: string
    SubscriptionExpand:
      name: expand
      in: query
      required: false
      description: >
        Comma-separated relations to join onto the subscription. Each is absent
        unless

        asked for, so a caller reconciling renewals by id pays for none of them.
        Every

        value names the field it fills.

          - `product` · the plan behind the subscription — name, description, image,
            its CURRENT price and cadence, its trial and agreed cycle count, and its
            `status` (an `ARCHIVED` plan keeps billing the people already on it).
          - `customer` · the buyer's id and email.
          - `nextCharge` · what the next renewal will take and when, sized the way the
            renewal itself sizes it (the agreed price through this subscription's own
            VAT context). `null` when nothing further is scheduled.
          - `lifetime` · settled-charge totals over the whole subscription, aggregated
            server-side rather than folded from a page of charges.
          - `renewalAttempts` · every renewal attempt, newest cycle first, with the
            issuer's own decline code and the payment behind it.
          - `accesses` · the entitlements this subscription granted and whether the
            customer ever claimed them.

        **`product` and `customer` are the only two the LIST accepts.** The
        other four

        are per-subscription computations, and running them for every row of a
        page is

        four more round trips per subscription — so the list refuses them with
        `422`

        rather than accepting the word and returning nothing, which would read
        as "this

        subscription has none" on every row. Retrieve one subscription to get
        them.


        An unrecognised value is refused with `422` rather than ignored.
      schema:
        type: string
        example: product,customer,nextCharge
  schemas:
    SubscriptionStatus:
      type: string
      enum:
        - INCOMPLETE
        - TRIALING
        - PENDING_3DS
        - PENDING_REDIRECT
        - ACTIVE
        - PAST_DUE
        - PAUSED
        - CANCELED
        - EXPIRED
    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
        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
    CursorPagination:
      type: object
      properties:
        nextCursor:
          type: string
          nullable: true
          description: Pass as `cursor` to fetch the next page. `null` on the last page.
        hasMore:
          type: boolean
        total:
          type: integer
          description: Present only in dashboard offset mode.
    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
    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.
    ValidationErrorEnvelope:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            data:
              type: object
              properties:
                fieldErrors:
                  type: array
                  items:
                    type: object
                    properties:
                      field:
                        type: string
                      message:
                        type: string
  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
    ValidationError:
      description: Request failed schema validation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationErrorEnvelope'
          example:
            success: false
            error: VALIDATION_ERROR
            message: Validation failed
            data:
              fieldErrors:
                - field: method
                  message: method is a required field
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Suby-Api-Key
      description: Secret API key. `sk_live_…` (production) or `sk_sandbox_…` (sandbox).

````