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

# Create a plan

> Create one plan without replacing other catalog items. Paid plans sync to the selected environment's Stripe account.

Create one plan without replacing other catalog items. A plan grants a monthly credit allowance to each customer who buys it. The request uses integer cents for the monthly price and an integer credit count for the allowance.

## Choose a free or paid plan

* Set `monthlyPriceCents` to `0` for a free plan. Attach it with `POST /v1/billing/attach`; Ledger activates it and grants its first monthly allowance immediately, without Stripe or a Checkout URL.
* Set `monthlyPriceCents` above `0` for a recurring plan. Ledger syncs a monthly Stripe Price to the Stripe account connected in the key's environment. For example, `1900` means \$19.00 per month in a USD catalog.
* `monthlyCredits` must be positive. Paid plans grant the allowance after Ledger receives Stripe's paid invoice event. Free plans grant it on assignment; each new monthly period creates an allowance when the customer's balance is next read or usage is recorded.

The plan ID is your stable API identifier. Use letters, numbers, underscores, or hyphens (up to 100 characters). The display name can be changed later; the ID cannot. A second active plan with the same ID returns `409 already_exists`.

## After creating a paid plan

The response contains the created `item`, `changed: true`, and a `stripeSync` state. `awaiting_connection` means Stripe has not been connected yet; connect it under **Developer → Stripe** in the dashboard, then confirm the plan reports `synced`. If the state is `pending` or `failed`, check the sync result before sending customers to Checkout. Ledger can retry synchronization when an attach request arrives. The dashboard shows the state in **Catalog → Plans**.

Create a customer with [Create or get a customer](https://docs.integrateledger.com/api-reference/customers/create.md), then use [Attach a plan or add-on](https://docs.integrateledger.com/api-reference/billing/attach.md) to get a hosted Checkout URL. A successful browser redirect is not proof of payment. Ledger activates the subscription and grants credits after the signed Stripe invoice webhook arrives.

For a free plan, the attach response contains `subscriptionId` and `status: "active"` rather than `checkoutUrl`. No Stripe connection or payment redirect is needed.

## Changing a plan later

[Update a plan](https://docs.integrateledger.com/api-reference/plans/update.md) requires its full editable body. A changed plan creates a new version for future Checkouts; existing subscriptions retain their referenced version. [Archive a plan](https://docs.integrateledger.com/api-reference/plans/archive.md) removes it from active lists and new purchases while retaining historical subscriptions. See [Catalog](https://docs.integrateledger.com/catalog.md) for the full lifecycle.


## OpenAPI

````yaml openapi.json POST /v1/plans
openapi: 3.1.0
info:
  title: Ledger API
  version: 0.1.0
servers:
  - url: https://api.integrateledger.com
    description: Hosted Ledger API
security: []
paths:
  /v1/plans:
    post:
      tags:
        - Plans
      summary: Create a plan
      operationId: createPlan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePlanInput'
      responses:
        '201':
          description: Plan created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanMutation'
              example:
                item:
                  id: starter
                  name: Starter
                  monthlyCredits: 100
                  monthlyPriceCents: 1900
                  stripeSyncStatus: synced
                changed: true
                stripeSync: synced
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
        '401':
          description: Invalid Ledger API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
        '409':
          description: Item ID already exists or add-on Checkout is pending
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
      security:
        - ApiKey: []
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --request POST 'https://api.integrateledger.com/v1/plans' \
              -H "Authorization: Bearer $LEDGER_API_KEY" \
              -H 'Content-Type: application/json' \
              --data '{"id":"starter","name":"Starter","monthlyCredits":100,"monthlyPriceCents":1900}'
        - lang: python
          label: Python SDK (source only)
          source: >-
            import os

            from ledger_sdk import Ledger


            ledger = Ledger(api_key=os.environ["LEDGER_API_KEY"])

            ledger.create_plan(id="starter", name="Starter",
            monthly_credits=100, monthly_price_cents=1900)
        - lang: typescript
          label: TypeScript SDK (source only)
          source: >-
            import { Ledger } from "@ledger/sdk";


            const ledger = new Ledger({ apiKey: process.env.LEDGER_API_KEY ?? ""
            });

            await ledger.createPlan({ id: "starter", name: "Starter",
            monthlyCredits: 100, monthlyPriceCents: 1900 });
components:
  schemas:
    CreatePlanInput:
      type: object
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 100
          pattern: ^[a-zA-Z0-9_-]+$
        name:
          type: string
          minLength: 1
          maxLength: 120
        monthlyCredits:
          type: integer
          exclusiveMinimum: 0
          maximum: 2147483647
        monthlyPriceCents:
          type: integer
          minimum: 0
          maximum: 2147483647
      required:
        - id
        - name
        - monthlyCredits
        - monthlyPriceCents
      additionalProperties: false
    PlanMutation:
      type: object
      properties:
        item:
          $ref: '#/components/schemas/PlanItem'
        changed:
          type: boolean
        stripeSync:
          type: string
          enum:
            - synced
            - pending
            - failed
            - awaiting_connection
      required:
        - item
        - changed
    PlanItem:
      type: object
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 100
          pattern: ^[a-zA-Z0-9_-]+$
        name:
          type: string
          minLength: 1
          maxLength: 120
        monthlyCredits:
          type: integer
          exclusiveMinimum: 0
          maximum: 2147483647
        monthlyPriceCents:
          type: integer
          minimum: 0
          maximum: 2147483647
        stripeSyncStatus:
          type: string
          nullable: true
          enum:
            - pending
            - synced
            - failed
            - null
      required:
        - id
        - name
        - monthlyCredits
        - monthlyPriceCents
      additionalProperties: false
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.