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

# Create a discount code

> Mint a promo code. The code is redeemed at checkout · passed on the session
as `discountCode`, applied on its own when `preApply` is set, or typed by
the payer on the hosted page · and is re-validated and re-priced
server-side there. A payer can never set an amount.




## OpenAPI

````yaml /v3-beta/api-reference/openapi.yaml post /v3/discount-codes
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/discount-codes:
    post:
      tags:
        - Discount Codes
      summary: Create a discount code
      description: >
        Mint a promo code. The code is redeemed at checkout · passed on the
        session

        as `discountCode`, applied on its own when `preApply` is set, or typed
        by

        the payer on the hosted page · and is re-validated and re-priced

        server-side there. A payer can never set an amount.
      operationId: createDiscountCode
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDiscountCodeBody'
      responses:
        '201':
          description: Code created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - properties:
                      data:
                        $ref: '#/components/schemas/DiscountCode'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: >-
            One of `productIds` is not yours
            (`DISCOUNT_CODE_PRODUCT_NOT_FOUND`).
        '409':
          description: >-
            The name is already taken in this environment
            (`DISCOUNT_CODE_NAME_TAKEN`).
        '422':
          description: >
            Invalid body, or a `FIXED` code scoped to a product priced in
            another

            currency (`DISCOUNT_CODE_PRODUCT_CURRENCY_MISMATCH`). A fixed amount
            is

            never converted, so it is refused here rather than at the checkout.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
components:
  parameters:
    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:
    CreateDiscountCodeBody:
      type: object
      required:
        - name
      description: >-
        Only the field matching `discountType` is stored; the other is cleared,
        so a code can never carry two contradictory discounts.
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 40
          pattern: ^[A-Z0-9][A-Z0-9_-]*$
          example: NOEL2026
          description: The code you hand out. Unique per account and environment.
        discountType:
          type: string
          enum:
            - PERCENTAGE
            - FIXED
          default: PERCENTAGE
        discountPercent:
          type: integer
          minimum: 1
          maximum: 99
          description: Required when `discountType=PERCENTAGE`.
        discountAmountCents:
          type: string
          pattern: ^[1-9]\\d*$
          example: '500'
          description: >-
            Required when `discountType=FIXED`. Minor units as a string of
            digits.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: Required when `discountType=FIXED`.
        productIds:
          type: array
          default: []
          items:
            type: string
            example: pro_abc123
          description: Eligible products. **Leave empty to apply to every product.**
        maxUses:
          type: integer
          minimum: 1
          nullable: true
          description: >-
            Global redemption cap. Null = unlimited. Counts settled redemptions
            plus checkouts still in flight, so the last use cannot be handed to
            two buyers at once; an abandoned attempt releases its slot on its
            own expiry.
        expiresAt:
          type: string
          format: date-time
          nullable: true
        preApply:
          type: boolean
          default: false
          description: >-
            Apply this code on the hosted checkout with no payer input, on every
            order it covers. A code carried by the session (`discountCode` on
            `POST /v3/checkout/sessions`) wins over it. The payer sees the
            discount already deducted and can still remove it. A pre-applied
            code that no longer redeems is silently not applied · the page shows
            the full price rather than an error the payer did not cause.
    SuccessEnvelope:
      type: object
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
        data:
          description: Endpoint-specific payload.
    DiscountCode:
      type: object
      properties:
        id:
          type: string
          example: disc_abc123
        name:
          type: string
          example: NOEL2026
        discountType:
          type: string
          enum:
            - PERCENTAGE
            - FIXED
        discountPercent:
          type: integer
          nullable: true
          minimum: 1
          maximum: 99
        discountAmountCents:
          type: string
          nullable: true
          example: '500'
        currency:
          type: string
          nullable: true
        productIds:
          type: array
          items:
            type: string
            example: pro_abc123
          description: Eligible products. **Empty means the whole catalogue.**
        maxUses:
          type: integer
          nullable: true
        usedCount:
          type: integer
          description: >-
            Redemptions counted against `maxUses` · payments that reached an
            authorized or settled state. Attempts that never settled are not
            counted, so an abandoned checkout leaves this untouched.
        expiresAt:
          type: string
          format: date-time
          nullable: true
        isActive:
          type: boolean
        preApply:
          type: boolean
          description: >-
            The hosted checkout applies this code on its own, with nothing for
            the payer to type.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    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
    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.
  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).

````