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

# Create a plan

> Create a new plan with its tiers and items. The plan starts in draft status. Activate it before you sell or switch into it.



## OpenAPI

````yaml post /api/plans
openapi: 3.1.0
info:
  title: Fynn V2 Plans API
  version: 2026-06
  description: >-
    REST API for the Fynn V2 Plans. Use it to define plans with upgrade and
    downgrade tiers, sell them through checkout links, and let customers switch
    tiers from your customer portal. All amounts use the minor or decimal
    representation documented per field. Authenticate setup endpoints with an
    API token and customer endpoints with a customer token.
servers:
  - url: https://coreapi.io
    description: Production
  - url: https://preview.coreapi.io
    description: Sandbox
security:
  - ApiToken: []
tags:
  - name: Plans
    description: Create and manage plans, tiers and items.
  - name: Checkout Links
    description: Sell a plan tier through a hosted checkout link.
  - name: Plan switching (customer)
    description: Customer facing upgrade and downgrade between tiers.
paths:
  /api/plans:
    post:
      tags:
        - Plans
      summary: Create a plan
      description: >-
        Create a new plan with its tiers and items. The plan starts in draft
        status. Activate it before you sell or switch into it.
      operationId: createPlan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanInput'
            example:
              code: pro-plan
              name: Pro Plan
              tiers:
                - position: 0
                  name: Starter
                  upgradeable: true
                  downgradeable: false
                  changeTiming: immediately
                  creditType: pro_rata
                  items:
                    - productId: 1280792f-4e23-793d-8e9c-3a1bf36630bd
                      pricePlanId: e0c546c5-eee0-7219-8847-c0ed16ae4a88
                      defaultQuantity: 1
                - position: 1
                  name: Pro
                  upgradeable: true
                  downgradeable: true
                  changeTiming: immediately
                  creditType: pro_rata
                  items:
                    - productId: f5ece259-08ea-77a4-8bd9-9f3a9ed9e246
                      pricePlanId: 295ad101-5d9f-7f1f-80f5-a9420c21eb1e
                      defaultQuantity: 1
                    - productId: 27e3a683-8829-73a8-8acb-5d9fbffb30b6
                      pricePlanId: a1384760-a089-76a8-82e0-abd2993a39ac
                      defaultQuantity: 1
                      minQuantity: 0
                      maxQuantity: 50
                - position: 2
                  name: Business
                  upgradeable: false
                  downgradeable: true
                  changeTiming: end_of_period
                  creditType: none
                  items:
                    - productId: 645077e0-f408-7f73-8f6e-585435440667
                      pricePlanId: 321d6d27-a4aa-7dd7-8395-a57ead3b5ffb
                      defaultQuantity: 1
      responses:
        '201':
          description: The created plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
        '401':
          description: Authentication is missing or the token is invalid.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemError'
        '403':
          description: The token is valid but lacks the required permission.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemError'
        '409':
          description: >-
            The request conflicts with the current state, for example a
            duplicate plan code or checkout link slug.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemError'
        '422':
          description: The request failed validation or a business rule was violated.
          content:
            application/problem+json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ValidationError'
                  - $ref: '#/components/schemas/ProblemError'
components:
  schemas:
    PlanInput:
      type: object
      required:
        - code
        - name
        - tiers
      properties:
        code:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
          description: >-
            Stable, Organisation unique identifier in lower kebab case, for
            example pro-plan.
          example: pro-plan
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Display name of the plan.
          example: Pro Plan
        tiers:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/PlanTierInput'
          description: >-
            The tiers of the plan, ordered by position. At least one tier is
            required.
      description: >-
        Full representation of a plan. PUT replaces the plan as a whole: send
        every tier and item you want to keep, provide ids to keep existing rows
        stable, and omit rows you want removed.
    Plan:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 1280792f-4e23-793d-8e9c-3a1bf36630bd
        code:
          type: string
          example: pro-plan
        name:
          type: string
          example: Pro Plan
        status:
          $ref: '#/components/schemas/PlanStatus'
        currency:
          type: string
          nullable: true
          example: EUR
          description: >-
            Currency derived from the price plans, null when the plan has no
            items yet.
        tiers:
          type: array
          items:
            $ref: '#/components/schemas/PlanTier'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ProblemError:
      type: object
      description: >-
        Problem detail error body (application/problem+json). Additional members
        may be present depending on the error.
      properties:
        status:
          type: integer
          example: 404
        type:
          type: string
          description: Stable machine readable error key.
          example: PLAN__NOT_FOUND
        title:
          type: string
          example: An error occurred
        detail:
          type: string
          example: Plan not found.
      required:
        - status
        - type
        - title
        - detail
    ValidationError:
      type: object
      description: >-
        Validation error body (application/problem+json) returned when input
        fails field level validation.
      properties:
        status:
          type: integer
          example: 422
        type:
          type: string
          example: https://tools.ietf.org/html/rfc2616#section-10
        title:
          type: string
          example: Validation Failed
        violations:
          type: array
          items:
            type: object
            properties:
              propertyPath:
                type: string
                example: tiers[0].name
              message:
                type: string
                example: This value should not be blank.
              code:
                type: string
                nullable: true
      required:
        - status
        - type
        - title
        - violations
    PlanTierInput:
      type: object
      required:
        - position
        - name
        - items
      properties:
        id:
          type: string
          nullable: true
          description: >-
            Existing tier UUID. Provide it on update to keep the tier stable,
            omit it to create a new tier.
        position:
          type: integer
          minimum: 0
          description: >-
            Order on the ladder. A lower position is a lower tier. Positions
            must be unique within the plan.
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Display name of the tier, for example Starter, Pro or Business.
        upgradeable:
          type: boolean
          default: true
          description: Whether a customer may leave this tier in the upward direction.
        downgradeable:
          type: boolean
          default: false
          description: Whether a customer may leave this tier in the downward direction.
        changeTiming:
          allOf:
            - $ref: '#/components/schemas/ChangeTiming'
          default: immediately
        creditType:
          allOf:
            - $ref: '#/components/schemas/CreditType'
          default: pro_rata
        commitmentPeriod:
          type: string
          nullable: true
          description: >-
            Minimum term as an ISO 8601 duration, for example P1Y. null means no
            minimum term.
          example: P1Y
        items:
          type: array
          items:
            $ref: '#/components/schemas/PlanTierItemInput'
          description: >-
            Products contained in this tier. A tier needs at least one item
            before the plan can be activated.
      description: >-
        A tier on the upgrade and downgrade ladder. changeTiming and creditType
        together define the proration behaviour: end_of_period forces creditType
        none, immediately with pro_rata issues a credit for unused time.
    PlanStatus:
      type: string
      enum:
        - draft
        - active
        - archived
      description: >-
        Lifecycle status. A plan is created as draft, becomes active once you
        activate it, and can later be archived. Only active plans can be sold or
        switched into.
    PlanTier:
      type: object
      properties:
        id:
          type: string
        position:
          type: integer
          example: 1
        name:
          type: string
          example: Pro
        upgradeable:
          type: boolean
        downgradeable:
          type: boolean
        changeTiming:
          $ref: '#/components/schemas/ChangeTiming'
        creditType:
          $ref: '#/components/schemas/CreditType'
        commitmentPeriod:
          type: string
          nullable: true
          example: P1Y
        items:
          type: array
          items:
            $ref: '#/components/schemas/PlanTierItem'
    ChangeTiming:
      type: string
      enum:
        - immediately
        - end_of_period
      description: >-
        When a switch into this tier takes effect. immediately applies the
        change at once, end_of_period waits until the current billing period
        ends.
    CreditType:
      type: string
      enum:
        - pro_rata
        - none
      description: >-
        How unused time is credited when switching into this tier with
        immediately timing. pro_rata credits the remaining part of the current
        period, none gives no credit. The values full and last_invoiced are not
        available for plans.
    PlanTierItemInput:
      type: object
      required:
        - productId
        - pricePlanId
        - defaultQuantity
      properties:
        id:
          type: string
          nullable: true
          description: >-
            Existing item UUID. Provide it on update to keep the item stable,
            omit it to create a new item.
        productId:
          type: string
          description: UUID of the product to include.
        pricePlanId:
          type: string
          description: >-
            UUID of a price plan that belongs to the product. All price plans
            across a plan must share one currency.
        defaultQuantity:
          type: number
          minimum: 0
          description: >-
            Quantity used at checkout and on a switch when no override is sent.
            For usage based products this is ignored.
        minQuantity:
          type: number
          minimum: 0
          nullable: true
          description: >-
            Lower bound for customer quantity overrides. null means no lower
            bound.
        maxQuantity:
          type: number
          minimum: 0
          nullable: true
          description: >-
            Upper bound for customer quantity overrides. null means no upper
            bound.
    PlanTierItem:
      type: object
      properties:
        id:
          type: string
        productId:
          type: string
        productName:
          type: string
          example: Add-On
        pricePlanId:
          type: string
        pricePlanName:
          type: string
          example: Add-On Monthly
        currency:
          type: string
          example: EUR
        defaultQuantity:
          type: number
          example: 1
        minQuantity:
          type: number
          nullable: true
        maxQuantity:
          type: number
          nullable: true
  securitySchemes:
    ApiToken:
      type: http
      scheme: bearer
      description: >-
        Organisation API token. Send it as Authorization: Bearer api_... The
        token is scoped to a single Organisation.

````