# CRM · Merchant accounts

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /crm/merchant-customers/dashboard

**Get merchant dashboard metrics**

`operationId: MerchantCustomerController_getDashboard`

Headline figures across every merchant account — total credit extended, outstanding balance and overdue exposure.

#### Signature

```http
GET /crm/merchant-customers/dashboard () -> Cross-merchant dashboard metrics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/balances/outstanding`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Cross-merchant dashboard metrics |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/invoices/all

**Get all invoices across merchants**

`operationId: MerchantCustomerController_getAllInvoices`

Every merchant invoice in the org, for a consolidated receivables view.

#### Signature

```http
GET /crm/merchant-customers/invoices/all (page?: string, pageSize?: string, status?: string) -> Invoices across all merchants
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/reports/aging`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | string | — | Filter by invoice status. |
| `page` | query | string | — | Page number (1-based). |
| `pageSize` | query | string | — | Rows per page. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Invoices across all merchants |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/transactions/all

**Get all transactions across merchants**

`operationId: MerchantCustomerController_getAllTransactions`

Every charge and credit across all merchant accounts — the consolidated ledger.

#### Signature

```http
GET /crm/merchant-customers/transactions/all (type?: string, page?: string, pageSize?: string, startDate?: string, endDate?: string) -> Transactions across all merchants
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/transactions/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `type` | query | string | — | Filter by type. |
| `startDate` | query | string | — | Start of the period. |
| `endDate` | query | string | — | End of the period. |
| `page` | query | string | — | Page number (1-based). |
| `pageSize` | query | string | — | Rows per page. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Transactions across all merchants |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/balances/outstanding

**Get outstanding balances**

`operationId: MerchantCustomerController_getOutstandingBalances`

What every merchant currently owes — the "who owes what" list, ordered so the largest exposures are visible first.

#### Signature

```http
GET /crm/merchant-customers/balances/outstanding () -> Outstanding balance per merchant
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/reports/aging`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Outstanding balance per merchant |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/reports/aging

**Get the accounts receivable aging report**

`operationId: MerchantCustomerController_getAgingReport`

Buckets outstanding receivables by how overdue they are — the standard 30 / 60 / 90 day aging report finance uses to judge collectability.

Ages are measured against each invoice's due date, which comes from the account's `paymentTermsDays`.

#### Signature

```http
GET /crm/merchant-customers/reports/aging () -> Receivables bucketed by age
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/reminders/bulk`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Receivables bucketed by age |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/reminders/{merchantId}/{invoiceId}

**Send a payment reminder**

`operationId: MerchantCustomerController_sendPaymentReminder`

Sends a reminder to one merchant about one overdue invoice.

#### Signature

```http
POST /crm/merchant-customers/reminders/{merchantId}/{invoiceId} (merchantId: string, invoiceId: string) -> Dispatch result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Sends every time it is called — there is no once-per-day guard.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/reminders/bulk`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `merchantId` | path | string | yes | Merchant account id. |
| `invoiceId` | path | string | yes | Invoice id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Dispatch result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/reminders/bulk

**Send reminders for all overdue invoices**

`operationId: MerchantCustomerController_sendBulkPaymentReminders`

Sends a payment reminder for **every** overdue invoice across every merchant.

This mails customers in bulk with no dry-run and no per-recipient throttle. Check the aging report first to see who it will reach.

#### Signature

```http
POST /crm/merchant-customers/reminders/bulk () -> What was sent, per merchant
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Org-wide mailing. Running it twice in a day chases every merchant twice.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/reports/aging`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | What was sent, per merchant |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/statements/{merchantId}

**Generate an account statement**

`operationId: MerchantCustomerController_generateStatement`

Builds a statement of account for a merchant over a period — the charges, credits and invoices that make up their balance. Both dates are required.

#### Signature

```http
GET /crm/merchant-customers/statements/{merchantId} (merchantId: string, startDate?: string, endDate?: string) -> The account statement
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/statements/{merchantId}/send`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `merchantId` | path | string | yes | Merchant account id. |
| `startDate` | query | string | yes | Start of the period. |
| `endDate` | query | string | yes | End of the period. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The account statement |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/statements/{merchantId}/send

**Generate and send an account statement**

`operationId: MerchantCustomerController_sendStatement`

Builds the statement for a period and emails it to the merchant. Unlike the read, the period is supplied in the body.

#### Signature

```http
POST /crm/merchant-customers/statements/{merchantId}/send (merchantId: string, body) -> Dispatch result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- The read takes the period as query parameters; this one takes it in the body.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/statements/{merchantId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `merchantId` | path | string | yes | Merchant account id. |

### Request body

The period to cover.

```json
{
  "startDate": "2026-08-01",
  "endDate": "2026-08-31"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Dispatch result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/invoices/generate-all

**Generate invoices for all merchants**

`operationId: MerchantCustomerController_generateBulkInvoices`

Raises invoices across **every** merchant with unbilled charges — the month-end billing run.

This creates real invoices for real money across the whole book. Check the response for what it produced before sending them.

#### Signature

```http
POST /crm/merchant-customers/invoices/generate-all () -> The invoices generated, per merchant
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Org-wide. Invoices are created as drafts — sending them is a separate step.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/send-all`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The invoices generated, per merchant |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/invoices/send-all

**Send all draft invoices**

`operationId: MerchantCustomerController_sendAllDraftInvoices`

Sends every draft invoice across all merchants, starting the payment-terms clock on each. The companion to the bulk generation step — review the drafts before running it.

#### Signature

```http
POST /crm/merchant-customers/invoices/send-all () -> What was sent, per merchant
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Mails every merchant with a draft invoice. There is no dry run — inspect the drafts first.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/generate-all`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | What was sent, per merchant |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/detail

**List merchant accounts**

`operationId: MerchantCustomerController_getMerchants`

Lists merchant accounts with filtering, search and paging.

#### Signature

```http
GET /crm/merchant-customers/detail (status?: string, accountType?: string, search?: string, page?: integer, pageSize?: integer) -> A page of merchant accounts
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/detail/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | "pending" \| "active" \| "suspended" \| "closed" | — |  |
| `accountType` | query | string | — |  |
| `search` | query | string | — | Free-text search across company name and contact. |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of merchant accounts |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/detail

**Create a merchant account**

`operationId: MerchantCustomerController_createMerchant`

Opens a trade-credit account for an existing customer. The customer must already exist — this attaches credit terms to them rather than creating a new party.

`customer` and `companyName` are both required, and a customer can only hold one merchant account.

This endpoint returns a coded error body — `{ message, code, ... }` — rather than the platform's standard envelope. Branch on `code`; it is stable, and the message is not.

#### Signature

```http
POST /crm/merchant-customers/detail (body) -> The created merchant account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_CUSTOMER | Customer ID is required to create a merchant account | `customer` is missing. | Create the customer first, then open the account against them. |
| `404` | CUSTOMER_NOT_FOUND | Customer not found | The `customer` id does not resolve. | The body echoes the `customerId` it tried. Check it exists. |
| `409` | ACCOUNT_EXISTS | This customer already has a merchant account | The customer already holds one. | Look it up with `GET /crm/merchant-customers/by-customer/{customerId}` and update that instead. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/approve/{id}`
- `GET /crm/merchant-customers/by-customer/{customerId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The account to open.

```json
{
  "customer": "cus_4821",
  "companyName": "Analytical Engines Ltd",
  "accountType": "trade",
  "spendingLimit": 25000,
  "paymentTermsDays": 30,
  "billingCycleDay": 1,
  "autoInvoice": true,
  "invoiceTrigger": "period_end"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created merchant account |
| `400` | Customer ID is required to create a merchant account — `customer` is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Customer not found — The `customer` id does not resolve. |
| `409` | This customer already has a merchant account — The customer already holds one. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/detail/{id}

**Get a merchant account**

`operationId: MerchantCustomerController_getMerchant`

Fetches one merchant account with its limits, balance, terms and authorized users.

#### Signature

```http
GET /crm/merchant-customers/detail/{id} (id: string) -> The merchant account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/stats/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The merchant account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /crm/merchant-customers/detail/{id}

**Update a merchant account**

`operationId: MerchantCustomerController_updateMerchant`

Updates an account's general fields. Status changes, limits and payment terms each have their own endpoint, which record the change properly.

#### Signature

```http
PUT /crm/merchant-customers/detail/{id} (id: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /crm/merchant-customers/spending-limit/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

Fields to change.

```json
{
  "companyName": "Analytical Engines Ltd (EMEA)"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/by-customer/{customerId}

**Get a merchant account by customer**

`operationId: MerchantCustomerController_getMerchantByCustomer`

Finds the merchant account belonging to a customer. Use this when you hold the customer id and need to know whether they trade on credit.

#### Signature

```http
GET /crm/merchant-customers/by-customer/{customerId} (customerId: string) -> The customer's merchant account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/detail`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `customerId` | path | string | yes | Customer id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The customer's merchant account |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/approve/{id}

**Approve a merchant account**

`operationId: MerchantCustomerController_approveMerchant`

Activates a pending account so it can be charged against. This is the credit decision — nothing can be charged before it.

#### Signature

```http
POST /crm/merchant-customers/approve/{id} (id: string, body) -> The approved account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/suspend/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

Optional approval notes.

```json
{
  "notes": "Credit check passed"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The approved account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/suspend/{id}

**Suspend a merchant account**

`operationId: MerchantCustomerController_suspendMerchant`

Suspends an account so nothing further can be charged to it. The outstanding balance stands and remains collectable — suspension stops new credit, it does not forgive existing debt.

A `reason` is required, and a closed account cannot be suspended.

#### Signature

```http
POST /crm/merchant-customers/suspend/{id} (id: string, body) -> The suspended account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/reactivate/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

Why the account is being suspended.

```json
{
  "reason": "Invoices 60 days overdue"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The suspended account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/reactivate/{id}

**Reactivate a merchant account**

`operationId: MerchantCustomerController_reactivateMerchant`

Lifts a suspension and returns the account to active, restoring its ability to be charged. The counterpart to suspend; a closed account cannot be reactivated.

#### Signature

```http
POST /crm/merchant-customers/reactivate/{id} (id: string, body) -> The reactivated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/suspend/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

Optional notes.

```json
{
  "notes": "Arrears cleared"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The reactivated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/close/{id}

**Close a merchant account**

`operationId: MerchantCustomerController_closeMerchant`

Closes an account permanently. Unlike suspension there is no reopen — closure is terminal, so suspend instead where the relationship might resume.

A `reason` is required.

#### Signature

```http
POST /crm/merchant-customers/close/{id} (id: string, body) -> The closed account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Terminal. Closing does not settle an outstanding balance — collect it before or after, but closure does not write it off.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/suspend/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

Why the account is being closed.

```json
{
  "reason": "Customer ceased trading"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The closed account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /crm/merchant-customers/spending-limit/{id}

**Set a spending limit**

`operationId: MerchantCustomerController_setSpendingLimit`

Sets the maximum credit the account may carry. `availableCredit` is this figure minus the current balance, and a charge that would exceed it is refused.

Lowering the limit below the current balance does not claw anything back; it simply leaves no available credit until the balance comes down.

#### Signature

```http
PUT /crm/merchant-customers/spending-limit/{id} (id: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /crm/merchant-customers/payment-terms/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

The new limit.

```json
{
  "limit": 25000,
  "notes": "Raised after two years of clean payment history"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /crm/merchant-customers/payment-terms/{id}

**Set payment terms**

`operationId: MerchantCustomerController_setPaymentTerms`

Sets how and when the account is invoiced.

`invoiceTrigger` decides when an invoice is raised:

- `period_end` — on the billing cycle day.
- `limit_reached` — when the balance reaches the spending limit.
- `either` — whichever comes first.
- `manual` — never automatically; you raise invoices yourself.

`paymentTermsDays` sets the due date relative to the invoice, and drives the aging report.

#### Signature

```http
PUT /crm/merchant-customers/payment-terms/{id} (id: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/generate/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

The terms to apply.

```json
{
  "paymentTermsDays": 30,
  "billingCycleDay": 1,
  "autoInvoice": true,
  "invoiceTrigger": "period_end"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/users/{id}

**Add an authorized user**

`operationId: MerchantCustomerController_addUser`

Grants someone the right to charge to the account. Give them their own `spendingLimit` to cap what they can commit — a purchasing clerk and a finance director on the same account need different ceilings.

`enabledServices` restricts what they may charge for.

#### Signature

```http
POST /crm/merchant-customers/users/{id} (id: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /crm/merchant-customers/users/{id}/{customerId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

Who to authorize, and with what powers.

```json
{
  "customer": "cus_9912",
  "canApproveTransactions": false,
  "spendingLimit": 5000,
  "enabledServices": [
    "catering"
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /crm/merchant-customers/users/{id}/{customerId}

**Update an authorized user**

`operationId: MerchantCustomerController_updateUser`

Changes an authorized user's limit, approval right or permitted services.

#### Signature

```http
PUT /crm/merchant-customers/users/{id}/{customerId} (id: string, customerId: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /crm/merchant-customers/users/{id}/{customerId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `customerId` | path | string | yes | Customer id of the authorized user. |

### Request body

What to change.

```json
{
  "spendingLimit": 10000,
  "canApproveTransactions": true
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/merchant-customers/users/{id}/{customerId}

**Remove an authorized user**

`operationId: MerchantCustomerController_removeUser`

Revokes someone's right to charge to the account. Charges they already made stand — this stops future ones only.

#### Signature

```http
DELETE /crm/merchant-customers/users/{id}/{customerId} (id: string, customerId: string) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/authorized/{id}/{customerId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `customerId` | path | string | yes | Customer id of the authorized user. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/charge/{id}

**Charge to an account**

`operationId: MerchantCustomerController_chargeToAccount`

Adds a charge to the account, increasing the balance and reducing available credit. This is how a purchase goes "on account" instead of being paid at the till.

`id`, `type` and `amount` are all required, and the amount must be positive. Depending on the account's `invoiceTrigger`, a charge that reaches the spending limit can cause an invoice to be raised automatically.

#### Signature

```http
POST /crm/merchant-customers/charge/{id} (id: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Not idempotent — nothing dedupes on the transaction `id`, so a retry charges twice.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/credit/{id}`
- `GET /crm/merchant-customers/transactions/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

The charge to record.

```json
{
  "id": "TXN-99182",
  "type": "order",
  "amount": 420,
  "reference": "A7K2M9QX4",
  "description": "Catering order, 12 August"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/credit/{id}

**Credit an account**

`operationId: MerchantCustomerController_creditAccount`

Reduces the balance — a goodwill credit, a correction, or a returned order. A `reason` is required so the ledger explains itself.

#### Signature

```http
POST /crm/merchant-customers/credit/{id} (id: string, body) -> The updated account
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- A credit is not a payment — record customer payments against an invoice with the payment endpoint so the invoice closes.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/payment/{id}/{invoiceId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Request body

The credit to apply.

```json
{
  "amount": 120,
  "reason": "Returned two trays, credited"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated account |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/invoices/generate/{id}

**Generate an invoice for an account**

`operationId: MerchantCustomerController_generateInvoice`

Raises an invoice for the account's unbilled charges. Use this when the account is on `manual` invoicing, or to bill early.

#### Signature

```http
POST /crm/merchant-customers/invoices/generate/{id} (id: string) -> The generated invoice
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/send/{id}/{invoiceId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The generated invoice |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/invoices/send/{id}/{invoiceId}

**Send an invoice**

`operationId: MerchantCustomerController_sendInvoice`

Delivers a generated invoice to the merchant, moving it out of draft and starting the payment-terms clock.

#### Signature

```http
POST /crm/merchant-customers/invoices/send/{id}/{invoiceId} (id: string, invoiceId: string) -> The sent invoice
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/payment/{id}/{invoiceId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `invoiceId` | path | string | yes | Invoice id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The sent invoice |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/invoices/payment/{id}/{invoiceId}

**Record an invoice payment**

`operationId: MerchantCustomerController_recordPayment`

Records a payment against an invoice, reducing the account balance and settling the invoice when it is covered in full.

An already-paid invoice is refused rather than double-credited.

#### Signature

```http
POST /crm/merchant-customers/invoices/payment/{id}/{invoiceId} (id: string, invoiceId: string, body) -> The updated invoice
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `invoiceId` | path | string | yes | Invoice id. |

### Request body

The payment received.

```json
{
  "amount": 4200,
  "method": "bank_transfer",
  "transactionId": "BACS-99182"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated invoice |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId}

**Mark an invoice overdue**

`operationId: MerchantCustomerController_markOverdue`

Flags an invoice as overdue, bringing it into the aging report and the bulk reminder run.

#### Signature

```http
POST /crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId} (id: string, invoiceId: string) -> The updated invoice
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/reports/aging`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `invoiceId` | path | string | yes | Invoice id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated invoice |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/stats/{id}

**Get account statistics**

`operationId: MerchantCustomerController_getStats`

Aggregate figures for one account — spend, payment history and outstanding balance.

#### Signature

```http
GET /crm/merchant-customers/stats/{id} (id: string) -> Account statistics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/dashboard`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Account statistics |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/transactions/{id}

**Get an account's transaction history**

`operationId: MerchantCustomerController_getTransactions`

The charge and credit ledger for one account — what makes up the balance.

#### Signature

```http
GET /crm/merchant-customers/transactions/{id} (id: string, page?: string, pageSize?: string) -> The account's transactions
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/merchant-customers/transactions/all`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `page` | query | string | — | Page number (1-based). |
| `pageSize` | query | string | — | Rows per page. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The account's transactions |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/merchant-customers/authorized/{id}/{customerId}

**Check whether a user is authorized**

`operationId: MerchantCustomerController_checkAuthorized`

Reports whether a customer may charge to an account, and under what limits. Call this before letting someone put something on account.

#### Signature

```http
GET /crm/merchant-customers/authorized/{id}/{customerId} (id: string, customerId: string, service?: string) -> Authorization status and limits
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |
| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: "MERCHANT_NOT_FOUND"` and the `merchantId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/merchant-customers/charge/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Merchant account id. |
| `customerId` | path | string | yes | Customer id of the authorized user. |
| `service` | query | string | — | Service key. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Authorization status and limits |
| `400` | Merchant ID is required — The merchant id path segment is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Merchant account not found — No merchant account has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

