# Subscriptions

Recurring billing on your Southbill account

# Subscriptions

Create recurring subscriptions for customers using a **recurring price** from your catalog. Subscriptions run on your connected Southbill account; Southbill deducts the platform fee automatically from each renewal invoice.

Base URL: `https://api.southbill.com/v1`
Auth: `Authorization: Bearer sk_live_…` (or `sk_test_…`)

## Prerequisites

1. Create a **product** and a **recurring price** (see [Products](/docs/api/products) and [Prices](/docs/api/prices)).
2. The price must have `recurring.interval` set (`day`, `week`, `month`, `year`).

## Create a subscription

`POST /v1/subscriptions`

```bash
curl -X POST https://api.southbill.com/v1/subscriptions \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub_20260718_001" \
  -d '{
    "price_id": "price_1Tu…",
    "customer_name":  "Jane Doe",
    "customer_email": "jane@example.com",
    "quantity": 1,
    "trial_days": 14,
    "metadata": { "plan": "pro" }
  }'
```

### Body

| Field | Type | Required | Notes |
|---|---|---|---|
| `price_id` | string | ✅ | Recurring price from your catalog. |
| `customer_name` | string | ✅ | 2–120 chars. |
| `customer_email` | string | ✅ | Valid email. Receipts and dunning are sent here. |
| `quantity` | integer | ➖ | Defaults to `1`. |
| `trial_days` | integer | ➖ | Free trial before first charge. |
| `metadata` | object | ➖ | Free-form key/value returned in webhooks. |

### Response

```json
{
  "id": "sub_1Tu…",
  "object": "subscription",
  "status": "incomplete",
  "customer": "cus_1Tu…",
  "price_id": "price_1Tu…",
  "quantity": 1,
  "currency": "EUR",
  "amount": 1990,
  "current_period_start": 1735689600,
  "current_period_end":   1738368000,
  "latest_invoice": "in_1Tu…",
  "client_secret": "pi_1Tu…_secret_…",
  "trial_end": null,
  "created": 1735689600
}
```

The subscription is created in `incomplete` state. Use the returned `client_secret` on the customer's browser to confirm the first payment. Once confirmed the status transitions to `active` (or `trialing` if `trial_days` was set) and a `customer.subscription.updated` webhook is fired.

## Retrieve

```
GET /v1/subscriptions/{id}
```

## List

```
GET /v1/subscriptions?limit=20
```

Returns up to 100 subscriptions ordered by creation date (newest first).

## Cancel

`POST /v1/subscriptions/{id}/cancel`

```bash
curl -X POST https://api.southbill.com/v1/subscriptions/sub_1Tu…/cancel \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cancel_sub_1Tu_20260718" \
  -d '{ "at_period_end": true }'
```

| Field | Default | Meaning |
|---|---|---|
| `at_period_end` | `true` | Keep active until the current period ends, then cancel. |
| `at_period_end: false` | — | Cancel immediately. No further invoices. |

## Status model

| Status | Meaning |
|---|---|
| `incomplete` | Waiting for the first payment confirmation. |
| `incomplete_expired` | First payment was not confirmed within 23 h. |
| `trialing` | Free trial in progress. |
| `active` | Paid and current. |
| `past_due` | Renewal failed. Dunning in progress. |
| `unpaid` | All retries exhausted, subscription frozen. |
| `canceled` | Terminated. |

## Fees

Recurring invoices carry a platform fee computed from your plan and payment method. Fees are settled in **EUR** and deducted per invoice — same rules as one-off Checkout Sessions. See [Fees, currency & minimum amounts](/docs/getting-started/fees-and-currency).

## Idempotency

Send `Idempotency-Key: <your-key>` on `POST /v1/subscriptions` and `POST /v1/subscriptions/{id}/cancel`. Keys are scoped to `(merchant_id, method, path)` and expire after 24 hours.

- **Same key + same body** → original response replayed verbatim.
- **Same key + different body** → **`409 Conflict`** with `type: "idempotency_error"`, `code: "idempotency_key_reused"`. See [Error reference](/docs/api/errors).

## Errors

| HTTP | `error.type` | `error.code` | Meaning |
|---|---|---|---|
| 400 | `invalid_request` | field-specific | Missing / malformed field, or price is not recurring. |
| 401 | `authentication_error` | `invalid_api_key` | Bad, revoked or wrong-mode key. |
| 404 | `not_found` | `price_not_found` / `subscription_not_found` | Not owned by your account. |
| 409 | `idempotency_error` | `idempotency_key_reused` | Same key with different body. |
| 409 | `resource_conflict` | `merchant_not_ready` / `subscription_already_canceled` | See message. |
| 429 | `rate_limit_error` | `rate_limited` | Respect `Retry-After`. |

## Webhooks

Listen for these events (see [Event reference](/docs/webhooks/events) for payloads):

- `customer.subscription.created` / `updated` / `deleted`
- `customer.subscription.trial_will_end` — fires 3 days before the trial ends
- `invoice.payment_succeeded` — renewal charged
- `invoice.payment_failed` — dunning stage advanced (notice → warning → final)

