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

# Stripe integration

> Connect a site to Stripe, drive the import of its billing history, and read back the connection status.

Connect a site to Stripe, drive the import of its billing history, and read back the connection status. These endpoints power the same connect → align currency → select products → import flow the web Revenue page runs, so both surfaces stay in lock-step.

A site is addressed by `{siteKey}` (UUID or domain). All endpoints require `Authorization: Bearer {token}` — see [Conventions](/api/conventions).

## Security

* **The status endpoint NEVER returns the API key or webhook secret.** Both are stored encrypted and hidden on the model; the status shape carries only connection state, the account currency, the product allowlist, and sync progress.
* **Connect with a Stripe RESTRICTED secret key** (`rk_live_…` / `rk_test_…`), never your full secret key. The key is stored encrypted and is never returned by any endpoint.
* A key with the wrong prefix, a test key used in production, or a key Stripe itself rejects is rejected with `422`.

***

### Get integration status

```http theme={null}
GET /api/sites/{siteKey}/integrations/stripe
```

The current Stripe connection status for the site, shaped identically for the API and MCP so they can never drift. Backed by `App\Support\Integrations\StripeIntegrationStatus`, wrapped in `StripeIntegrationResource`. **Never exposes the API key or webhook secret.**

When disconnected the shape is minimal. When connected it adds the live account currency, the product allowlist, and sync progress.

**Response**

| Field              | Type                      | Description                                                                                                                                                                |
| ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connected`        | bool                      | Whether a Stripe integration exists for the site.                                                                                                                          |
| `connected_at`     | string (ISO 8601) \| null | When it was connected; `null` when disconnected.                                                                                                                           |
| `account_currency` | string \| null            | Stripe account settlement currency (read live). `null` when the restricted key lacks account read. Present only when connected.                                            |
| `products`         | array                     | Product allowlist. Each: `external_id`, `name`, `selected` (bool). Present only when connected.                                                                            |
| `sync`             | object                    | Import progress: `running` (bool) and `resources[]`, each `resource`, `status`, `processed_count`, `error` (string\|null), `stranded` (bool). Present only when connected. |

Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl "https://clickbase.so/api/sites/example.com/integrations/stripe" \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/integrations/stripe', { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } })
  ```

  ```python title="Python" theme={null}
  requests.get('https://clickbase.so/api/sites/example.com/integrations/stripe', headers={'Authorization': f'Bearer {token}', 'Accept':'application/json'})
  ```
</CodeGroup>

***

### Connect Stripe

```http theme={null}
POST /api/sites/{siteKey}/integrations/stripe
```

Connect (or reconnect) the site's Stripe restricted key. Backed by `App\Actions\Integrations\ConnectStripeIntegration`. On success the key is stored encrypted and the status shape is returned; the key is never echoed back.

**Body parameters**

| Parameter | Type   | Required | Description                                                                                                      |
| --------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `key`     | string | Yes      | A Stripe **restricted secret key** (`rk_live_…` / `rk_test_…`). Max 255 chars. Stored encrypted, never returned. |

A bad prefix, a test key used in production, or a key Stripe rejects each returns `422` with the same validation error the web form shows.

**Response** — the integration status (as above). Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X POST "https://clickbase.so/api/sites/example.com/integrations/stripe" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"key":"rk_live_..."}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/integrations/stripe', { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ key: 'rk_live_...' }) })
  ```

  ```python title="Python" theme={null}
  requests.post('https://clickbase.so/api/sites/example.com/integrations/stripe', json={'key':'rk_live_...'}, headers={'Authorization': f'Bearer {token}', 'Accept':'application/json'})
  ```
</CodeGroup>

***

### Align reporting currency

```http theme={null}
PUT /api/sites/{siteKey}/integrations/stripe/currency
```

Step two of the connection flow: set the site's reporting currency to the Stripe account's settlement currency. Backed by `App\Actions\Integrations\AlignSiteCurrency` — which applies the same off-currency-payments guard as the site currency endpoint (a currency change is refused when it would strand already-ingested payments in another currency).

**Body parameters**

| Parameter  | Type   | Required | Description                               |
| ---------- | ------ | -------- | ----------------------------------------- |
| `currency` | string | Yes      | ISO 4217 currency code (exactly 3 chars). |

**Response** — the integration status (as above). Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X PUT "https://clickbase.so/api/sites/example.com/integrations/stripe/currency" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"currency":"usd"}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/integrations/stripe/currency', { method: 'PUT', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ currency: 'usd' }) })
  ```

  ```python title="Python" theme={null}
  requests.put('https://clickbase.so/api/sites/example.com/integrations/stripe/currency', json={'currency':'usd'}, headers={'Authorization': f'Bearer {token}', 'Accept':'application/json'})
  ```
</CodeGroup>

***

### Select products and start the import

```http theme={null}
PUT /api/sites/{siteKey}/integrations/stripe/products
```

Step three: persist the product allowlist **and then** start the backfill — in that order. Backed by `App\Actions\Integrations\UpdateStripeProductSelection` followed by `App\Actions\Integrations\StartStripeBackfill`.

The order is forced, not stylistic: the allowlist filters ingestion only and never removes already-ingested history, so a backfill that ran before product selection would leave a permanently unfilterable history. Select, then import.

**Body parameters**

| Parameter  | Type      | Required | Description                                                                                             |
| ---------- | --------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `products` | string\[] | Yes      | The allowlist of Stripe product ids to ingest. Must be present (may be empty). Each item max 255 chars. |

**Response** — the integration status, now reflecting the running sync. Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X PUT "https://clickbase.so/api/sites/example.com/integrations/stripe/products" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"products":["prod_ABC","prod_DEF"]}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/integrations/stripe/products', { method: 'PUT', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ products: ['prod_ABC', 'prod_DEF'] }) })
  ```

  ```python title="Python" theme={null}
  requests.put('https://clickbase.so/api/sites/example.com/integrations/stripe/products', json={'products':['prod_ABC','prod_DEF']}, headers={'Authorization': f'Bearer {token}', 'Accept':'application/json'})
  ```
</CodeGroup>

***

### Retry a stranded resource

```http theme={null}
POST /api/sites/{siteKey}/integrations/stripe/retry
```

Retry one failed or stranded resource of the import. Backed by `App\Actions\Integrations\ResumeStripeResourceSync`.

**This RESUMES from the resource's persisted cursor — it never restarts the import from page zero.** A retry can never re-run a 40,000-charge import from the beginning. The resource must already have a sync row; a resource with no sync row returns `404`.

**Body parameters**

| Parameter  | Type   | Required | Description                                     |
| ---------- | ------ | -------- | ----------------------------------------------- |
| `resource` | string | Yes      | One of the `StripeResource` values (see below). |

`resource` is one of: `products`, `charges`, `refunds`, `disputes`, `invoices`, `subscriptions`, `customers`. Any other value returns `422`.

**Response** — the integration status. Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X POST "https://clickbase.so/api/sites/example.com/integrations/stripe/retry" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"resource":"charges"}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/integrations/stripe/retry', { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ resource: 'charges' }) })
  ```

  ```python title="Python" theme={null}
  requests.post('https://clickbase.so/api/sites/example.com/integrations/stripe/retry', json={'resource':'charges'}, headers={'Authorization': f'Bearer {token}', 'Accept':'application/json'})
  ```
</CodeGroup>

***

### Disconnect Stripe

```http theme={null}
DELETE /api/sites/{siteKey}/integrations/stripe
```

Disconnect the site's Stripe integration. Wipes Stripe-derived payments, subscriptions, MRR movements, invoice MRR observations, lifecycle events, and settlement FX rates (`WipeStripeRevenueData`), then deletes the remote webhook (best-effort) and the local integration row. API/manual payments are kept. Backed by `App\Actions\Integrations\DisconnectStripeIntegration`. Returns the minimal disconnected status (`connected: false`, `connected_at: null`).

**Response**

| Field          | Type | Description                      |
| -------------- | ---- | -------------------------------- |
| `connected`    | bool | Always `false` after disconnect. |
| `connected_at` | null | Always `null` after disconnect.  |

Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X DELETE "https://clickbase.so/api/sites/example.com/integrations/stripe" \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/integrations/stripe', { method: 'DELETE', headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } })
  ```

  ```python title="Python" theme={null}
  requests.delete('https://clickbase.so/api/sites/example.com/integrations/stripe', headers={'Authorization': f'Bearer {token}', 'Accept':'application/json'})
  ```
</CodeGroup>
