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

# Retry a failing renewal now

> Bring the next dunning attempt forward on a `PAST_DUE` subscription.

**It does not charge.** It moves `nextRenewalAttemptAt` to now, and the renewal
sweep makes the attempt it was already going to make — on its own retry budget,
writing its own attempt record, exactly as a scheduled retry would. Poll the
subscription (or listen for `subscription.*`) for the outcome.

Anything other than `PAST_DUE` has no retry to bring forward and is refused with
`422`.




## OpenAPI

````yaml /v3-beta/api-reference/openapi.yaml post /v3/subscriptions/{id}/retry-renewal
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}/retry-renewal:
    post:
      tags:
        - Subscriptions
      summary: Retry a failing renewal now
      description: >
        Bring the next dunning attempt forward on a `PAST_DUE` subscription.


        **It does not charge.** It moves `nextRenewalAttemptAt` to now, and the
        renewal

        sweep makes the attempt it was already going to make — on its own retry
        budget,

        writing its own attempt record, exactly as a scheduled retry would. Poll
        the

        subscription (or listen for `subscription.*`) for the outcome.


        Anything other than `PAST_DUE` has no retry to bring forward and is
        refused with

        `422`.
      operationId: retrySubscriptionRenewal
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Next attempt moved to now
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - properties:
                      data:
                        type: object
                        properties:
                          subscription:
                            $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: >-
            Not `PAST_DUE`, so there is no pending attempt to bring forward
            (`SUBSCRIPTION_NOT_MODIFIABLE`)
          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) that makes this POST safe to
        retry: the first request executes and its response is cached for 24h; a
        retry with the SAME key replays that response instead of re-executing
        (no duplicate payment/subscription). A reused key with a different
        request → `422 IDEMPOTENCY_KEY_CONFLICT`; a retry while the first is
        still in flight → `409`. See the Idempotency guide.
      schema:
        type: string
        maxLength: 255
        example: 5f3b9c2e-1a4d-4f2b-9c31-7e2a1b6d8c04
  schemas:
    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
    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
  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
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error: NOT_FOUND
            message: Resource not found
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Suby-Api-Key
      description: Secret API key. `sk_live_…` (production) or `sk_sandbox_…` (sandbox).

````