# Billing groups

Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none.

## GET /v1/billing-groups

List billing groups

Returns the workspace's billing groups (who pays for which screens), each with its current count of billable screens.

**Notes.**
- Sorted by name; not paginated. Unlike the single read, list rows have no `createdAt` / `updatedAt`.

Auth: Bearer token. Permission: `billing.view`.

```bash
curl "https://api.brixsignage.com/v1/billing-groups" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].id` | string |  |
| `data[].name` | string |  |
| `data[].financeContactName` | string \| null |  |
| `data[].financeContactEmail` | string \| null | Where this group's invoices go. |
| `data[].chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. |
| `data[].chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. |
| `data[].subscriptionStatus` | string \| null |  |
| `data[].paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. |
| `data[].screenCount` | integer | Screens billed to this group. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: You do not hold the permission for the whole workspace (a grant at one location is not enough).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/billing-groups

Create billing group

Creates a billing group with a name and finance contact. Connecting it to an account with the billing provider is a separate step; one is never created automatically here.

**Notes.**
- The group bills nothing until Brix links it to a billing account.

Auth: Bearer token. Permission: `billing.edit`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes |  |
| `financeContactName` | string | no |  |
| `financeContactEmail` | string | no | Stored in lower case. |
| `paymentTermsDays` | number | no | Net terms in days; rounded. Not range-checked on create. |

```bash
curl -X POST "https://api.brixsignage.com/v1/billing-groups" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | BillingGroup | A group of screens billed separately (its own invoice and finance contact). |
| `data.id` | string |  |
| `data.name` | string |  |
| `data.financeContactName` | string \| null |  |
| `data.financeContactEmail` | string \| null | Where this group's invoices go. |
| `data.chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. |
| `data.chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. |
| `data.subscriptionStatus` | string \| null |  |
| `data.paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. |
| `data.screenCount` | integer | Screens billed to this group. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: You do not hold the permission for the whole workspace (a grant at one location is not enough).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: `name` missing.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/billing-groups/{id}

Get billing group

Returns one billing group by id.

Auth: Bearer token. Permission: `billing.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Billing group id. |

```bash
curl "https://api.brixsignage.com/v1/billing-groups/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | BillingGroup | A group of screens billed separately (its own invoice and finance contact). |
| `data.id` | string |  |
| `data.name` | string |  |
| `data.financeContactName` | string \| null |  |
| `data.financeContactEmail` | string \| null | Where this group's invoices go. |
| `data.chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. |
| `data.chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. |
| `data.subscriptionStatus` | string \| null |  |
| `data.paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. |
| `data.screenCount` | integer | Screens billed to this group. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: You do not hold the permission for the whole workspace (a grant at one location is not enough).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such group in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## PATCH /v1/billing-groups/{id}

Update billing group

Updates a billing group's name, finance contact, or invoice terms.

**Notes.**
- `paymentTermsDays` is range-checked here (1–365) but not on create.

Auth: Bearer token. Permission: `billing.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Billing group id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no |  |
| `financeContactName` | string \| null | no |  |
| `financeContactEmail` | string \| null | no |  |
| `paymentTermsDays` | number \| null | no | 1–365, or null to use the workspace default. |

```bash
curl -X PATCH "https://api.brixsignage.com/v1/billing-groups/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | BillingGroup | A group of screens billed separately (its own invoice and finance contact). |
| `data.id` | string |  |
| `data.name` | string |  |
| `data.financeContactName` | string \| null |  |
| `data.financeContactEmail` | string \| null | Where this group's invoices go. |
| `data.chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. |
| `data.chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. |
| `data.subscriptionStatus` | string \| null |  |
| `data.paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. |
| `data.screenCount` | integer | Screens billed to this group. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: You do not hold the permission for the whole workspace (a grant at one location is not enough).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such group.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Empty `name`, or `paymentTermsDays` outside 1–365.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## DELETE /v1/billing-groups/{id}

Delete billing group

Soft-deletes a billing group. Returns 409 if any screens are still billed to it; move or remove those screens first.

Auth: Bearer token. Permission: `billing.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Billing group id. |

```bash
curl -X DELETE "https://api.brixsignage.com/v1/billing-groups/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.deleted` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: You do not hold the permission for the whole workspace (a grant at one location is not enough).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such group.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `group_in_use`: screens still bill to it; move them first (`PATCH /v1/screens/:id` `billingGroupId`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/billing-groups/{id}/screens

Assign screens to billing group

Assigns a batch of screens to a billing group. This updates the destination group, every group the screens are moving from, and the workspace subscription, so screen counts and billing stay in sync.

Auth: Bearer token. Permission: `billing.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Billing group id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `screenIds` | array of string | yes | Up to 500 are read; more are ignored. Ids that are not live screens are skipped. |

```bash
curl -X POST "https://api.brixsignage.com/v1/billing-groups/{id}/screens" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.assigned` | integer | Screens moved into the group. |
| `data.skipped` | integer | Ids not moved (unknown, or already in the group). |
| `data.screenCount` | integer |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: You lack `billing.edit` at one of the screens' locations (the response names them in `screenIds`); nothing changes. Also: You do not hold the permission for the whole workspace (a grant at one location is not enough).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such group, or none of the ids is a live screen.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: `screenIds` missing or empty.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |
