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

# App Subscriptions

> Create, list, and cancel per-app billing subscriptions for an installed app.

App subscriptions are the recurring (or one-time) charges a merchant pays
**for an installed app**. They are distinct from the store-level platform
subscription at `/api/v1/billing/*` — each one is scoped to an
installation that links a single app to a single merchant.

A subscription is always created against an existing installation. If the
app is not installed yet, the API returns `404 — App is not installed`.

**Auth:** merchant JWT (`Authorization: Bearer <merchant-jwt>`). All endpoints
below require a merchant session — they cannot be called with an OAuth
app token.

## Charge approval flow

1. App developer publishes pricing for their app: `POST /apps/billing/pricing/:appId`
   with `{ monthly, yearly, currency }`. This provisions the Stripe Product +
   Price objects.
2. Merchant installs the app via the marketplace.
3. App (or merchant UI) calls `POST /apps/billing/checkout-session` to obtain
   a Stripe Checkout URL. The merchant is redirected, completes payment, and
   is returned to `successUrl`.
4. Stripe fires `checkout.session.completed` → `POST /apps/billing/webhook`
   verifies the signature and marks the installation as `subscribed` plus
   records a billing transaction.
5. Subsequent renewals fire `invoice.paid` webhooks; one transaction row is
   created per cycle.

For usage-based pricing (per-API-call, per-email-sent, etc.) the developer
also records usage events via `POST /apps/billing/usage` — see
[Usage Records](/api-reference/app-billing/usage-records).

***

## Setup pricing

<ParamField path="appId" type="string" required>
  Application UUID. The app must be owned by the calling developer or admin.
</ParamField>

<ParamField body="monthly" type="number" required>
  Monthly recurring price in major currency units (e.g. `9.99` for \$9.99).
</ParamField>

<ParamField body="yearly" type="number" required>
  Yearly recurring price in major currency units.
</ParamField>

<ParamField body="currency" type="string" required>
  ISO-4217 currency code (e.g. `USD`, `EUR`, `INR`).
</ParamField>

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/billing/pricing/<APP_ID>" \
  -H "Authorization: Bearer <MERCHANT_JWT>" \
  -H "Content-Type: application/json" \
  -d '{ "monthly": 9.99, "yearly": 99.00, "currency": "USD" }'
```

Returns `{ status, type, message, data: { stripeProductId, stripePriceMonthly, stripePriceYearly } }`.

***

## Subscribe an installation

`POST /apps/billing/subscribe` creates a Stripe Subscription wired to the
merchant's stored payment method. Use this when the merchant has already
saved a card on the platform; for first-time card collection, use
`/checkout-session` (below).

<ParamField body="appId" type="string" required>
  Application UUID. Must correspond to an existing installation for
  the calling store; otherwise `404`.
</ParamField>

<ParamField body="planInterval" type="string" required>
  Either `monthly` or `yearly`. The Stripe Price for that interval must have
  been provisioned via `POST /pricing/:appId`.
</ParamField>

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/billing/subscribe" \
  -H "Authorization: Bearer <MERCHANT_JWT>" \
  -H "Content-Type: application/json" \
  -d '{ "appId": "fb7c…", "planInterval": "monthly" }'
```

<ResponseField name="data.subscriptionId" type="string">Stripe Subscription id.</ResponseField>
<ResponseField name="data.installationId" type="string">UUID of the installation now linked to this subscription.</ResponseField>
<ResponseField name="data.status" type="string">Stripe status: `active`, `incomplete`, `incomplete_expired`, `past_due`, `canceled`, `trialing`, `unpaid`.</ResponseField>
<ResponseField name="data.currentPeriodEnd" type="string">ISO timestamp of next renewal.</ResponseField>

***

## Hosted checkout session (charge approval URL)

`POST /apps/billing/checkout-session` returns a Stripe Checkout URL. This
is the canonical "charge approval URL" — redirect the merchant to it, they
pay, Stripe redirects them back to `successUrl`.

<ParamField body="appId" type="string" required>UUID of the installed app.</ParamField>
<ParamField body="planInterval" type="string" required>One of `monthly`, `yearly`, `one_time`. `one_time` is used for non-recurring app charges.</ParamField>
<ParamField body="successUrl" type="string">URL Stripe redirects to on payment success. Defaults to the app's configured success URL.</ParamField>
<ParamField body="cancelUrl" type="string">URL Stripe redirects to if the merchant abandons checkout. Defaults to the app's configured cancel URL.</ParamField>

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/billing/checkout-session" \
  -H "Authorization: Bearer <MERCHANT_JWT>" \
  -H "Content-Type: application/json" \
  -d '{
    "appId": "fb7c…",
    "planInterval": "monthly",
    "successUrl": "https://app.example.com/billing/return?success=1",
    "cancelUrl":  "https://app.example.com/billing/return?cancel=1"
  }'
```

<ResponseField name="data.url" type="string">Stripe-hosted checkout URL. Open in a new tab or redirect.</ResponseField>
<ResponseField name="data.sessionId" type="string">`cs_…` Stripe Checkout Session id.</ResponseField>
<ResponseField name="data.installationId" type="string">UUID of the linked installation.</ResponseField>

***

## Cancel a subscription

`POST /apps/billing/cancel` cancels the Stripe Subscription tied to an
installation. The installation row remains; only the recurring charge is
stopped. Cancellation takes effect at the end of the current billing
period — the merchant retains access until then.

<ParamField body="installationId" type="string" required>
  UUID of the installation. Get it from `GET /apps/installations` or
  from the `subscribe` / `checkout-session` response.
</ParamField>

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/billing/cancel" \
  -H "Authorization: Bearer <MERCHANT_JWT>" \
  -H "Content-Type: application/json" \
  -d '{ "installationId": "f73049dc-…" }'
```

<ResponseField name="data.status" type="string">Stripe subscription status after cancellation — typically `canceled` (if immediate) or `active` with `cancelAtPeriodEnd: true`.</ResponseField>
<ResponseField name="data.cancelAtPeriodEnd" type="boolean">`true` when the merchant keeps access until `currentPeriodEnd`.</ResponseField>
<ResponseField name="data.currentPeriodEnd" type="string">ISO end date.</ResponseField>

***

## Active subscriptions for a merchant

There is currently no dedicated "list app subscriptions for this merchant"
endpoint. Use one of:

* `GET /apps/installations` (merchant) → each row carries
  `billing: { subscriptionId, status, currentPeriodEnd, planInterval }`.
* `GET /apps/billing/transactions` (merchant) → groups paid invoices per
  installation. See [Transactions](/api-reference/app-billing/transactions).

<Note>
  A dedicated `GET /apps/billing/subscriptions` endpoint that returns just
  the active subscription rows for the merchant is planned but not yet
  exposed publicly.
</Note>

***

## Webhook

`POST /apps/billing/webhook` (public, no auth) is the Stripe webhook
endpoint. The handler verifies the `Stripe-Signature` header against
`STRIPE_APP_BILLING_WEBHOOK_SECRET` and processes:

| Event                           | Effect                                                                                                                              |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `checkout.session.completed`    | Activates the installation, records a `paid` billing transaction, fires the `app/subscription/created` outbound webhook to the app. |
| `invoice.paid`                  | Inserts a `paid` transaction row for the renewal cycle.                                                                             |
| `invoice.payment_failed`        | Marks the installation as `past_due`; fires `app/subscription/past_due`.                                                            |
| `customer.subscription.deleted` | Marks the installation as `cancelled`; fires `app/subscription/cancelled`.                                                          |

Apps subscribe to these outbound webhook topics in their manifest — they do
NOT receive the raw Stripe webhook.

## Error codes

| Code  | Description                                                                 |
| ----- | --------------------------------------------------------------------------- |
| `400` | Missing `Stripe-Signature` header or unverifiable signature (webhook only). |
| `400` | `planInterval` not one of `monthly` / `yearly` / `one_time`.                |
| `401` | Missing or invalid merchant JWT.                                            |
| `404` | App is not installed — call `POST /apps/install/:appId` first.              |
| `500` | Stripe API error (logged with the underlying Stripe error code).            |
