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

# Finance products, memberships, and credits

> Read products and subscriptions, assign guarded finance products, and adjust credits through the FITsociety Public API.

Finance writes can create invoices, allocate credits, trigger payment links,
touch payment-provider validation, and send emails. Public API v1 therefore exposes
guarded write endpoints instead of raw finance controller payloads.

The authenticated company is always derived from the Bearer token. Public
requests must not include `companyId`, `addedByCoach`, price, or VAT fields.

## Scopes

| Scope                                | Allows                                                           |
| :----------------------------------- | :--------------------------------------------------------------- |
| `finance_invoices:read`              | Read invoice metadata and line items.                            |
| `finance_invoice_pdfs:read`          | Read existing invoice PDF download metadata.                     |
| `finance_payments:read`              | Read payment/transaction metadata.                               |
| `finance_payment_requests:read`      | Read payment request metadata.                                   |
| `finance_payment_requests:write`     | Create and cancel guarded payment requests.                      |
| `finance_chargebacks:read`           | Read chargeback metadata.                                        |
| `finance_invoice_messages:write`     | Send invoice reminders and invoice copies for eligible invoices. |
| `finance_products:read`              | Read product metadata.                                           |
| `finance_products:write`             | Assign supported products and revoke supported client products.  |
| `finance_subscriptions:read`         | Read client subscriptions.                                       |
| `finance_subscription_actions:write` | Create pause or cancellation requests for review.                |
| `finance_memberships:write`          | Assign a membership product to a client.                         |
| `finance_credits:read`               | Read client credit balances and credit mutations.                |
| `finance_credits:write`              | Adjust client credits.                                           |

## Finance write guardrails

Finance write endpoints reject these caller-supplied fields:

```txt theme={null}
productPrice
price
priceExcVat
priceVat
vat
vatPercentage
```

Pricing and VAT come from the stored FITsociety product. Payment methods that
require provider-side validation keep the existing payment-provider readiness,
direct debit limit, IBAN, and BIC checks.

## List invoices

```http theme={null}
GET /public/v1/finance/invoices?dateFrom=2026-07-01T00:00:00.000Z&dateTo=2026-07-31T23:59:59.999Z
Authorization: Bearer <access_token>
```

Required scope: `finance_invoices:read`

Query parameters:

| Parameter     | Type          | Required | Rule                                                           |
| :------------ | :------------ | :------- | :------------------------------------------------------------- |
| `page`        | integer       | no       | Minimum `1`.                                                   |
| `limit`       | integer       | no       | Default `100`, maximum `100`.                                  |
| `dateFrom`    | ISO date-time | no       | Optional issue-date range start. Must be paired with `dateTo`. |
| `dateTo`      | ISO date-time | no       | Optional issue-date range end. Max 366-day range.              |
| `clientId`    | string        | no       | Client must belong to the authenticated company.               |
| `status`      | string        | no       | `Pending`, `Paid`, or `Chargeback`.                            |
| `invoiceType` | string        | no       | `Invoice` or `CreditNote`.                                     |

Response fields:

| Field                                  | Type    | Nullable | Description                                                        |
| :------------------------------------- | :------ | :------- | :----------------------------------------------------------------- |
| `data.page`                            | integer | no       | Current page.                                                      |
| `data.limit`                           | integer | no       | Page size after cap.                                               |
| `data.total`                           | integer | no       | Total matching invoices.                                           |
| `data.totalPages`                      | integer | no       | Total pages.                                                       |
| `data.hasNextPage`                     | boolean | no       | Next page availability.                                            |
| `data.hasPrevPage`                     | boolean | no       | Previous page availability.                                        |
| `data.invoices[].invoiceId`            | string  | no       | Invoice document ID.                                               |
| `data.invoices[].invoiceNumber`        | string  | no       | Human invoice number.                                              |
| `data.invoices[].invoiceType`          | string  | no       | `Invoice` or `CreditNote`.                                         |
| `data.invoices[].status`               | string  | no       | Invoice status.                                                    |
| `data.invoices[].paymentStatus`        | string  | no       | Provider/payment processing status.                                |
| `data.invoices[].totalPrice`           | number  | yes      | Total price including VAT.                                         |
| `data.invoices[].totalVat`             | number  | yes      | Total VAT.                                                         |
| `data.invoices[].currency`             | string  | no       | Currency code.                                                     |
| `data.invoices[].issueDate`            | string  | no       | Issue date.                                                        |
| `data.invoices[].dueDate`              | string  | no       | Due date.                                                          |
| `data.invoices[].paymentMethod`        | string  | no       | Invoice payment method.                                            |
| `data.invoices[].title`                | string  | no       | Invoice title.                                                     |
| `data.invoices[].client.clientId`      | string  | yes      | Client ID when linked.                                             |
| `data.invoices[].client.name`          | string  | no       | Client display name.                                               |
| `data.invoices[].subscriptionId`       | string  | yes      | Linked subscription ID.                                            |
| `data.invoices[].clientProductId`      | string  | yes      | Linked client product ID.                                          |
| `data.invoices[].paymentTransactionId` | string  | no       | Linked payment transaction reference.                              |
| `data.invoices[].pdfAvailable`         | boolean | no       | Whether an existing PDF can be requested through the PDF endpoint. |
| `data.invoices[].source`               | string  | no       | Invoice source.                                                    |
| `data.invoices[].createdAt`            | string  | yes      | Creation timestamp.                                                |
| `data.invoices[].updatedAt`            | string  | yes      | Last update timestamp.                                             |

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    "https://api.fitsociety.io/public/v1/finance/invoices?dateFrom=2026-07-01T00:00:00.000Z&dateTo=2026-07-31T23:59:59.999Z&status=Pending&page=1&limit=100"
  ```

  ```javascript JavaScript theme={null}
  const query = new URLSearchParams({
    dateFrom: "2026-07-01T00:00:00.000Z",
    dateTo: "2026-07-31T23:59:59.999Z",
    status: "Pending",
    page: "1",
    limit: "100",
  });

  const response = await fetch(
    `https://api.fitsociety.io/public/v1/finance/invoices?${query}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
      },
    },
  );

  const body = await response.json();
  console.log(body.data.total, body.data.invoices);
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.get(
      "https://api.fitsociety.io/public/v1/finance/invoices",
      headers={"Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}"},
      params={
          "dateFrom": "2026-07-01T00:00:00.000Z",
          "dateTo": "2026-07-31T23:59:59.999Z",
          "status": "Pending",
          "page": 1,
          "limit": 100,
      },
  )
  response.raise_for_status()

  body = response.json()
  print(body["data"]["total"], body["data"]["invoices"])
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": {
    "page": 1,
    "limit": 100,
    "total": 1,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false,
    "invoices": [
      {
        "invoiceId": "64b64c0f2f5f4c00123456b0",
        "invoiceNumber": "2026-0142",
        "invoiceType": "Invoice",
        "status": "Pending",
        "paymentStatus": "open",
        "totalPrice": 99.95,
        "totalVat": 17.35,
        "currency": "EUR",
        "issueDate": "2026-07-14T00:00:00.000Z",
        "dueDate": "2026-07-28T00:00:00.000Z",
        "paymentMethod": "SepaIncasso",
        "title": "Monthly membership",
        "client": {
          "clientId": "64b64c0f2f5f4c0012345678",
          "name": "Jane Doe"
        },
        "subscriptionId": "64b64c0f2f5f4c00123456b1",
        "clientProductId": "64b64c0f2f5f4c00123456b2",
        "paymentTransactionId": "",
        "pdfAvailable": true,
        "source": "subscription",
        "createdAt": "2026-07-14T09:30:00.000Z",
        "updatedAt": "2026-07-14T09:30:00.000Z"
      }
    ]
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 }
  }
}
```

## List client invoices

```http theme={null}
GET /public/v1/finance/clients/{clientId}/invoices
Authorization: Bearer <access_token>
```

Required scope: `finance_invoices:read`

The response uses `data.page`, `data.limit`, `data.total`, `data.totalPages`,
`data.hasNextPage`, `data.hasPrevPage`, and `data.invoices[]` entries with
`invoiceId`, `invoiceNumber`, `invoiceType`, `status`, `paymentStatus`,
`totalPrice`, `totalVat`, `currency`, `issueDate`, `dueDate`, `paymentMethod`,
`title`, `client`, `subscriptionId`, `clientProductId`,
`paymentTransactionId`, `pdfAvailable`, `source`, `createdAt`, and `updatedAt`.
The `clientId` path parameter must belong to the authenticated company.

## Get invoice

```http theme={null}
GET /public/v1/finance/invoices/{invoiceId}
Authorization: Bearer <access_token>
```

Required scope: `finance_invoices:read`

Response fields:

| Field                                     | Type    | Nullable | Description                                                        |
| :---------------------------------------- | :------ | :------- | :----------------------------------------------------------------- |
| `data.invoice.invoiceId`                  | string  | no       | Invoice document ID.                                               |
| `data.invoice.invoiceNumber`              | string  | no       | Human invoice number.                                              |
| `data.invoice.invoiceType`                | string  | no       | `Invoice` or `CreditNote`.                                         |
| `data.invoice.status`                     | string  | no       | Invoice status.                                                    |
| `data.invoice.paymentStatus`              | string  | no       | Provider/payment processing status.                                |
| `data.invoice.totalPrice`                 | number  | yes      | Total price including VAT.                                         |
| `data.invoice.totalVat`                   | number  | yes      | Total VAT.                                                         |
| `data.invoice.currency`                   | string  | no       | Currency code.                                                     |
| `data.invoice.issueDate`                  | string  | no       | Issue date.                                                        |
| `data.invoice.dueDate`                    | string  | no       | Due date.                                                          |
| `data.invoice.paymentMethod`              | string  | no       | Invoice payment method.                                            |
| `data.invoice.title`                      | string  | no       | Invoice title.                                                     |
| `data.invoice.client.clientId`            | string  | yes      | Client ID when linked.                                             |
| `data.invoice.client.name`                | string  | no       | Client display name.                                               |
| `data.invoice.subscriptionId`             | string  | yes      | Linked subscription ID.                                            |
| `data.invoice.clientProductId`            | string  | yes      | Linked client product ID.                                          |
| `data.invoice.paymentTransactionId`       | string  | no       | Linked payment transaction reference.                              |
| `data.invoice.pdfAvailable`               | boolean | no       | Whether an existing PDF can be requested through the PDF endpoint. |
| `data.invoice.source`                     | string  | no       | Invoice source.                                                    |
| `data.invoice.createdAt`                  | string  | yes      | Creation timestamp.                                                |
| `data.invoice.updatedAt`                  | string  | yes      | Last update timestamp.                                             |
| `data.invoice.items[]`                    | array   | no       | Invoice line items, maximum 100 returned.                          |
| `data.invoice.items[].name`               | string  | no       | Line item name.                                                    |
| `data.invoice.items[].price`              | number  | yes      | Unit price including VAT.                                          |
| `data.invoice.items[].quantity`           | number  | yes      | Quantity.                                                          |
| `data.invoice.items[].totalPrice`         | number  | yes      | Line total when parseable.                                         |
| `data.invoice.items[].vat`                | number  | yes      | VAT percentage.                                                    |
| `data.invoice.items[].adjustmentType`     | string  | no       | `charge` or `discount`.                                            |
| `data.invoice.items[].billingPeriodStart` | string  | no       | Billing period start.                                              |
| `data.invoice.items[].billingPeriodEnd`   | string  | no       | Billing period end.                                                |

The invoice detail endpoint does not return `pdfLink`, raw provider payloads,
payment-provider snapshots, billing-profile snapshots, internal notes, or
history arrays.

## Get invoice PDF metadata

```http theme={null}
GET /public/v1/finance/invoices/{invoiceId}/pdf
Authorization: Bearer <access_token>
```

Required scope: `finance_invoice_pdfs:read`

This endpoint returns a short-lived URL for an existing invoice PDF. It does not
generate PDFs, does not append invoice history, and does not modify the invoice.
If no PDF is stored yet, the response is `404 PDF_NOT_AVAILABLE`.

Response fields:

| Field                           | Type    | Nullable | Description                                               |
| :------------------------------ | :------ | :------- | :-------------------------------------------------------- |
| `data.invoicePdf.invoiceId`     | string  | no       | Invoice document ID.                                      |
| `data.invoicePdf.invoiceNumber` | string  | no       | Human invoice number.                                     |
| `data.invoicePdf.pdfAvailable`  | boolean | no       | Always `true` for successful responses.                   |
| `data.invoicePdf.downloadUrl`   | string  | no       | Signed/normalized media URL for download.                 |
| `data.invoicePdf.expiresAt`     | string  | no       | Expiry timestamp, currently 15 minutes from request time. |
| `data.invoicePdf.fileName`      | string  | no       | Suggested PDF filename.                                   |
| `data.invoicePdf.contentType`   | string  | no       | `application/pdf`.                                        |

## Send invoice reminder

```http theme={null}
POST /public/v1/finance/invoices/{invoiceId}/send-reminder
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_invoice_messages:write`

Request body:

| Field           | Type    | Required | Rule                                                 |
| :-------------- | :------ | :------- | :--------------------------------------------------- |
| `notifyClient`  | boolean | no       | Defaults to the underlying reminder helper behavior. |
| `clientMessage` | string  | no       | Optional message passed to the reminder helper.      |

Validation:

| Rule           | Behavior                                                              |
| :------------- | :-------------------------------------------------------------------- |
| Invoice scope  | Invoice must belong to the authenticated company.                     |
| Invoice type   | Only `Invoice` is allowed; credit notes are rejected.                 |
| Status         | Only `Pending` and `Chargeback` are eligible.                         |
| Client access  | Linked clients must belong to the authenticated company relationship. |
| Unknown fields | Rejected with `PUBLIC_API_UNKNOWN_FIELDS`.                            |

Response fields:

| Field                               | Type    | Nullable | Description                                                        |
| :---------------------------------- | :------ | :------- | :----------------------------------------------------------------- |
| `data.delivery.type`                | string  | no       | `payment_reminder`.                                                |
| `data.delivery.status`              | string  | no       | `sent` for successful requests.                                    |
| `data.delivery.sentAt`              | string  | no       | Send request timestamp.                                            |
| `data.invoice.invoiceId`            | string  | no       | Invoice document ID.                                               |
| `data.invoice.invoiceNumber`        | string  | no       | Human invoice number.                                              |
| `data.invoice.invoiceType`          | string  | no       | `Invoice`.                                                         |
| `data.invoice.status`               | string  | no       | Invoice status.                                                    |
| `data.invoice.paymentStatus`        | string  | no       | Provider/payment processing status.                                |
| `data.invoice.totalPrice`           | number  | yes      | Total price including VAT.                                         |
| `data.invoice.totalVat`             | number  | yes      | Total VAT.                                                         |
| `data.invoice.currency`             | string  | no       | Currency code.                                                     |
| `data.invoice.issueDate`            | string  | no       | Issue date.                                                        |
| `data.invoice.dueDate`              | string  | no       | Due date.                                                          |
| `data.invoice.paymentMethod`        | string  | no       | Invoice payment method.                                            |
| `data.invoice.title`                | string  | no       | Invoice title.                                                     |
| `data.invoice.client.clientId`      | string  | yes      | Client ID when linked.                                             |
| `data.invoice.client.name`          | string  | no       | Client display name.                                               |
| `data.invoice.subscriptionId`       | string  | yes      | Linked subscription ID.                                            |
| `data.invoice.clientProductId`      | string  | yes      | Linked client product ID.                                          |
| `data.invoice.paymentTransactionId` | string  | no       | Linked payment transaction reference.                              |
| `data.invoice.pdfAvailable`         | boolean | no       | Whether an existing PDF can be requested through the PDF endpoint. |
| `data.invoice.source`               | string  | no       | Invoice source.                                                    |
| `data.invoice.createdAt`            | string  | yes      | Creation timestamp.                                                |
| `data.invoice.updatedAt`            | string  | yes      | Last update timestamp.                                             |

## Resend invoice copy

```http theme={null}
POST /public/v1/finance/invoices/{invoiceId}/resend
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_invoice_messages:write`

This endpoint accepts the Send invoice reminder request contract: optional
`notifyClient` and optional `clientMessage`, with unknown fields rejected. The
response uses `data.delivery.type = invoice_copy` and returns
`data.delivery.status`, `data.delivery.sentAt`, and `data.invoice.*` with
`invoiceId`, `invoiceNumber`, `invoiceType`, `status`, `paymentStatus`,
`totalPrice`, `totalVat`, `currency`, `issueDate`, `dueDate`, `paymentMethod`,
`title`, `client`, `subscriptionId`, `clientProductId`,
`paymentTransactionId`, `pdfAvailable`, `source`, `createdAt`, and `updatedAt`.

## List payments

```http theme={null}
GET /public/v1/finance/payments?dateFrom=2026-07-01T00:00:00.000Z&dateTo=2026-07-31T23:59:59.999Z
Authorization: Bearer <access_token>
```

Required scope: `finance_payments:read`

Query parameters: `page`, `limit` (max 100), `dateFrom`, `dateTo` (max 366
days), `clientId`, `invoiceId`, and `status`.

Response fields:

| Field                               | Type    | Nullable | Description                      |
| :---------------------------------- | :------ | :------- | :------------------------------- |
| `data.page`                         | integer | no       | Current page.                    |
| `data.limit`                        | integer | no       | Page size after cap.             |
| `data.total`                        | integer | no       | Total matching payments.         |
| `data.totalPages`                   | integer | no       | Total pages.                     |
| `data.hasNextPage`                  | boolean | no       | Next page availability.          |
| `data.hasPrevPage`                  | boolean | no       | Previous page availability.      |
| `data.payments[].paymentId`         | string  | no       | Payment/transaction document ID. |
| `data.payments[].invoiceId`         | string  | yes      | Linked invoice document ID.      |
| `data.payments[].invoiceNumber`     | string  | no       | Human invoice number/reference.  |
| `data.payments[].externalPaymentId` | string  | no       | Provider payment reference.      |
| `data.payments[].status`            | string  | no       | Payment status.                  |
| `data.payments[].amount`            | number  | yes      | Payment amount.                  |
| `data.payments[].currency`          | string  | no       | Currency code.                   |
| `data.payments[].provider`          | string  | no       | Payment provider key.            |
| `data.payments[].productType`       | string  | no       | Product type.                    |
| `data.payments[].paymentMethod`     | string  | no       | Payment method when available.   |
| `data.payments[].source`            | string  | no       | Transaction source.              |
| `data.payments[].orderReference`    | string  | no       | Order reference.                 |
| `data.payments[].client.clientId`   | string  | yes      | Client ID when linked.           |
| `data.payments[].createdAt`         | string  | yes      | Creation timestamp.              |
| `data.payments[].updatedAt`         | string  | yes      | Last update timestamp.           |

Payment responses never include raw payment-provider payloads, customer
IDs, mandate IDs, provider snapshots, or internal notes.

## Get payment

```http theme={null}
GET /public/v1/finance/payments/{paymentId}
Authorization: Bearer <access_token>
```

Required scope: `finance_payments:read`

Response fields:

| Field                            | Type   | Nullable | Description                               |
| :------------------------------- | :----- | :------- | :---------------------------------------- |
| `data.payment.paymentId`         | string | no       | Payment/transaction document ID.          |
| `data.payment.invoiceId`         | string | yes      | Linked invoice document ID.               |
| `data.payment.invoiceNumber`     | string | no       | Human invoice number/reference.           |
| `data.payment.externalPaymentId` | string | no       | Provider payment reference.               |
| `data.payment.status`            | string | no       | Payment status.                           |
| `data.payment.amount`            | number | yes      | Payment amount.                           |
| `data.payment.currency`          | string | no       | Currency code.                            |
| `data.payment.provider`          | string | no       | Payment provider key.                     |
| `data.payment.productType`       | string | no       | Product type.                             |
| `data.payment.paymentMethod`     | string | no       | Payment method when available.            |
| `data.payment.source`            | string | no       | Transaction source.                       |
| `data.payment.orderReference`    | string | no       | Order reference.                          |
| `data.payment.client.clientId`   | string | yes      | Client ID when linked.                    |
| `data.payment.client.name`       | string | no       | Empty string in payment detail responses. |
| `data.payment.createdAt`         | string | yes      | Creation timestamp.                       |
| `data.payment.updatedAt`         | string | yes      | Last update timestamp.                    |

## List payment requests

```http theme={null}
GET /public/v1/finance/payment-requests
Authorization: Bearer <access_token>
```

Required scope: `finance_payment_requests:read`

Query parameters: `page`, `limit` (max 100), `status`, and `clientId` when
filtering linked-client requests.

Response fields:

| Field                                              | Type    | Nullable | Description                                 |
| :------------------------------------------------- | :------ | :------- | :------------------------------------------ |
| `data.page`                                        | integer | no       | Current page.                               |
| `data.limit`                                       | integer | no       | Page size after cap.                        |
| `data.total`                                       | integer | no       | Total matching payment requests.            |
| `data.totalPages`                                  | integer | no       | Total pages.                                |
| `data.hasNextPage`                                 | boolean | no       | Next page availability.                     |
| `data.hasPrevPage`                                 | boolean | no       | Previous page availability.                 |
| `data.paymentRequests[].paymentRequestId`          | string  | no       | Payment request ID.                         |
| `data.paymentRequests[].recipientMode`             | string  | no       | Recipient mode.                             |
| `data.paymentRequests[].clientId`                  | string  | yes      | Linked client ID.                           |
| `data.paymentRequests[].counterpartyId`            | string  | yes      | Counterparty ID when stored.                |
| `data.paymentRequests[].description`               | string  | no       | Request description.                        |
| `data.paymentRequests[].amount`                    | number  | yes      | Total amount.                               |
| `data.paymentRequests[].currency`                  | string  | no       | Currency.                                   |
| `data.paymentRequests[].status`                    | string  | no       | Request status.                             |
| `data.paymentRequests[].fulfillmentStatus`         | string  | no       | Fulfillment status.                         |
| `data.paymentRequests[].lineItems[]`               | array   | no       | Public line items, max existing stored set. |
| `data.paymentRequests[].lineItems[].kind`          | string  | no       | Line kind, for example `custom`.            |
| `data.paymentRequests[].lineItems[].productId`     | string  | yes      | Linked product ID when stored.              |
| `data.paymentRequests[].lineItems[].productType`   | string  | no       | Linked product type when stored.            |
| `data.paymentRequests[].lineItems[].name`          | string  | no       | Line item name.                             |
| `data.paymentRequests[].lineItems[].description`   | string  | no       | Line item description.                      |
| `data.paymentRequests[].lineItems[].quantity`      | number  | yes      | Quantity.                                   |
| `data.paymentRequests[].lineItems[].price`         | number  | yes      | Unit price including VAT when stored.       |
| `data.paymentRequests[].lineItems[].totalPrice`    | number  | yes      | Line total including VAT when stored.       |
| `data.paymentRequests[].lineItems[].vatPercentage` | number  | yes      | VAT percentage when stored.                 |
| `data.paymentRequests[].invoiceId`                 | string  | yes      | Linked invoice ID.                          |
| `data.paymentRequests[].clientProductId`           | string  | yes      | Linked client product ID.                   |
| `data.paymentRequests[].paidAt`                    | string  | yes      | Paid timestamp.                             |
| `data.paymentRequests[].cancelledAt`               | string  | yes      | Cancelled timestamp.                        |
| `data.paymentRequests[].createdAt`                 | string  | yes      | Creation timestamp.                         |
| `data.paymentRequests[].updatedAt`                 | string  | yes      | Last update timestamp.                      |

Not exposed: public tokens, checkout/public URLs, return/webhook URLs,
provider responses, payment transaction IDs, or ledger account IDs.

## Get payment request

```http theme={null}
GET /public/v1/finance/payment-requests/{paymentRequestId}
Authorization: Bearer <access_token>
```

Required scope: `finance_payment_requests:read`

Response fields:

| Field                                           | Type   | Nullable | Description                           |
| :---------------------------------------------- | :----- | :------- | :------------------------------------ |
| `data.paymentRequest.paymentRequestId`          | string | no       | Payment request ID.                   |
| `data.paymentRequest.recipientMode`             | string | no       | Recipient mode.                       |
| `data.paymentRequest.clientId`                  | string | yes      | Linked client ID.                     |
| `data.paymentRequest.counterpartyId`            | string | yes      | Counterparty ID when stored.          |
| `data.paymentRequest.description`               | string | no       | Request description.                  |
| `data.paymentRequest.amount`                    | number | yes      | Total amount.                         |
| `data.paymentRequest.currency`                  | string | no       | Currency.                             |
| `data.paymentRequest.status`                    | string | no       | Request status.                       |
| `data.paymentRequest.fulfillmentStatus`         | string | no       | Fulfillment status.                   |
| `data.paymentRequest.lineItems[].kind`          | string | no       | Line kind, for example `custom`.      |
| `data.paymentRequest.lineItems[].productId`     | string | yes      | Linked product ID when stored.        |
| `data.paymentRequest.lineItems[].productType`   | string | no       | Linked product type when stored.      |
| `data.paymentRequest.lineItems[].name`          | string | no       | Line item name.                       |
| `data.paymentRequest.lineItems[].description`   | string | no       | Line item description.                |
| `data.paymentRequest.lineItems[].quantity`      | number | yes      | Quantity.                             |
| `data.paymentRequest.lineItems[].price`         | number | yes      | Unit price including VAT when stored. |
| `data.paymentRequest.lineItems[].totalPrice`    | number | yes      | Line total including VAT when stored. |
| `data.paymentRequest.lineItems[].vatPercentage` | number | yes      | VAT percentage when stored.           |
| `data.paymentRequest.invoiceId`                 | string | yes      | Linked invoice ID.                    |
| `data.paymentRequest.clientProductId`           | string | yes      | Linked client product ID.             |
| `data.paymentRequest.paidAt`                    | string | yes      | Paid timestamp.                       |
| `data.paymentRequest.cancelledAt`               | string | yes      | Cancelled timestamp.                  |
| `data.paymentRequest.createdAt`                 | string | yes      | Creation timestamp.                   |
| `data.paymentRequest.updatedAt`                 | string | yes      | Last update timestamp.                |

## Create payment request

```http theme={null}
POST /public/v1/finance/payment-requests
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_payment_requests:write`

Request body:

| Field                   | Type    | Required    | Rule                                                                                  |
| :---------------------- | :------ | :---------- | :------------------------------------------------------------------------------------ |
| `recipientMode`         | string  | yes         | Recipient mode accepted by the payment request helper.                                |
| `clientId`              | string  | conditional | Required for linked-client requests. Client must belong to the authenticated company. |
| `counterparty`          | object  | conditional | Counterparty payload accepted by the payment request helper.                          |
| `description`           | string  | yes         | Payment request description.                                                          |
| `amount`                | number  | yes         | Positive amount accepted by the payment request helper.                               |
| `lineItems[]`           | array   | no          | Optional public line items.                                                           |
| `currency`              | string  | no          | Currency code.                                                                        |
| `locationId`            | string  | no          | Payment context location.                                                             |
| `homeLocationId`        | string  | no          | Payment context home location.                                                        |
| `billingProfileId`      | string  | no          | Billing profile.                                                                      |
| `paymentConnectionId`   | string  | no          | Payment connection.                                                                   |
| `includePaymentPageUrl` | boolean | no          | When true, response may include public checkout metadata.                             |

Forbidden caller-supplied fields: `productPrice`, `price`, `priceExcVat`,
`priceVat`, `vat`, and `vatPercentage`. Unknown fields are rejected.

Response fields:

| Field                                           | Type   | Nullable | Description                                                   |
| :---------------------------------------------- | :----- | :------- | :------------------------------------------------------------ |
| `data.paymentRequest.paymentRequestId`          | string | no       | Payment request ID.                                           |
| `data.paymentRequest.recipientMode`             | string | no       | Recipient mode.                                               |
| `data.paymentRequest.clientId`                  | string | yes      | Linked client ID.                                             |
| `data.paymentRequest.counterpartyId`            | string | yes      | Counterparty ID when stored.                                  |
| `data.paymentRequest.description`               | string | no       | Request description.                                          |
| `data.paymentRequest.amount`                    | number | yes      | Total amount.                                                 |
| `data.paymentRequest.currency`                  | string | no       | Currency.                                                     |
| `data.paymentRequest.status`                    | string | no       | Request status.                                               |
| `data.paymentRequest.fulfillmentStatus`         | string | no       | Fulfillment status.                                           |
| `data.paymentRequest.lineItems[].kind`          | string | no       | Line kind, for example `custom`.                              |
| `data.paymentRequest.lineItems[].productId`     | string | yes      | Linked product ID when stored.                                |
| `data.paymentRequest.lineItems[].productType`   | string | no       | Linked product type when stored.                              |
| `data.paymentRequest.lineItems[].name`          | string | no       | Line item name.                                               |
| `data.paymentRequest.lineItems[].description`   | string | no       | Line item description.                                        |
| `data.paymentRequest.lineItems[].quantity`      | number | yes      | Quantity.                                                     |
| `data.paymentRequest.lineItems[].price`         | number | yes      | Unit price including VAT when stored.                         |
| `data.paymentRequest.lineItems[].totalPrice`    | number | yes      | Line total including VAT when stored.                         |
| `data.paymentRequest.lineItems[].vatPercentage` | number | yes      | VAT percentage when stored.                                   |
| `data.paymentRequest.invoiceId`                 | string | yes      | Linked invoice ID.                                            |
| `data.paymentRequest.clientProductId`           | string | yes      | Linked client product ID.                                     |
| `data.paymentRequest.paidAt`                    | string | yes      | Paid timestamp.                                               |
| `data.paymentRequest.cancelledAt`               | string | yes      | Cancelled timestamp.                                          |
| `data.paymentRequest.createdAt`                 | string | yes      | Creation timestamp.                                           |
| `data.paymentRequest.updatedAt`                 | string | yes      | Last update timestamp.                                        |
| `data.paymentPage.publicUrl`                    | string | yes      | Public page URL only when explicitly requested and available. |
| `data.paymentPage.checkoutUrl`                  | string | yes      | Checkout URL only when explicitly requested and available.    |

List and get endpoints never return checkout/public URLs.

## Cancel payment request

```http theme={null}
POST /public/v1/finance/payment-requests/{paymentRequestId}/cancel
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_payment_requests:write`

Request body:

| Field    | Type   | Required | Rule                                                 |
| :------- | :----- | :------- | :--------------------------------------------------- |
| `reason` | string | no       | Optional cancellation reason accepted by the helper. |

The payment request must belong to the authenticated company. Linked-client
requests also require client access.

Response fields:

| Field                                           | Type    | Nullable | Description                                         |
| :---------------------------------------------- | :------ | :------- | :-------------------------------------------------- |
| `data.paymentRequest.paymentRequestId`          | string  | no       | Payment request ID.                                 |
| `data.paymentRequest.recipientMode`             | string  | no       | Recipient mode.                                     |
| `data.paymentRequest.clientId`                  | string  | yes      | Linked client ID.                                   |
| `data.paymentRequest.counterpartyId`            | string  | yes      | Counterparty ID when stored.                        |
| `data.paymentRequest.description`               | string  | no       | Request description.                                |
| `data.paymentRequest.amount`                    | number  | yes      | Total amount.                                       |
| `data.paymentRequest.currency`                  | string  | no       | Currency.                                           |
| `data.paymentRequest.status`                    | string  | no       | Request status after cancellation.                  |
| `data.paymentRequest.fulfillmentStatus`         | string  | no       | Fulfillment status.                                 |
| `data.paymentRequest.lineItems[].kind`          | string  | no       | Line kind, for example `custom`.                    |
| `data.paymentRequest.lineItems[].productId`     | string  | yes      | Linked product ID when stored.                      |
| `data.paymentRequest.lineItems[].productType`   | string  | no       | Linked product type when stored.                    |
| `data.paymentRequest.lineItems[].name`          | string  | no       | Line item name.                                     |
| `data.paymentRequest.lineItems[].description`   | string  | no       | Line item description.                              |
| `data.paymentRequest.lineItems[].quantity`      | number  | yes      | Quantity.                                           |
| `data.paymentRequest.lineItems[].price`         | number  | yes      | Unit price including VAT when stored.               |
| `data.paymentRequest.lineItems[].totalPrice`    | number  | yes      | Line total including VAT when stored.               |
| `data.paymentRequest.lineItems[].vatPercentage` | number  | yes      | VAT percentage when stored.                         |
| `data.paymentRequest.invoiceId`                 | string  | yes      | Linked invoice ID.                                  |
| `data.paymentRequest.clientProductId`           | string  | yes      | Linked client product ID.                           |
| `data.paymentRequest.paidAt`                    | string  | yes      | Paid timestamp.                                     |
| `data.paymentRequest.cancelledAt`               | string  | yes      | Cancelled timestamp.                                |
| `data.paymentRequest.createdAt`                 | string  | yes      | Creation timestamp.                                 |
| `data.paymentRequest.updatedAt`                 | string  | yes      | Last update timestamp.                              |
| `data.cancelled`                                | boolean | no       | `true` when the cancel helper accepted the request. |

## List chargebacks

```http theme={null}
GET /public/v1/finance/chargebacks
Authorization: Bearer <access_token>
```

Required scope: `finance_chargebacks:read`

Query parameters: `page`, `limit` (max 100), `status`, and `clientId`.

Response fields:

| Field                                   | Type    | Nullable | Description                              |
| :-------------------------------------- | :------ | :------- | :--------------------------------------- |
| `data.page`                             | integer | no       | Current page.                            |
| `data.limit`                            | integer | no       | Page size after cap.                     |
| `data.total`                            | integer | no       | Total matching chargebacks.              |
| `data.totalPages`                       | integer | no       | Total pages.                             |
| `data.hasNextPage`                      | boolean | no       | Next page availability.                  |
| `data.hasPrevPage`                      | boolean | no       | Previous page availability.              |
| `data.chargebacks[].chargebackId`       | string  | no       | Chargeback ID.                           |
| `data.chargebacks[].clientId`           | string  | yes      | Linked client ID.                        |
| `data.chargebacks[].invoiceId`          | string  | yes      | Linked invoice document ID.              |
| `data.chargebacks[].amount`             | number  | yes      | Chargeback amount.                       |
| `data.chargebacks[].currency`           | string  | no       | Currency.                                |
| `data.chargebacks[].reason.code`        | string  | no       | Provider reason code when stored.        |
| `data.chargebacks[].reason.description` | string  | no       | Provider reason description when stored. |
| `data.chargebacks[].status`             | string  | no       | Chargeback status.                       |
| `data.chargebacks[].createdAt`          | string  | yes      | Creation timestamp.                      |
| `data.chargebacks[].reversedAt`         | string  | yes      | Reversal timestamp.                      |

Not exposed: provider IDs, payment IDs, raw provider payloads, webhook payloads,
or notification timestamps.

## Get chargeback

```http theme={null}
GET /public/v1/finance/chargebacks/{chargebackId}
Authorization: Bearer <access_token>
```

Required scope: `finance_chargebacks:read`

Response fields:

| Field                                | Type   | Nullable | Description                              |
| :----------------------------------- | :----- | :------- | :--------------------------------------- |
| `data.chargeback.chargebackId`       | string | no       | Chargeback ID.                           |
| `data.chargeback.clientId`           | string | yes      | Linked client ID.                        |
| `data.chargeback.invoiceId`          | string | yes      | Linked invoice document ID.              |
| `data.chargeback.amount`             | number | yes      | Chargeback amount.                       |
| `data.chargeback.currency`           | string | no       | Currency.                                |
| `data.chargeback.reason.code`        | string | no       | Provider reason code when stored.        |
| `data.chargeback.reason.description` | string | no       | Provider reason description when stored. |
| `data.chargeback.status`             | string | no       | Chargeback status.                       |
| `data.chargeback.createdAt`          | string | yes      | Creation timestamp.                      |
| `data.chargeback.reversedAt`         | string | yes      | Reversal timestamp.                      |

## Product catalog overview

```http theme={null}
GET /public/v1/finance/products
Authorization: Bearer <access_token>
```

Required scope: `finance_products:read`

This endpoint lists all active product catalog items across:

* `StoreModuleMembership`
* `StoreModuleCreditPack`
* `StoreModuleDay`
* `StoreModuleSingle`

The typed catalog endpoints below expose create, read, update, archive, and
linked-client lookup for the same four product families. All catalog operations
derive `companyId` from the access token. Clients must never send `companyId`.

Query parameters:

| Parameter     | Type    | Required | Rule                                                                                        |
| :------------ | :------ | :------- | :------------------------------------------------------------------------------------------ |
| `page`        | integer | no       | Minimum `1`.                                                                                |
| `limit`       | integer | no       | Default `100`, maximum `100`.                                                               |
| `productType` | string  | no       | `StoreModuleMembership`, `StoreModuleCreditPack`, `StoreModuleDay`, or `StoreModuleSingle`. |

Envelope fields:

| Field                              | Type    | Nullable | Description                                                |
| :--------------------------------- | :------ | :------- | :--------------------------------------------------------- |
| `meta.requestId`                   | string  | no       | Request correlation ID.                                    |
| `meta.rateLimit.limit`             | integer | yes      | Applied request limit when rate-limit headers are present. |
| `meta.rateLimit.remaining`         | integer | yes      | Remaining requests in the current window.                  |
| `meta.rateLimit.resetSeconds`      | integer | yes      | Seconds until the current limit window resets.             |
| `meta.rateLimit.retryAfterSeconds` | integer | yes      | Present on limited responses when retry timing is known.   |

Pagination fields:

| Field              | Type    | Nullable | Description                                   |
| :----------------- | :------ | :------- | :-------------------------------------------- |
| `data.page`        | integer | no       | Current page.                                 |
| `data.limit`       | integer | no       | Page size after cap.                          |
| `data.total`       | integer | no       | Total products across included product types. |
| `data.totalPages`  | integer | no       | Total pages.                                  |
| `data.hasNextPage` | boolean | no       | Next page availability.                       |
| `data.hasPrevPage` | boolean | no       | Previous page availability.                   |

Common product response fields:

| Field                           | Type    | Nullable | Description                                                                                 |
| :------------------------------ | :------ | :------- | :------------------------------------------------------------------------------------------ |
| `data.products[].productId`     | string  | no       | Product ID.                                                                                 |
| `data.products[].productType`   | string  | no       | `StoreModuleMembership`, `StoreModuleCreditPack`, `StoreModuleDay`, or `StoreModuleSingle`. |
| `data.products[].name`          | string  | no       | Product name.                                                                               |
| `data.products[].description`   | string  | no       | Product description.                                                                        |
| `data.products[].price`         | number  | yes      | Stored price including VAT.                                                                 |
| `data.products[].currency`      | string  | no       | Currency code.                                                                              |
| `data.products[].vatPercentage` | number  | yes      | Stored VAT percentage.                                                                      |
| `data.products[].visibility`    | string  | no       | One of the visibility enum values below.                                                    |
| `data.products[].isArchived`    | boolean | no       | `true` when the catalog item is soft-archived.                                              |
| `data.products[].storeType`     | string  | no       | Human product family label.                                                                 |
| `data.products[].trial`         | boolean | no       | Mirrors `trialCheck`.                                                                       |
| `data.products[].createdAt`     | string  | yes      | Creation timestamp.                                                                         |
| `data.products[].updatedAt`     | string  | yes      | Last update timestamp.                                                                      |

Product-specific response fields:

| Field                            | Type    | Nullable | Product type            | Description                                                                                           |
| :------------------------------- | :------ | :------- | :---------------------- | :---------------------------------------------------------------------------------------------------- |
| `period`                         | string  | no       | Membership              | Billing period.                                                                                       |
| `credits`                        | integer | yes      | Membership, credit pack | Membership credit amount or credit-pack amount.                                                       |
| `creditCalendar`                 | string  | no       | Membership              | Legacy credit allocation cadence.                                                                     |
| `creditValidity.value`           | number  | yes      | Membership, credit pack | Validity amount for unused credits. Membership values are whole numbers; credit packs may be decimal. |
| `creditValidity.unit`            | string  | no       | Membership, credit pack | `Days`, `Weeks`, `Months`, `Years`, or empty string.                                                  |
| `contractPeriod.value`           | integer | yes      | Membership              | Contract period value.                                                                                |
| `contractPeriod.unit`            | string  | no       | Membership              | Contract period unit.                                                                                 |
| `autoRenew`                      | boolean | no       | Membership              | Auto-renew flag.                                                                                      |
| `cancellationNoticePeriod`       | string  | no       | Membership              | Cancellation notice period.                                                                           |
| `assignCredits`                  | boolean | no       | Membership              | Whether membership credit assignment is enabled.                                                      |
| `allowNegativeCredits`           | boolean | no       | Membership              | Negative-credit policy.                                                                               |
| `maxNegativeCredits`             | integer | yes      | Membership              | Maximum negative credits.                                                                             |
| `creditPools[]`                  | array   | no       | Membership              | Configured public credit pool summaries.                                                              |
| `creditPools[].key`              | string  | no       | Membership              | Credit pool key.                                                                                      |
| `creditPools[].name`             | string  | no       | Membership              | Credit pool name.                                                                                     |
| `creditPools[].credits`          | number  | yes      | Membership              | Credits allocated by this pool.                                                                       |
| `creditPools[].scheduleInterval` | number  | yes      | Membership              | Credit schedule interval.                                                                             |
| `creditPools[].scheduleUnit`     | string  | no       | Membership              | Credit schedule unit.                                                                                 |
| `creditPools[].mode`             | string  | no       | Membership              | Credit pool mode.                                                                                     |
| `daysUsable`                     | integer | yes      | Day/week pass           | Number of days usable after activation.                                                               |
| `validityAfterPurchase.value`    | integer | yes      | Day/week pass           | Purchase validity amount.                                                                             |
| `validityAfterPurchase.unit`     | string  | no       | Day/week pass           | `Days`, `Weeks`, `Months`, `Years`, or empty string.                                                  |

Single-product response fields:

| Field                                         | Type    | Nullable | Product type            | Description                                                                                           |
| :-------------------------------------------- | :------ | :------- | :---------------------- | :---------------------------------------------------------------------------------------------------- |
| `data.product.productId`                      | string  | no       | all                     | Product ID.                                                                                           |
| `data.product.productType`                    | string  | no       | all                     | `StoreModuleMembership`, `StoreModuleCreditPack`, `StoreModuleDay`, or `StoreModuleSingle`.           |
| `data.product.name`                           | string  | no       | all                     | Product name.                                                                                         |
| `data.product.description`                    | string  | no       | all                     | Product description.                                                                                  |
| `data.product.price`                          | number  | yes      | all                     | Stored price including VAT.                                                                           |
| `data.product.currency`                       | string  | no       | all                     | Currency code.                                                                                        |
| `data.product.vatPercentage`                  | number  | yes      | all                     | Stored VAT percentage.                                                                                |
| `data.product.visibility`                     | string  | no       | all                     | Product visibility enum value.                                                                        |
| `data.product.isArchived`                     | boolean | no       | all                     | `true` when the catalog item is soft-archived.                                                        |
| `data.product.storeType`                      | string  | no       | all                     | Human product family label.                                                                           |
| `data.product.trial`                          | boolean | no       | all                     | Mirrors `trialCheck`.                                                                                 |
| `data.product.createdAt`                      | string  | yes      | all                     | Creation timestamp.                                                                                   |
| `data.product.updatedAt`                      | string  | yes      | all                     | Last update timestamp.                                                                                |
| `data.product.period`                         | string  | no       | Membership              | Billing period.                                                                                       |
| `data.product.credits`                        | integer | yes      | Membership, credit pack | Membership credit amount or credit-pack amount.                                                       |
| `data.product.creditCalendar`                 | string  | no       | Membership              | Legacy credit allocation cadence.                                                                     |
| `data.product.creditValidity.value`           | number  | yes      | Membership, credit pack | Validity amount for unused credits. Membership values are whole numbers; credit packs may be decimal. |
| `data.product.creditValidity.unit`            | string  | no       | Membership, credit pack | `Days`, `Weeks`, `Months`, `Years`, or empty string.                                                  |
| `data.product.contractPeriod.value`           | integer | yes      | Membership              | Contract period value.                                                                                |
| `data.product.contractPeriod.unit`            | string  | no       | Membership              | Contract period unit.                                                                                 |
| `data.product.autoRenew`                      | boolean | no       | Membership              | Auto-renew flag.                                                                                      |
| `data.product.cancellationNoticePeriod`       | string  | no       | Membership              | Cancellation notice period.                                                                           |
| `data.product.assignCredits`                  | boolean | no       | Membership              | Whether membership credit assignment is enabled.                                                      |
| `data.product.allowNegativeCredits`           | boolean | no       | Membership              | Negative-credit policy.                                                                               |
| `data.product.maxNegativeCredits`             | integer | yes      | Membership              | Maximum negative credits.                                                                             |
| `data.product.creditPools[].key`              | string  | no       | Membership              | Credit pool key.                                                                                      |
| `data.product.creditPools[].name`             | string  | no       | Membership              | Credit pool name.                                                                                     |
| `data.product.creditPools[].credits`          | number  | yes      | Membership              | Credits allocated by this pool.                                                                       |
| `data.product.creditPools[].scheduleInterval` | number  | yes      | Membership              | Credit schedule interval.                                                                             |
| `data.product.creditPools[].scheduleUnit`     | string  | no       | Membership              | Credit schedule unit.                                                                                 |
| `data.product.creditPools[].mode`             | string  | no       | Membership              | Credit pool mode.                                                                                     |
| `data.product.daysUsable`                     | integer | yes      | Day/week pass           | Number of days usable after activation.                                                               |
| `data.product.validityAfterPurchase.value`    | integer | yes      | Day/week pass           | Purchase validity amount.                                                                             |
| `data.product.validityAfterPurchase.unit`     | string  | no       | Day/week pass           | `Days`, `Weeks`, `Months`, `Years`, or empty string.                                                  |

Not exposed: `companyId`, arbitrary `coachId`, `coverImage`, `slug`,
`popularity`, ledger/accounting IDs, default billing profile IDs,
availability tags, linked event templates, raw access config, service history,
provider snapshots, image upload state, or any payment-provider credentials.

## Product catalog endpoints

Required scopes:

* Reads: `finance_products:read`
* Creates, updates, and archives: `finance_products:write`

Write requests require an `Idempotency-Key` header.

| Product family  | List/create                                         | Get/update                                                       | Archive                                                           | Linked clients                                                   |
| :-------------- | :-------------------------------------------------- | :--------------------------------------------------------------- | :---------------------------------------------------------------- | :--------------------------------------------------------------- |
| Memberships     | `GET`/`POST /public/v1/finance/membership-products` | `GET`/`PATCH /public/v1/finance/membership-products/{productId}` | `POST /public/v1/finance/membership-products/{productId}/archive` | `GET /public/v1/finance/membership-products/{productId}/clients` |
| Credit packs    | `GET`/`POST /public/v1/finance/credit-packs`        | `GET`/`PATCH /public/v1/finance/credit-packs/{productId}`        | `POST /public/v1/finance/credit-packs/{productId}/archive`        | `GET /public/v1/finance/credit-packs/{productId}/clients`        |
| Day/week passes | `GET`/`POST /public/v1/finance/day-passes`          | `GET`/`PATCH /public/v1/finance/day-passes/{productId}`          | `POST /public/v1/finance/day-passes/{productId}/archive`          | `GET /public/v1/finance/day-passes/{productId}/clients`          |
| Single sessions | `GET`/`POST /public/v1/finance/single-sessions`     | `GET`/`PATCH /public/v1/finance/single-sessions/{productId}`     | `POST /public/v1/finance/single-sessions/{productId}/archive`     | `GET /public/v1/finance/single-sessions/{productId}/clients`     |

Typed list query parameters:

| Parameter         | Type           | Required | Rule                                          |
| :---------------- | :------------- | :------- | :-------------------------------------------- |
| `page`            | integer        | no       | Minimum `1`.                                  |
| `limit`           | integer        | no       | Default `100`, maximum `100`.                 |
| `search`          | string         | no       | Searches `name`, max 100 chars recommended.   |
| `visibility`      | string         | no       | Must be one of the visibility values below.   |
| `includeArchived` | boolean string | no       | Use `true` to include soft-archived products. |

Get supports `includeArchived=true`. Without it, archived products return not
found.

### Product catalog request fields

Common create/update fields:

| Field           | Type                     | Required   | Validation                                                                             |
| :-------------- | :----------------------- | :--------- | :------------------------------------------------------------------------------------- |
| `name`          | string                   | create yes | Trimmed, max 160 chars.                                                                |
| `description`   | string                   | no         | Trimmed, max 5000 chars.                                                               |
| `price`         | number or numeric string | no         | Minimum `0`, price includes VAT.                                                       |
| `currency`      | string                   | no         | `EUR`, `USD`, or `GBP`. Lowercase input is normalized.                                 |
| `vatPercentage` | number or numeric string | no         | Minimum `0`, maximum `100`.                                                            |
| `visibility`    | string                   | no         | `Visible`, `VisibleInStore`, `VisibleInStoreAndApp`, `VisibleInApp`, or `HideProduct`. |
| `trial`         | boolean                  | no         | Also accepts `"true"`, `"false"`, `1`, or `0`.                                         |

Membership-specific fields:

| Field                      | Type    | Required | Validation                                                                                                                                                                                                   |
| :------------------------- | :------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `period`                   | string  | no       | `Payment per month`, `Payment per week`, `Payment per 2 weeks`, `Payment per 4 weeks`, `Payment per 8 weeks`, `Payment per 2 months`, `Payment per 3 months`, `Payment per 6 months`, or `Payment per year`. |
| `credits`                  | integer | no       | Minimum `0`.                                                                                                                                                                                                 |
| `creditCalendar`           | string  | no       | Empty string, `unlimited`, `per week`, `per 2 weeks`, `per 4 weeks`, `per month`, or `custom`.                                                                                                               |
| `creditValidity.value`     | integer | no       | Minimum `0`.                                                                                                                                                                                                 |
| `creditValidity.unit`      | string  | no       | Empty string, `Days`, `Weeks`, `Months`, or `Years`.                                                                                                                                                         |
| `contractPeriod`           | integer | no       | Minimum `0`.                                                                                                                                                                                                 |
| `contractPeriodUnit`       | string  | no       | `Days`, `Weeks`, `Months`, or `Years`.                                                                                                                                                                       |
| `autoRenew`                | boolean | no       | Auto-renew default flag.                                                                                                                                                                                     |
| `cancellationNoticePeriod` | string  | no       | `No Notification`, `1 Month`, `2 Month`, or `3 Month`.                                                                                                                                                       |
| `assignCredits`            | boolean | no       | Whether membership credits are assigned.                                                                                                                                                                     |
| `allowNegativeCredits`     | boolean | no       | Whether negative credit balances are allowed.                                                                                                                                                                |
| `maxNegativeCredits`       | integer | no       | Minimum `0`.                                                                                                                                                                                                 |

Credit-pack-specific fields:

| Field                  | Type                     | Required   | Validation                              |
| :--------------------- | :----------------------- | :--------- | :-------------------------------------- |
| `credits`              | integer                  | create yes | Minimum `1`.                            |
| `creditValidity.value` | number or numeric string | create yes | Greater than `0`; decimals are allowed. |
| `creditValidity.unit`  | string                   | create yes | `Days`, `Weeks`, `Months`, or `Years`.  |

Day/week-pass-specific fields:

| Field                         | Type    | Required   | Validation                                           |
| :---------------------------- | :------ | :--------- | :--------------------------------------------------- |
| `daysUsable`                  | integer | create yes | Minimum `1`.                                         |
| `validityAfterPurchase.value` | integer | no         | Minimum `0`.                                         |
| `validityAfterPurchase.unit`  | string  | no         | Empty string, `Days`, `Weeks`, `Months`, or `Years`. |

Single-session products only use the common fields.

Rejected request fields include `companyId`, `coachId`, `addedByCoach`,
`payment`, `productPrice`, `priceExcVat`, `priceVat`, `vat`, `coverImage`,
`slug`, `storeType`, `isDeleted`, `history`, `ledgerAccountId`,
`defaultBillingProfileId`, `productAvailableTags`,
`productAvailableEventTemplates`, `accessConfig`, `createdAt`, and `updatedAt`.
Unknown fields return `PUBLIC_API_UNKNOWN_FIELDS`.

### Create product example

```bash theme={null}
curl -X POST "https://api.fitsociety.io/public/v1/finance/credit-packs" \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: credit-pack-10-credits-20260714" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "10 credits",
    "description": "Ten training credits",
    "price": 99.95,
    "currency": "EUR",
    "vatPercentage": 21,
    "credits": 10,
    "creditValidity": {
      "value": 3.5,
      "unit": "Months"
    },
    "visibility": "VisibleInStoreAndApp"
  }'
```

The response returns `data.product.*` fields from the single-product response
contract on this page, plus:

| Field                            | Type    | Nullable | Description                                            |
| :------------------------------- | :------ | :------- | :----------------------------------------------------- |
| `data.links.totalLinkedClients`  | integer | no       | Number of client products linked to this catalog item. |
| `data.links.activeLinkedClients` | integer | no       | Linked client products with active-like statuses.      |

### Update product example

```bash theme={null}
curl -X PATCH "https://api.fitsociety.io/public/v1/finance/day-passes/{productId}" \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: day-pass-update-20260714" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Weekly pass",
    "daysUsable": 7,
    "validityAfterPurchase": {
      "value": 30,
      "unit": "Days"
    }
  }'
```

Patch requests are partial. An empty patch returns
`PUBLIC_API_NO_FIELDS_TO_UPDATE`.

### Archive product example

```bash theme={null}
curl -X POST "https://api.fitsociety.io/public/v1/finance/single-sessions/{productId}/archive" \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: single-session-archive-20260714"
```

Archiving is a soft archive. It sets `isArchived: true` in the Public API DTO
and does not remove existing client products, subscriptions, invoices,
payments, or credit history. Empty request bodies are accepted.

Archive response fields:

| Field                            | Type    | Nullable | Description                               |
| :------------------------------- | :------ | :------- | :---------------------------------------- |
| `data.archived`                  | boolean | no       | Always `true` when the archive succeeded. |
| `data.product.isArchived`        | boolean | no       | `true` after the soft archive.            |
| `data.links.totalLinkedClients`  | integer | no       | Linked client product count.              |
| `data.links.activeLinkedClients` | integer | no       | Active-like linked client product count.  |

## Product linked clients

```http theme={null}
GET /public/v1/finance/credit-packs/{productId}/clients
Authorization: Bearer <access_token>
```

Required scope: `finance_products:read`

Use the matching path for memberships, day/week passes, or single sessions.

Query parameters:

| Parameter    | Type           | Required | Rule                                                                                                                             |
| :----------- | :------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------- |
| `page`       | integer        | no       | Minimum `1`.                                                                                                                     |
| `limit`      | integer        | no       | Default `100`, maximum `100`.                                                                                                    |
| `activeOnly` | boolean string | no       | Use `true` for active-like statuses only.                                                                                        |
| `status`     | string         | no       | `Active`, `Pending`, `Paused`, `Cancelled`, `Failed`, `Expired`, `Open`, `Completed`, or `Paid`. Ignored when `activeOnly=true`. |

Response fields:

| Field                                   | Type    | Nullable | Description                           |
| :-------------------------------------- | :------ | :------- | :------------------------------------ |
| `data.page`                             | integer | no       | Current page.                         |
| `data.limit`                            | integer | no       | Page size after cap.                  |
| `data.total`                            | integer | no       | Total linked client products.         |
| `data.totalPages`                       | integer | no       | Total pages.                          |
| `data.hasNextPage`                      | boolean | no       | Next page availability.               |
| `data.hasPrevPage`                      | boolean | no       | Previous page availability.           |
| `data.clientProducts[].clientProductId` | string  | no       | Assigned product document ID.         |
| `data.clientProducts[].client.clientId` | string  | no       | Client ID.                            |
| `data.clientProducts[].client.name`     | string  | no       | Client display name.                  |
| `data.clientProducts[].productId`       | string  | no       | Source product ID.                    |
| `data.clientProducts[].subscriptionId`  | string  | yes      | Linked subscription ID.               |
| `data.clientProducts[].productType`     | string  | no       | Source product model type.            |
| `data.clientProducts[].productName`     | string  | no       | Stored client product name.           |
| `data.clientProducts[].productPrice`    | string  | no       | Stored client product price snapshot. |
| `data.clientProducts[].currency`        | string  | no       | Currency.                             |
| `data.clientProducts[].paymentMethod`   | string  | no       | Payment method.                       |
| `data.clientProducts[].status`          | string  | no       | Client product status.                |
| `data.clientProducts[].startDate`       | string  | yes      | Start date.                           |
| `data.clientProducts[].createdAt`       | string  | yes      | Creation timestamp.                   |
| `data.clientProducts[].updatedAt`       | string  | yes      | Last update timestamp.                |

Not exposed: client email, phone, address, birth date, health data, billing
profile snapshots, payment provider snapshots, benefit snapshots, legal
signatures, agreement document snapshots, provider transaction IDs, SEPA IDs,
idempotency keys, internal notes, email messages, or history arrays.

## List assigned client products

```http theme={null}
GET /public/v1/finance/clients/{clientId}/products
Authorization: Bearer <access_token>
```

Required scope: `finance_products:read`

Response fields:

| Field                                                        | Type    | Nullable | Description                            |
| :----------------------------------------------------------- | :------ | :------- | :------------------------------------- |
| `data.page`                                                  | integer | no       | Current page.                          |
| `data.limit`                                                 | integer | no       | Page size after cap.                   |
| `data.total`                                                 | integer | no       | Total assigned client products.        |
| `data.totalPages`                                            | integer | no       | Total pages.                           |
| `data.hasNextPage`                                           | boolean | no       | Next page availability.                |
| `data.hasPrevPage`                                           | boolean | no       | Previous page availability.            |
| `data.clientProducts[].clientProductId`                      | string  | no       | Assigned product ID.                   |
| `data.clientProducts[].clientId`                             | string  | no       | Client ID.                             |
| `data.clientProducts[].productId`                            | string  | yes      | Source product ID.                     |
| `data.clientProducts[].subscriptionId`                       | string  | yes      | Linked subscription ID.                |
| `data.clientProducts[].productType`                          | string  | no       | Product type.                          |
| `data.clientProducts[].productName`                          | string  | no       | Product name.                          |
| `data.clientProducts[].productPrice`                         | string  | no       | Stored client product price as stored. |
| `data.clientProducts[].currency`                             | string  | no       | Currency.                              |
| `data.clientProducts[].paymentMethod`                        | string  | no       | Payment method.                        |
| `data.clientProducts[].status`                               | string  | no       | Client product status.                 |
| `data.clientProducts[].startDate`                            | string  | yes      | Start date.                            |
| `data.clientProducts[].nextCreditAllocationDate`             | string  | yes      | Next credit allocation date.           |
| `data.clientProducts[].cancellationRequest`                  | object  | yes      | Public cancellation summary.           |
| `data.clientProducts[].cancellationRequest.requestedAt`      | string  | yes      | Cancellation request timestamp.        |
| `data.clientProducts[].cancellationRequest.effectiveTiming`  | string  | no       | Requested cancellation timing.         |
| `data.clientProducts[].cancellationRequest.requestedEndDate` | string  | yes      | Requested end date.                    |
| `data.clientProducts[].cancellationRequest.resolvedEndDate`  | string  | yes      | Resolved end date when known.          |
| `data.clientProducts[].cancellationRequest.reasonCode`       | string  | no       | Public reason code.                    |
| `data.clientProducts[].cancellationRequest.reasonLabel`      | string  | no       | Public reason label.                   |
| `data.clientProducts[].cancellationRequest.reasonText`       | string  | no       | Public reason text.                    |
| `data.clientProducts[].pauseRequest`                         | object  | yes      | Public pause summary.                  |
| `data.clientProducts[].pauseRequest.requestedAt`             | string  | yes      | Pause request timestamp.               |
| `data.clientProducts[].pauseRequest.pauseStartsAt`           | string  | yes      | Requested pause start.                 |
| `data.clientProducts[].pauseRequest.pausedUntil`             | string  | yes      | Requested pause end.                   |
| `data.clientProducts[].pauseRequest.accessDuringPause`       | string  | no       | Access policy during pause.            |
| `data.clientProducts[].pauseRequest.reasonCode`              | string  | no       | Public reason code.                    |
| `data.clientProducts[].pauseRequest.reasonLabel`             | string  | no       | Public reason label.                   |
| `data.clientProducts[].pauseRequest.reasonText`              | string  | no       | Public reason text.                    |
| `data.clientProducts[].createdAt`                            | string  | yes      | Creation timestamp.                    |
| `data.clientProducts[].updatedAt`                            | string  | yes      | Last update timestamp.                 |

Not exposed: billing snapshots, payment-provider snapshots, legal signatures,
agreement documents, payment transaction IDs, idempotency keys, SEPA IDs,
internal notes, email message bodies, or history arrays.

## Get assigned client product

```http theme={null}
GET /public/v1/finance/clients/{clientId}/products/{clientProductId}
Authorization: Bearer <access_token>
```

Required scope: `finance_products:read`

Response fields:

| Field                                                     | Type   | Nullable | Description                            |
| :-------------------------------------------------------- | :----- | :------- | :------------------------------------- |
| `data.clientProduct.clientProductId`                      | string | no       | Assigned product ID.                   |
| `data.clientProduct.clientId`                             | string | no       | Client ID.                             |
| `data.clientProduct.productId`                            | string | yes      | Source product ID.                     |
| `data.clientProduct.subscriptionId`                       | string | yes      | Linked subscription ID.                |
| `data.clientProduct.productType`                          | string | no       | Product type.                          |
| `data.clientProduct.productName`                          | string | no       | Product name.                          |
| `data.clientProduct.productPrice`                         | string | no       | Stored client product price as stored. |
| `data.clientProduct.currency`                             | string | no       | Currency.                              |
| `data.clientProduct.paymentMethod`                        | string | no       | Payment method.                        |
| `data.clientProduct.status`                               | string | no       | Client product status.                 |
| `data.clientProduct.startDate`                            | string | yes      | Start date.                            |
| `data.clientProduct.nextCreditAllocationDate`             | string | yes      | Next credit allocation date.           |
| `data.clientProduct.cancellationRequest.requestedAt`      | string | yes      | Cancellation request timestamp.        |
| `data.clientProduct.cancellationRequest.effectiveTiming`  | string | no       | Requested cancellation timing.         |
| `data.clientProduct.cancellationRequest.requestedEndDate` | string | yes      | Requested end date.                    |
| `data.clientProduct.cancellationRequest.resolvedEndDate`  | string | yes      | Resolved end date when known.          |
| `data.clientProduct.cancellationRequest.reasonCode`       | string | no       | Public reason code.                    |
| `data.clientProduct.cancellationRequest.reasonLabel`      | string | no       | Public reason label.                   |
| `data.clientProduct.cancellationRequest.reasonText`       | string | no       | Public reason text.                    |
| `data.clientProduct.pauseRequest.requestedAt`             | string | yes      | Pause request timestamp.               |
| `data.clientProduct.pauseRequest.pauseStartsAt`           | string | yes      | Requested pause start.                 |
| `data.clientProduct.pauseRequest.pausedUntil`             | string | yes      | Requested pause end.                   |
| `data.clientProduct.pauseRequest.accessDuringPause`       | string | no       | Access policy during pause.            |
| `data.clientProduct.pauseRequest.reasonCode`              | string | no       | Public reason code.                    |
| `data.clientProduct.pauseRequest.reasonLabel`             | string | no       | Public reason label.                   |
| `data.clientProduct.pauseRequest.reasonText`              | string | no       | Public reason text.                    |
| `data.clientProduct.createdAt`                            | string | yes      | Creation timestamp.                    |
| `data.clientProduct.updatedAt`                            | string | yes      | Last update timestamp.                 |

## List subscriptions

```http theme={null}
GET /public/v1/finance/subscriptions?clientId={clientId}
Authorization: Bearer <access_token>
```

Required scope: `finance_subscriptions:read`

Query parameters:

| Parameter  | Type    | Required | Rule                                                                                     |
| :--------- | :------ | :------- | :--------------------------------------------------------------------------------------- |
| `page`     | integer | no       | Minimum `1`.                                                                             |
| `limit`    | integer | no       | Default `100`, maximum `100`.                                                            |
| `status`   | string  | no       | `Active`, `Pending`, `Paused`, `Cancelled`, `Failed`, `Expired`, `Open`, or `Completed`. |
| `clientId` | string  | no       | Must belong to the authenticated company.                                                |
| `search`   | string  | no       | Max 100 chars; searches client fields.                                                   |

Response fields:

| Field                                    | Type    | Nullable | Description                         |
| :--------------------------------------- | :------ | :------- | :---------------------------------- |
| `data.page`                              | integer | no       | Current page.                       |
| `data.limit`                             | integer | no       | Page size after cap.                |
| `data.total`                             | integer | no       | Total matching subscriptions.       |
| `data.totalPages`                        | integer | no       | Total pages.                        |
| `data.hasNextPage`                       | boolean | no       | Next page availability.             |
| `data.hasPrevPage`                       | boolean | no       | Previous page availability.         |
| `data.subscriptions[].subscriptionId`    | string  | no       | Subscription ID.                    |
| `data.subscriptions[].status`            | string  | no       | Subscription status.                |
| `data.subscriptions[].startDate`         | string  | yes      | Start date.                         |
| `data.subscriptions[].endDate`           | string  | yes      | End date.                           |
| `data.subscriptions[].nextBillingDate`   | string  | yes      | Next billing date.                  |
| `data.subscriptions[].client.name`       | string  | no       | Client display name when available. |
| `data.subscriptions[].product.productId` | string  | no       | Membership product ID.              |
| `data.subscriptions[].product.name`      | string  | no       | Product name.                       |
| `data.subscriptions[].product.price`     | number  | yes      | Product price.                      |
| `data.subscriptions[].product.currency`  | string  | no       | Currency.                           |
| `data.subscriptions[].product.period`    | string  | no       | Billing period.                     |

## Get subscription

```http theme={null}
GET /public/v1/finance/subscriptions/{subscriptionId}
Authorization: Bearer <access_token>
```

Required scope: `finance_subscriptions:read`

Response fields:

| Field                                              | Type    | Nullable | Description                         |
| :------------------------------------------------- | :------ | :------- | :---------------------------------- |
| `data.subscription.subscriptionId`                 | string  | no       | Subscription ID.                    |
| `data.subscription.status`                         | string  | no       | Subscription status.                |
| `data.subscription.paymentMethod`                  | string  | no       | Payment method.                     |
| `data.subscription.startDate`                      | string  | yes      | Start date.                         |
| `data.subscription.endDate`                        | string  | yes      | End date.                           |
| `data.subscription.nextBillingDate`                | string  | yes      | Next billing date.                  |
| `data.subscription.lastPaymentDate`                | string  | yes      | Last payment date.                  |
| `data.subscription.pausedUntil`                    | string  | yes      | Pause end date.                     |
| `data.subscription.autoRenew`                      | boolean | no       | Auto-renew flag.                    |
| `data.subscription.client.clientId`                | string  | no       | Client ID.                          |
| `data.subscription.client.name`                    | string  | no       | Client display name when available. |
| `data.subscription.product.productId`              | string  | no       | Membership product ID.              |
| `data.subscription.product.name`                   | string  | no       | Product name.                       |
| `data.subscription.product.price`                  | number  | yes      | Product price.                      |
| `data.subscription.product.currency`               | string  | no       | Currency.                           |
| `data.subscription.product.period`                 | string  | no       | Billing period.                     |
| `data.subscription.extraServices[]`                | array   | no       | Active extra services.              |
| `data.subscription.extraServices[].name`           | string  | no       | Service name.                       |
| `data.subscription.extraServices[].price`          | number  | yes      | Service price.                      |
| `data.subscription.extraServices[].quantity`       | number  | yes      | Quantity.                           |
| `data.subscription.extraServices[].vatPercentage`  | number  | yes      | VAT percentage.                     |
| `data.subscription.extraServices[].adjustmentType` | string  | no       | Charge/discount type.               |
| `data.subscription.oneTimeCharges[]`               | array   | no       | One-time charges.                   |
| `data.subscription.oneTimeCharges[].name`          | string  | no       | Charge name.                        |
| `data.subscription.oneTimeCharges[].price`         | number  | yes      | Charge price.                       |
| `data.subscription.oneTimeCharges[].quantity`      | number  | yes      | Quantity.                           |
| `data.subscription.oneTimeCharges[].status`        | string  | no       | Charge status.                      |
| `data.subscription.oneTimeCharges[].chargeOn`      | string  | no       | Charge timing.                      |
| `data.subscription.oneTimeCharges[].isDeposit`     | boolean | no       | Deposit flag.                       |

## Request subscription pause

```http theme={null}
POST /public/v1/finance/subscriptions/{subscriptionId}/pause-request
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_subscription_actions:write`

This endpoint creates a membership action request for review. It does not
directly pause billing, change subscription status, allocate credits, or mutate
payment-provider state.

Request body:

| Field                           | Type          | Required | Rule                                                    |
| :------------------------------ | :------------ | :------- | :------------------------------------------------------ |
| `pauseStartsAt`                 | ISO date-time | no       | Requested pause start.                                  |
| `pausedUntil`                   | ISO date-time | yes      | Requested pause end.                                    |
| `reason`                        | string        | no       | Request reason.                                         |
| `accessDuringPause`             | string        | no       | Access policy accepted by the membership action helper. |
| `blockCreditsDuringPause`       | boolean       | no       | Whether credits should be blocked during pause.         |
| `shiftCreditExpiryDates`        | boolean       | no       | Whether credit expiry should shift.                     |
| `shiftNextCreditAllocationDate` | boolean       | no       | Whether next allocation should shift.                   |

Response fields:

| Field                                                    | Type   | Nullable | Description                               |
| :------------------------------------------------------- | :----- | :------- | :---------------------------------------- |
| `data.membershipActionRequest.membershipActionRequestId` | string | no       | Action request ID.                        |
| `data.membershipActionRequest.action`                    | string | no       | Requested action.                         |
| `data.membershipActionRequest.status`                    | string | no       | Review status.                            |
| `data.membershipActionRequest.clientId`                  | string | no       | Client ID.                                |
| `data.membershipActionRequest.subscriptionId`            | string | no       | Subscription ID.                          |
| `data.membershipActionRequest.clientProductId`           | string | no       | Client product ID.                        |
| `data.membershipActionRequest.membershipId`              | string | no       | Membership product ID.                    |
| `data.membershipActionRequest.targetMembershipId`        | string | no       | Target membership ID when applicable.     |
| `data.membershipActionRequest.requestedAt`               | string | yes      | Request creation timestamp.               |
| `data.membershipActionRequest.reviewedAt`                | string | yes      | Review timestamp.                         |
| `data.membershipActionRequest.appliedAt`                 | string | yes      | Applied timestamp when processed.         |
| `data.membershipActionRequest.failedAt`                  | string | yes      | Failure timestamp when processing failed. |
| `data.membershipActionRequest.reviewMessage`             | string | no       | Public review message.                    |
| `data.membershipActionRequest.errorCode`                 | string | no       | Public error code when failed.            |

## Request subscription cancellation

```http theme={null}
POST /public/v1/finance/subscriptions/{subscriptionId}/cancel-request
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_subscription_actions:write`

This endpoint creates a membership action request for review. It does not
directly cancel the subscription.

Request body:

| Field              | Type          | Required    | Rule                                                     |
| :----------------- | :------------ | :---------- | :------------------------------------------------------- |
| `effectiveTiming`  | string        | yes         | `immediate`, `nextTerm`, `afterDays`, or `nextMonth`.    |
| `requestedEndDate` | ISO date-time | conditional | Required by timing modes that need an explicit end date. |
| `reasonCode`       | string        | no          | Public reason code.                                      |
| `reason`           | string        | no          | Request reason.                                          |

Response fields:

| Field                                                    | Type   | Nullable | Description                               |
| :------------------------------------------------------- | :----- | :------- | :---------------------------------------- |
| `data.membershipActionRequest.membershipActionRequestId` | string | no       | Action request ID.                        |
| `data.membershipActionRequest.action`                    | string | no       | `cancel`.                                 |
| `data.membershipActionRequest.status`                    | string | no       | Review status.                            |
| `data.membershipActionRequest.clientId`                  | string | no       | Client ID.                                |
| `data.membershipActionRequest.subscriptionId`            | string | no       | Subscription ID.                          |
| `data.membershipActionRequest.clientProductId`           | string | no       | Client product ID.                        |
| `data.membershipActionRequest.membershipId`              | string | no       | Membership product ID.                    |
| `data.membershipActionRequest.targetMembershipId`        | string | no       | Target membership ID when applicable.     |
| `data.membershipActionRequest.requestedAt`               | string | yes      | Request creation timestamp.               |
| `data.membershipActionRequest.reviewedAt`                | string | yes      | Review timestamp.                         |
| `data.membershipActionRequest.appliedAt`                 | string | yes      | Applied timestamp when processed.         |
| `data.membershipActionRequest.failedAt`                  | string | yes      | Failure timestamp when processing failed. |
| `data.membershipActionRequest.reviewMessage`             | string | no       | Public review message.                    |
| `data.membershipActionRequest.errorCode`                 | string | no       | Public error code when failed.            |

## Assign membership

```http theme={null}
POST /public/v1/finance/clients/{clientId}/memberships
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_memberships:write`

Request body:

| Field                   | Type    | Required | Rule                                                                            |
| :---------------------- | :------ | :------- | :------------------------------------------------------------------------------ |
| `membershipProductId`   | string  | yes      | Existing membership product in the authenticated company.                       |
| `paymentMethod`         | string  | no       | `LinkAuto`, `LinkManual`, `SepaIncasso`, `Paid`, or `Free`. Defaults to `Free`. |
| `startDate`             | date    | no       | `YYYY-MM-DD`, cannot be in the past.                                            |
| `billingAnchorDate`     | date    | no       | `YYYY-MM-DD`.                                                                   |
| `firstChargeDate`       | date    | no       | `YYYY-MM-DD`.                                                                   |
| `creditsStartAt`        | string  | no       | `billingAnchorDate`, `firstChargeDate`, or `startDate`.                         |
| `membershipEmailsCheck` | boolean | no       | Defaults to `true`. Required for payment-link flows.                            |
| `autoRenewCheck`        | boolean | no       | Defaults to `true`.                                                             |
| `internalNote`          | string  | no       | Stored internally.                                                              |
| `messageForEmail`       | string  | no       | Optional client email message.                                                  |
| `locationId`            | string  | no       | Payment context location.                                                       |
| `homeLocationId`        | string  | no       | Payment context home location.                                                  |
| `billingProfileId`      | string  | no       | Payment billing profile.                                                        |
| `paymentConnectionId`   | string  | no       | Payment connection.                                                             |

Response fields:

| Field                                             | Type   | Nullable | Description                    |
| :------------------------------------------------ | :----- | :------- | :----------------------------- |
| `data.subscriptionAssignment.subscriptionId`      | string | yes      | Created subscription ID.       |
| `data.subscriptionAssignment.clientProductId`     | string | yes      | Created client product ID.     |
| `data.subscriptionAssignment.billingAnchorDate`   | string | yes      | Billing anchor date.           |
| `data.subscriptionAssignment.firstChargeDate`     | string | yes      | First charge date.             |
| `data.subscriptionAssignment.currency`            | string | no       | Currency.                      |
| `data.subscriptionAssignment.creditsAllocatedNow` | number | yes      | Credits allocated immediately. |

## List client credits

```http theme={null}
GET /public/v1/finance/clients/{clientId}/credits
Authorization: Bearer <access_token>
```

Required scope: `finance_credits:read`

Query parameters:

| Parameter         | Type           | Required | Rule                                    |
| :---------------- | :------------- | :------- | :-------------------------------------- |
| `includeInactive` | boolean string | no       | Use `true` to include inactive credits. |

Response fields:

| Field                                 | Type    | Nullable | Description                      |
| :------------------------------------ | :------ | :------- | :------------------------------- |
| `data.totalAvailable`                 | number  | no       | Sum of available active credits. |
| `data.credits[].creditId`             | string  | no       | Client credit ID.                |
| `data.credits[].status`               | string  | no       | Credit status.                   |
| `data.credits[].productType`          | string  | no       | Product type.                    |
| `data.credits[].poolKey`              | string  | no       | Credit pool key.                 |
| `data.credits[].poolName`             | string  | no       | Credit pool name.                |
| `data.credits[].poolMode`             | string  | no       | Pool mode.                       |
| `data.credits[].totalCredits`         | number  | no       | Total credits.                   |
| `data.credits[].usedCredits`          | number  | no       | Used credits.                    |
| `data.credits[].availableCredits`     | number  | no       | Remaining credits.               |
| `data.credits[].startDate`            | string  | no       | Start date.                      |
| `data.credits[].expiryDate`           | string  | no       | Expiry date.                     |
| `data.credits[].allowNegativeCredits` | boolean | no       | Negative-credit policy.          |
| `data.credits[].subscriptionId`       | string  | yes      | Linked subscription ID.          |

## List credit mutations

```http theme={null}
GET /public/v1/finance/clients/{clientId}/credit-mutations
Authorization: Bearer <access_token>
```

Required scope: `finance_credits:read`

Query parameters: `page`, `limit` (max 100), `creditId`, `dateFrom`, `dateTo`
(max 366-day range).

Response fields:

| Field                             | Type    | Nullable | Description                 |
| :-------------------------------- | :------ | :------- | :-------------------------- |
| `data.page`                       | integer | no       | Current page.               |
| `data.limit`                      | integer | no       | Page size after cap.        |
| `data.total`                      | integer | no       | Total matching mutations.   |
| `data.totalPages`                 | integer | no       | Total pages.                |
| `data.hasNextPage`                | boolean | no       | Next page availability.     |
| `data.hasPrevPage`                | boolean | no       | Previous page availability. |
| `data.mutations[].mutationId`     | string  | no       | Mutation ID.                |
| `data.mutations[].creditId`       | string  | yes      | Related credit ID.          |
| `data.mutations[].mutationType`   | string  | no       | Mutation type.              |
| `data.mutations[].creditsChanged` | number  | yes      | Positive or negative delta. |
| `data.mutations[].reason`         | string  | no       | Reason text.                |
| `data.mutations[].category`       | string  | no       | Mutation category.          |
| `data.mutations[].createdByType`  | string  | no       | Actor type.                 |
| `data.mutations[].createdAt`      | string  | yes      | Creation time.              |

## Adjust credits

```http theme={null}
POST /public/v1/finance/clients/{clientId}/credits/{creditId}/adjust
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_credits:write`

Request body:

| Field       | Type   | Required | Rule                                                           |
| :---------- | :----- | :------- | :------------------------------------------------------------- |
| `operation` | string | yes      | Existing credit helper operation, for example add or subtract. |
| `credits`   | number | yes      | Positive integer amount.                                       |
| `reason`    | string | yes      | Stored mutation reason.                                        |

Response fields:

| Field                           | Type      | Nullable | Description              |
| :------------------------------ | :-------- | :------- | :----------------------- |
| `data.adjustment.creditId`      | string    | no       | Credit ID.               |
| `data.adjustment.clientId`      | string    | no       | Client ID.               |
| `data.adjustment.operation`     | string    | no       | Applied operation.       |
| `data.adjustment.credits`       | number    | no       | Credit amount.           |
| `data.adjustment.reason`        | string    | no       | Reason.                  |
| `data.adjustment.mutationIds[]` | string\[] | no       | Created mutation IDs.    |
| `data.adjustment.updatedAt`     | string    | yes      | Last mutation timestamp. |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fitsociety.io/public/v1/finance/clients/64b64c0f2f5f4c0012345678/credits/64b64c0f2f5f4c00123456c0/adjust" \
    -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    -H "Idempotency-Key: credit-adjust-goodwill-20260823" \
    -H "Content-Type: application/json" \
    -d '{
      "operation": "add",
      "credits": 2,
      "reason": "Goodwill after cancelled class"
    }'
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const creditId = "64b64c0f2f5f4c00123456c0";

  const response = await fetch(
    `https://api.fitsociety.io/public/v1/finance/clients/${clientId}/credits/${creditId}/adjust`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
        "Idempotency-Key": "credit-adjust-goodwill-20260823",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        operation: "add",
        credits: 2,
        reason: "Goodwill after cancelled class",
      }),
    },
  );

  const body = await response.json();
  console.log(body.data.adjustment);
  ```

  ```python Python theme={null}
  import os
  import requests

  client_id = "64b64c0f2f5f4c0012345678"
  credit_id = "64b64c0f2f5f4c00123456c0"

  response = requests.post(
      f"https://api.fitsociety.io/public/v1/finance/clients/{client_id}/credits/{credit_id}/adjust",
      headers={
          "Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}",
          "Idempotency-Key": "credit-adjust-goodwill-20260823",
      },
      json={
          "operation": "add",
          "credits": 2,
          "reason": "Goodwill after cancelled class",
      },
  )
  response.raise_for_status()

  body = response.json()
  print(body["data"]["adjustment"])
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": {
    "adjustment": {
      "creditId": "64b64c0f2f5f4c00123456c0",
      "clientId": "64b64c0f2f5f4c0012345678",
      "operation": "add",
      "credits": 2,
      "reason": "Goodwill after cancelled class",
      "mutationIds": ["64b64c0f2f5f4c00123456c1"],
      "updatedAt": "2026-08-23T10:00:00.000Z"
    }
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 8, "resetSeconds": 1 }
  }
}
```

## Assign product

```http theme={null}
POST /public/v1/finance/clients/{clientId}/products
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_products:write`

Supported product types: `StoreModuleCreditPack`, `StoreModuleDay`,
`StoreModuleSingle`.

Response fields:

| Field                                    | Type    | Nullable | Description                       |
| :--------------------------------------- | :------ | :------- | :-------------------------------- |
| `data.productAssignment.clientProductId` | string  | yes      | Created client product ID.        |
| `data.productAssignment.productId`       | string  | yes      | Assigned product ID.              |
| `data.productAssignment.productType`     | string  | no       | Product type.                     |
| `data.productAssignment.status`          | string  | no       | Client product status.            |
| `data.productAssignment.paymentMethod`   | string  | no       | Payment method.                   |
| `data.productAssignment.invoiceId`       | string  | yes      | Created invoice ID.               |
| `data.productAssignment.invoiceNumber`   | string  | no       | Created invoice number.           |
| `data.productAssignment.paymentLink`     | string  | yes      | Payment link only when created.   |
| `data.productAssignment.creditsDeferred` | boolean | no       | Whether credits wait for payment. |

## Revoke product

```http theme={null}
POST /public/v1/finance/clients/{clientId}/products/{clientProductId}/revoke
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `finance_products:write`

Supported revocations:

| Product type            | Behavior                           |
| :---------------------- | :--------------------------------- |
| `StoreModuleCreditPack` | Deactivates remaining credits.     |
| `StoreModuleDay`        | Marks day pass credits as expired. |

Request body:

| Field         | Type   | Required | Rule                    |
| :------------ | :----- | :------- | :---------------------- |
| `productType` | string | yes      | Supported product type. |
| `reason`      | string | yes      | Required audit reason.  |

Response fields:

| Field                              | Type   | Nullable | Product type               | Description                                                          |
| :--------------------------------- | :----- | :------- | :------------------------- | :------------------------------------------------------------------- |
| `data.revocation.creditId`         | string | no       | credit pack, day/week pass | Client credit ID affected by the revoke action.                      |
| `data.revocation.status`           | string | no       | credit pack, day/week pass | Final credit status, currently `Expired` for Public API revocations. |
| `data.revocation.totalCredits`     | number | yes      | credit pack                | Total credits before deactivation.                                   |
| `data.revocation.usedCredits`      | number | yes      | credit pack                | Used credits after deactivation.                                     |
| `data.revocation.remainingCredits` | number | yes      | credit pack                | Remaining credits after deactivation, normally `0`.                  |
| `data.revocation.expiryDate`       | string | yes      | credit pack                | Expiry date applied to the credit pack.                              |
