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

# Create Plan

> Creates a recurring subscription plan and returns the plan details with its payment link.



## OpenAPI

````yaml https://payment-b2b.singapay.id/api/docs/merchant-api.json post /api/v2.0/recurring/plans
openapi: 3.1.0
info:
  title: Singa Merchant API
  description: >-
    OpenAPI specification for the merchant/partner HTTP API. All routes below
    are additionally protected by `ip.whitelisted.merchant` — the caller IP must
    be registered for the credential or merchant. Obtain a JWT using `POST
    /api/v1.0/access-token/b2b` (Basic auth) or `POST
    /api/v1.1/access-token/b2b` (X-Signature) before calling secured endpoints.
  version: 1.0.0
servers:
  - url: https://sandbox-payment-b2b.singapay.id
    description: >-
      API host. Paths include `/api` prefix (see `RouteServiceProvider`).
      Replace scheme/host with your environment.
security: []
tags:
  - name: Security
    description: >-
      Merchant authentication (`OauthMerchantTokenController`). **v1.1** B2B
      token uses `X-CLIENT-ID`, `X-PARTNER-ID`, and `X-Signature` (no Basic
      auth). Secured routes also require the issued Bearer JWT plus
      `X-PARTNER-ID`.
  - name: Accounts
    description: >-
      Account management (`routes/merchantApiRoute.php`, `v1.0`). Path parameter
      `{id}` is always the account ULID.
  - name: Balance Inquiry
    description: >-
      Merchant and per-account balance inquiry (`BalanceController`, `v1.0`).
      Path `account_id` is the account ULID.
  - name: Statements
    description: >-
      Per-account statement list and detail
      (`AccountController::accountStatements`, `accountStatementDetail`, prefix
      `v1.0/statements`). Flugger responses; detail path param `{statement_id}`
      maps to `statements.transaction_id`.
  - name: Payment Link
    description: >-
      Payment link CRUD and payment-method catalog (`PaymentLinkApiController`,
      prefix `v1.0/payment-link-manage`). `account_id` is ULID;
      `payment_link_id` is numeric `payment_links.id`.
  - name: Payment Link History
    description: >-
      Payment link transaction/history listing and detail
      (`PaymentLinkApiController`, prefix `v1.0/payment-link-histories`).
      `history_id` is numeric `payment_link_histories.id`.
  - name: Virtual Account
    description: >-
      Native VA CRUD (`VirtualAccountController`, prefix
      `v1.0/virtual-accounts`). `account_id` and `virtual_account_id` are ULIDs.
  - name: VA Transaction
    description: >-
      VA money-in transaction listing and detail (`VirtualAccountController`,
      prefix `v1.0/va-transactions`).
  - name: QRIS (Money In)
    description: >-
      MPM dynamic QRIS list, show, and generate (`QrisMpmDynamicApiController`,
      prefix `v1.0/qris-dynamic`).
  - name: QRIS (Money Out)
    description: >-
      Issuer MPM decode/inquiry, payment credit (money out), and transaction
      status (`QrisIssuerMpmController`, `QrisApiV2Controller::checkStatus`,
      prefix `v2.0/qris`). Related list/detail: `GET
      /api/v2.0/qris/transaction/...`.
  - name: E-Wallet (Money In)
    description: >-
      E-Wallet Native checkout and transactions (`EwalletNativeApiController`,
      `EwalletNativeTransactionApiController`, `EwalletNativeV2ApiController`).
      Paths include `v1.0/ewallet-native`, `v1.0/ewallet-native-transactions`,
      and `v2.0/ewallet-native`.
  - name: E-Wallet (Money Out)
    description: >-
      E-wallet disbursement / top-up to beneficiary wallets
      (`EWalletTopUpController`, prefix `v2.0/ewallet`). Responses use the
      custom **`MerchantV2ApiEnvelope`** with **`MerchantV2ResponseCode`**
      (SP000–SP020).
  - name: Card (Money In)
    description: >-
      One-time card payment, cancel, and inquiry
      (`CardPaymentMerchantApiController`, prefix `v2.0/card`).
  - name: Subscription (Recurring)
    description: >-
      Credit-card recurring subscription plans (`SubscriptionPlanApiController`,
      prefix `v2.0/recurring`). Plan `{id}` is UUID (`sub_plans.id`).
  - name: Direct Debit
    description: >-
      Direct Debit — bind a customer bank account once via hosted webview, then
      charge it host-to-host (`DirectDebitMerchantController`, prefix
      `v2.0/direct-debit`). Binding `{binding_id}` and transaction
      `{transaction_id}` are UUIDs. Responses use the
      **`MerchantV2ApiEnvelope`** with both standard SP000–SP020 codes and
      Direct-Debit-specific codes (`SP_DD_*`). The `/charge` endpoint is
      additionally protected by **`X-Signature`** + **`X-Timestamp`**
      (`verify.signature-internal`); see operation parameters for the signing
      scheme.
  - name: Account Transfer
    description: >-
      Move funds between sub-accounts within the same merchant
      (`AnotherAccountApiController`, prefix `v1.0/account-transfer`). The
      transfer endpoint requires request signature headers
      (`verify.signature-internal`). Both accounts must belong to the
      authenticated merchant and be accessible to the credential.
  - name: Disbursement (Money Out)
    description: >-
      Bank disbursement (`DisbursementController` **v1.0**,
      `DisbursementV2Controller` **v2.0**). **v1.0**
      list/show/fee/beneficiary/transfer use Flugg envelopes; v1 inquiry-status
      uses the **custom v2 envelope** (`MerchantV2ApiEnvelope`, codes
      **SP000–SP020**). **v2.0** check-beneficiary, transfer, and inquiry-status
      use the same custom envelope — see component schema
      **`MerchantV2ResponseCode`** for the full response code table.
  - name: Cardless Withdrawal
    description: >-
      Cardless withdrawal API for initiating ATM cash withdrawals without a
      physical card. Supports creating withdrawals with OTP generation, listing
      transaction history, viewing transaction details, canceling pending
      withdrawals, and deleting canceled records. All endpoints use the
      `v1.0/cardless-withdrawals` route prefix and the standard success/error
      response envelope. Path parameter `{id}` refers to the `transaction_id`
      (platform-assigned business identifier).
paths:
  /api/v2.0/recurring/plans:
    post:
      tags:
        - Subscription (Recurring)
      summary: Create Plan
      description: >-
        Creates a recurring subscription plan and returns the plan details with
        its payment link.
      operationId: recurringPlanCreate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRecurringPlanRequest'
      responses:
        '201':
          description: >-
            **SP000** Successfully — plan created (`data` is
            `RecurringPlanData`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecurringPlanSuccessResponse'
        '422':
          description: Validation error (laravel-responder envelope).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecurringPlanError422'
        '500':
          description: '**SP002** General Failure — unexpected error.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecurringPlanErrorGeneralFailureResponse'
      security:
        - BearerAuth: []
          PartnerId: []
components:
  schemas:
    CreateRecurringPlanRequest:
      description: >-
        Payload for `CreateSubscriptionPlanRequest`. **Exactly one** of `amount`
        or `items` must be present (mutually exclusive). Per-cycle charge
        (amount-only or sum of `quantity * unit_price`) must be **at least IDR
        10,000** when greater than zero (`ChannelType::MIN_CARD_AMOUNT_IDR`).
      required:
        - name
        - customer_name
        - customer_email
        - customer_phone
        - account_id
        - schedule
      properties:
        name:
          description: Human-readable plan name.
          type: string
          maxLength: 255
          example: Monthly Pro Subscription
        subscription_id:
          description: >-
            Stored in `sub_plans.reference_id`; must be unique on that column
            when provided.
          type:
            - string
            - 'null'
          maxLength: 100
          example: SUB-2026-0001
        merchant_reff_no:
          description: Optional merchant reference number for reconciliation.
          type:
            - string
            - 'null'
          maxLength: 255
          example: REF-0001
        amount:
          description: >-
            Per-cycle charge in IDR. Use **without** `items` for amount-only
            plans.
          type: number
          minimum: 0
          example: 150000
        currency:
          description: ISO 4217 currency code. Defaults to `IDR`.
          type:
            - string
            - 'null'
          maxLength: 3
          minLength: 3
          example: IDR
        items:
          description: >-
            Use **without** `amount` for itemized plans; per-cycle total = sum
            of `quantity * unit_price`.
          type: array
          items:
            $ref: '#/components/schemas/RecurringPlanItemInput'
          minItems: 1
        customer_name:
          description: Full name of the subscribing customer.
          type: string
          maxLength: 191
          example: John Doe
        customer_email:
          description: Customer email address.
          type: string
          format: email
          maxLength: 191
          example: john.doe@example.com
        customer_phone:
          description: Customer contact phone number.
          type: string
          maxLength: 50
          example: '081234567890'
        customer_id:
          description: Optional merchant-side customer identifier.
          type:
            - string
            - 'null'
          maxLength: 100
          example: CUST-001
        account_id:
          description: Account ULID owned by the authenticated merchant.
          type: string
          maxLength: 50
          example: 01K5G4FZZ18DMK0M5QTR8Y9QY9
        schedule:
          $ref: '#/components/schemas/RecurringPlanScheduleInput'
        payment_type:
          description: Preferred payment instrument for the plan.
          type:
            - string
            - 'null'
          enum:
            - credit_card
            - gopay
          example: credit_card
        return_url:
          description: URL the customer is redirected to after completing payment setup.
          type:
            - string
            - 'null'
          format: uri
          maxLength: 2048
          example: https://merchant.example.com/subscription/return
        retry_policy:
          oneOf:
            - $ref: '#/components/schemas/RecurringPlanRetryPolicyInput'
            - type: 'null'
        retry_count:
          description: Deprecated — use `retry_policy.max_attempts`.
          type:
            - integer
            - 'null'
          maximum: 5
          minimum: 1
          example: 3
          deprecated: true
        retry_interval_days:
          description: Deprecated — use `retry_policy.interval_days`.
          type:
            - integer
            - 'null'
          maximum: 7
          minimum: 1
          example: 1
          deprecated: true
        failed_payment_action:
          description: Deprecated — use `retry_policy.failed_payment_action`.
          type:
            - string
            - 'null'
          enum:
            - continue_plan
            - stop_plan
          example: continue_plan
          deprecated: true
        charge_immediately:
          description: >-
            Charge the first cycle immediately on creation instead of waiting
            for `start_time`.
          type:
            - boolean
            - 'null'
          example: false
        allow_manual_payment:
          description: Allow the customer to pay bills manually via the payment link.
          type:
            - boolean
            - 'null'
          example: true
        allow_user_notification:
          description: >-
            Send subscription notifications (reminders, receipts) to the
            customer.
          type:
            - boolean
            - 'null'
          example: true
        metadata:
          description: Free-form metadata stored with the plan.
          properties:
            description:
              description: Optional plan description.
              type:
                - string
                - 'null'
              maxLength: 1000
              example: Pro plan monthly billing
          type:
            - object
            - 'null'
          additionalProperties: true
      type: object
    RecurringPlanSuccessResponse:
      description: >-
        Merchant v2 envelope returned on a successful plan operation (`SP000`).
        Used by create (HTTP 201), show, update, and cancel (HTTP 200). The
        `data.upgrade` block is present only on an upgrade/downgrade.
      required:
        - response_code
        - response_message
        - data
      properties:
        response_code:
          $ref: '#/components/schemas/MerchantV2ResponseCode'
        response_message:
          $ref: '#/components/schemas/MerchantV2ResponseMessage'
        data:
          $ref: '#/components/schemas/RecurringPlanData'
      type: object
      example:
        response_code: SP000
        response_message: Successfully
        data:
          id: 9f8b6c2e-1a2b-4c3d-8e4f-5a6b7c8d9e0f
          name: Monthly Pro Subscription
          amount: '150000'
          currency: IDR
          created_at: '2026-06-09T10:00:00+07:00'
          schedule:
            interval: 1
            interval_unit: month
            current_interval: 1
            total_interval: 12
            start_time: '2026-07-01T00:00:00+07:00'
            previous_payment_at: null
            next_payment_at: '2026-07-01T00:00:00+07:00'
          status: active
          payment_type: credit_card
          retry_policy:
            max_attempts: 3
            interval_days: 1
            failed_payment_action: continue_plan
          metadata:
            description: Pro plan monthly billing
            extra: []
          subscription_id: SUB-2026-0001
          merchant_reff_no: REF-0001
          payment_link_url: https://pay.singapay.id/sub/9f8b6c2e
          parent_plan_id: null
          created_from: null
    RecurringPlanError422:
      description: Merchant Envelope for 422 error
      properties:
        status:
          description: Business response code.
          type: integer
          example: 422
        success:
          description: Human-readable message.
          type: boolean
          example: false
        errors:
          description: Human-readable message.
          properties:
            code:
              description: Business response code.
              type: integer
              example: 422
            message:
              description: Business response code.
              type: string
              example: The given data was invalid.
            errors:
              description: Business response code.
              properties:
                subscription_id:
                  description: >-
                    List of validation error messages for the `subscription_id`
                    field.
                  type: array
                  items:
                    type: string
                    example: The subscription id has already been taken.
                  minItems: 1
              type: object
          type: object
      type: object
    RecurringPlanErrorGeneralFailureResponse:
      description: >-
        Merchant v2 envelope for unexpected server-side failures (`SP002`, HTTP
        500).
      required:
        - response_code
        - response_message
      properties:
        response_code:
          description: Business response code.
          type: string
          example: SP002
        response_message:
          description: Human-readable message.
          type: string
          example: An unexpected error occurred
        data:
          type:
            - object
            - 'null'
          example: null
      type: object
      example:
        response_code: SP002
        response_message: An unexpected error occurred
        data: null
    RecurringPlanItemInput:
      description: >-
        A single line item used to build an itemized plan. Per-cycle total is
        the sum of `quantity * unit_price` across all items.
      required:
        - item_name
        - quantity
        - unit_price
      properties:
        item_name:
          description: Display name of the line item.
          type: string
          maxLength: 191
          example: Pro Plan License
        item_type:
          description: Optional item category or type.
          type:
            - string
            - 'null'
          maxLength: 50
          example: subscription
        quantity:
          description: Number of units billed each cycle.
          type: integer
          minimum: 1
          example: 1
        unit_price:
          description: Price per unit in IDR.
          type: number
          minimum: 0
          example: 50000
      type: object
    RecurringPlanScheduleInput:
      description: >-
        Billing schedule that controls how often and how many times the plan
        charges the customer.
      required:
        - interval
        - interval_unit
        - start_time
      properties:
        interval:
          description: >-
            Number of `interval_unit`s between charges (e.g. `1` with `month` =
            monthly).
          type: integer
          minimum: 1
          example: 1
        interval_unit:
          description: Unit of the billing interval.
          type: string
          enum:
            - day
            - week
            - month
          example: month
        total_interval:
          description: Maximum number of billing cycles. Omit for an open-ended plan.
          type:
            - integer
            - 'null'
          minimum: 1
          example: 12
        start_time:
          description: First charge window; must be `after_or_equal:today` (server date).
          type: string
          format: date-time
          example: '2026-07-01T00:00:00+07:00'
      type: object
    RecurringPlanRetryPolicyInput:
      description: Optional retry policy applied to every cycle when a charge fails.
      properties:
        max_attempts:
          description: Automatic retry attempts after a failed charge (1–5).
          type:
            - integer
            - 'null'
          maximum: 5
          minimum: 1
          example: 3
        interval_days:
          description: Days to wait between consecutive retries (1–7).
          type:
            - integer
            - 'null'
          maximum: 7
          minimum: 1
          example: 1
        failed_payment_action:
          description: Action taken once retries are exhausted.
          type:
            - string
            - 'null'
          enum:
            - continue_plan
            - stop_plan
          example: continue_plan
      type: object
    MerchantV2ResponseCode:
      description: SingaPay custom business response code.
      type: string
      example: SP000
    MerchantV2ResponseMessage:
      description: Human-readable label paired with `response_code`.
      type: string
      example: Successfully
    RecurringPlanData:
      description: '`data` object shape from `SubscriptionPlanResource` (success responses).'
      properties:
        id:
          description: Plan UUID (`sub_plans.id`).
          type: string
          format: uuid
          example: 9f8b6c2e-1a2b-4c3d-8e4f-5a6b7c8d9e0f
        name:
          description: Plan name.
          type:
            - string
            - 'null'
          example: Monthly Pro Subscription
        amount:
          description: Stringified IDR amount (integer string when whole).
          type: string
          example: '150000'
        currency:
          description: ISO 4217 currency code.
          type: string
          example: IDR
        created_at:
          description: Plan creation timestamp.
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-06-09T10:00:00+07:00'
        schedule:
          $ref: '#/components/schemas/RecurringPlanScheduleBlock'
        status:
          description: Current plan status.
          type:
            - string
            - 'null'
          example: active
        payment_type:
          description: Resolved payment instrument.
          type:
            - string
            - 'null'
          example: credit_card
        retry_policy:
          $ref: '#/components/schemas/RecurringPlanRetryPolicyBlock'
        metadata:
          description: Plan metadata.
          properties:
            description:
              description: Plan description.
              type:
                - string
                - 'null'
              example: Pro plan monthly billing
            extra:
              description: Free-form merchant metadata.
              type: object
              additionalProperties: true
          type: object
        subscription_id:
          description: API-facing alias of `sub_plans.reference_id`.
          type:
            - string
            - 'null'
          example: SUB-2026-0001
        merchant_reff_no:
          description: Merchant reference number.
          type:
            - string
            - 'null'
          example: REF-0001
        payment_link_url:
          description: Payment link for the plan.
          type:
            - string
            - 'null'
          format: uri
          example: https://pay.singapay.id/sub/9f8b6c2e
        parent_plan_id:
          description: Superseded plan UUID when this plan came from an upgrade.
          type:
            - string
            - 'null'
          format: uuid
          example: null
        created_from:
          description: '`upgrade` or `downgrade` when this plan replaced another.'
          type:
            - string
            - 'null'
          example: null
        upgrade:
          oneOf:
            - $ref: '#/components/schemas/RecurringPlanUpgradeBlock'
            - type: 'null'
      type: object
    RecurringPlanScheduleBlock:
      description: Billing schedule details returned with a plan.
      properties:
        interval:
          description: Number of `interval_unit`s between charges.
          type: integer
          example: 1
        interval_unit:
          description: Unit of the billing interval.
          type: string
          enum:
            - day
            - week
            - month
          example: month
        current_interval:
          description: Current cycle number (0 before the first cycle).
          type: integer
          example: 1
        total_interval:
          description: Maximum number of billing cycles, or null for open-ended.
          type:
            - integer
            - 'null'
          example: 12
        start_time:
          description: First charge window.
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-07-01T00:00:00+07:00'
        previous_payment_at:
          description: Timestamp of the last paid bill.
          type:
            - string
            - 'null'
          format: date-time
          example: null
        next_payment_at:
          description: Timestamp of the next scheduled charge.
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-07-01T00:00:00+07:00'
      type: object
    RecurringPlanRetryPolicyBlock:
      description: Failed-payment retry policy returned with a plan.
      properties:
        max_attempts:
          description: Automatic retry attempts after a failed charge.
          type: integer
          example: 3
        interval_days:
          description: Days between consecutive retries.
          type: integer
          example: 1
        failed_payment_action:
          description: Action taken once retries are exhausted.
          type:
            - string
            - 'null'
          example: continue_plan
      type: object
    RecurringPlanUpgradeBlock:
      description: >-
        Present on successful **upgrade** PATCH responses only
        (`SubscriptionPlanApiController::update` when `isUpgrade()`).
      properties:
        previous_plan_id:
          description: UUID of the superseded plan.
          type:
            - string
            - 'null'
          format: uuid
          example: 7c1a9d4e-2b3c-4d5e-9f60-1a2b3c4d5e6f
        direction:
          description: Whether the new plan is more or less expensive than the old one.
          type: string
          enum:
            - upgrade
            - downgrade
          example: upgrade
        difference:
          description: Amount delta between the old and new plan.
          properties:
            old_amount:
              description: Previous per-cycle amount in IDR.
              type: number
              example: 150000
            new_amount:
              description: New per-cycle amount in IDR.
              type: number
              example: 200000
            difference:
              description: Absolute difference in IDR.
              type: number
              example: 50000
            type:
              description: Direction of the amount change.
              type: string
              enum:
                - increase
                - decrease
                - no_change
              example: increase
          type: object
        prorated_charge:
          description: Proration bill raised for the upgrade, when applicable.
          properties:
            bill_id:
              description: Internal proration bill id.
              type: integer
              example: 9012
            amount:
              description: Proration amount in IDR.
              type: number
              example: 50000
            status:
              description: Proration bill status.
              type:
                - string
                - 'null'
              example: pending
          type:
            - object
            - 'null'
        payment_link_url:
          description: Payment link for the prorated charge, when one was issued.
          type:
            - string
            - 'null'
          format: uri
          example: https://pay.singapay.id/bill/9012
      type: object
  securitySchemes:
    BearerAuth:
      type: http
      description: >-
        JWT issued by `POST /api/v1.1/access-token/b2b`. Send `Authorization:
        Bearer <token>`.
      bearerFormat: JWT
      scheme: bearer
    PartnerId:
      type: apiKey
      description: Merchant API key (`Credential.api_key`). Required on every request.
      name: X-PARTNER-ID
      in: header

````