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

# Billing and subscriptions

> List plans and create checkout or direct subscriptions from Python.

## List plans

`plans()` accepts either `pk_` or `sk_` credentials.

```python theme={null}
plans = nozle.plans()

for plan in plans:
    print(
        plan["code"],
        plan["amount_cents"],
        plan["amount_currency"],
        plan["interval"],
    )
```

Each `Plan` is a typed dictionary containing `code`, `name`, `amount_cents`, `amount_currency`, and `interval`.

## Create checkout

```python theme={null}
result = nozle.checkout(
    "workspace_123",
    "pro_monthly",
    return_url="https://app.example.com/settings/billing",
)
```

`checkout()` requires `sk_` and returns one of three typed result variants:

* `StripeCheckoutResult` with a hosted `url` or embedded `client_secret`;
* `CompletedCheckoutResult`; or
* `ScheduledCheckoutResult`.

```python theme={null}
if result["type"] == "stripe":
    if "url" in result:
        redirect_to = result["url"]
    else:
        client_secret = result.get("client_secret") or result.get("clientSecret")
elif result["type"] == "completed":
    refresh_billing_state()
else:
    show_scheduled_change(result["status"])
```

Validate the exact HTTPS return origin before passing a browser-provided URL to this method.

### Deprecated `success_url`

`success_url=` remains a deprecated alias for `return_url=`. The SDK emits `DeprecationWarning`, sends only `return_url`, and rejects conflicting values with `NozleValidationError`.

<Warning>
  Stripe webhooks remain authoritative. Checkout return values and browser redirects are interface signals, not proof that a paid subscription is active.
</Warning>

## Direct subscription

```python theme={null}
subscription = nozle.subscribe("workspace_123", "free")

print(subscription["subscription_id"])
print(subscription["status"])
```

Use checkout for payment-gated changes. Use direct subscription only when the configured flow does not require interactive payment.

## Cancellation

```python theme={null}
result = nozle.cancel_subscription(
    "workspace_123",
    "subscription_123",
    policy="end_of_period",
)
```

The policy defaults to `end_of_period` and also accepts `immediate`.

## Entity subscriptions

```python theme={null}
nozle.entity_subscriptions.ensure("workspace_123", "user_42")

checkout = nozle.entity_subscriptions.checkout(
    "workspace_123",
    "user_42",
    plan_code="pro_monthly",
    billing_time="anniversary",
    return_url="https://app.example.com/settings/billing",
    idempotency_key="checkout-user-42-pro-v1",
)
```

Change or cancel only that Entity:

```python theme={null}
nozle.entity_subscriptions.change_plan(
    "workspace_123",
    "user_42",
    plan_code="max_annual",
    return_url="https://app.example.com/settings/billing",
    idempotency_key="change-user-42-max-v1",
)

nozle.entity_subscriptions.cancel(
    "workspace_123",
    "user_42",
    timing="end_of_period",
    idempotency_key="cancel-user-42-v1",
)
```

Every method requires an `sk_` key. See [Entity Subscriptions](/api/entity-subscriptions) for lifecycle and payment behavior.

The Python SDK currently exposes single-Entity checkout and plan changes. For a mixed seat-pool purchase, call the bulk Entity checkout endpoint from your trusted backend. Do not loop over `entity_subscriptions.checkout()`, because each call creates an independent checkout instead of one combined invoice.

## Settlement transitions

```python theme={null}
params = {
    "customer_id": "workspace_123",
    "subscription_id": "subscription_123",
    "operation": "downgrade",
    "timing": "end_of_period",
    "target_plan_code": "growth_monthly",
    "credit_action": "none",
}

preview = nozle.preview_subscription_transition(params)

transition = nozle.apply_subscription_transition(
    params,
    idempotency_key="transition-subscription-123-growth-v1",
)
```

Apply requires an idempotency key up to 255 UTF-8 bytes. See [Subscriptions](/guides/billing/subscriptions) for settlement options and validation rules.

## Upsert the customer first

```python theme={null}
customer = nozle.customers.upsert(
    "workspace_123",
    name="Acme Workspace",
    email="billing@example.com",
)
```

See [Customers and Entities](/sdks/python/customers-entities) for lifecycle details.
