# GDPR

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

## POST /v1/gdpr/erase

Erase user under GDPR

Permanently anonymizes a user's profile and deletes their associated personal data, including linked identities, passkeys, sessions, and pending email verifications, to satisfy a right-to-be-forgotten request. You cannot erase your own account or the last remaining account owner. Only an account owner can do this to an owner, and you cannot do it to a person who holds a permission you do not hold. An API key is never an owner.

**Notes.**
- Irreversible. Unlike `POST /v1/users/:id/erase`, the person does not have to be deactivated first.

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

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `userId` | string | yes |  |

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

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.userId` | string |  |
| `data.erased` | 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, or in a franchise workspace, is not enough). Also `owner_required` or `outranked`: the person is an owner, or holds a permission you do not hold.

| 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 person 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 409: `cant_erase_self` or `last_owner`.

| 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: `userId` is 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/gdpr/export

Export user data (GDPR)

Assembles a data subject access request export for the whole workspace: every person, screen and piece of content, and the last 365 days of the activity log, as one JSON file with secret values withheld. The key or person must have `billing.edit` for the whole workspace. Each table is capped; `complete` is false when one was cut.

**Notes.**
- Exports the whole workspace, not one person: every person, screen and piece of content, and the last 365 days of the activity log.
- No `{ data }` envelope: the body is the export file (`Content-Disposition: attachment`).
- Credentials are never exported: their columns are kept with `null` or a `[secret; not exported]` / `[encrypted; not exported]` marker.

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

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

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `schemaVersion` | 1 |  |
| `generatedAt` | string | ISO-8601 timestamp (UTC). |
| `subject` | object |  |
| `subject.spaceId` | string | The workspace exported. |
| `subject.requestedByUserId` | string \| null | Null for an API key. |
| `limits` | object |  |
| `limits.perTable` | integer | At most this many rows per table. |
| `limits.auditEvents` | integer | At most this many activity log events. |
| `limits.auditWindowDays` | integer | Activity log events from this many days back. |
| `truncated` | object | The tables cut at the limit; empty when none. |
| `complete` | boolean | False when a table was cut at the limit. |
| `tables` | object |  |
| `tables.workspace` | object \| null | The workspace record, every column (the Screen Lock PIN hash replaced by `kioskHasGlobalPin` inside `prefs`). |
| `tables.users` | array of object |  |
| `tables.users[].id` | string |  |
| `tables.users[].name` | string |  |
| `tables.users[].email` | string |  |
| `tables.users[].title` | string \| null |  |
| `tables.users[].phone` | string \| null |  |
| `tables.users[].status` | string |  |
| `tables.users[].lastLoginAt` | string \| null |  |
| `tables.users[].emailVerifiedAt` | string \| null |  |
| `tables.users[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `tables.users[].updatedAt` | string | ISO-8601 timestamp (UTC). |
| `tables.users[].deletedAt` | string \| null |  |
| `tables.screens` | array of object | Every column; credentials withheld, and `kioskHasCustomPin` / `kioskHasRecovery` added. |
| `tables.media` | array of object |  |
| `tables.playlists` | array of object |  |
| `tables.schedules` | array of object |  |
| `tables.layouts` | array of object |  |
| `tables.creatives` | array of object |  |
| `tables.appInstances` | array of object |  |
| `tables.dataSources` | array of object | `config` is `[encrypted; not exported]` (or null). |
| `tables.banners` | array of object |  |
| `tables.auditEvents` | array of object | Newest first. |

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, or in a franchise workspace, 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/gdpr/export

Export user data (GDPR)

Same as the GET version of this operation: assembles a data subject access request export for the whole workspace as one JSON file, with secret values withheld. The key or person must have `billing.edit` for the whole workspace. Takes no request body.

**Notes.**
- Exports the whole workspace, not one person: every person, screen and piece of content, and the last 365 days of the activity log.
- No `{ data }` envelope: the body is the export file (`Content-Disposition: attachment`).
- Credentials are never exported: their columns are kept with `null` or a `[secret; not exported]` / `[encrypted; not exported]` marker.
- Takes no body; the same export as the GET.

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

```bash
curl -X POST "https://api.brixsignage.com/v1/gdpr/export" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `schemaVersion` | 1 |  |
| `generatedAt` | string | ISO-8601 timestamp (UTC). |
| `subject` | object |  |
| `subject.spaceId` | string | The workspace exported. |
| `subject.requestedByUserId` | string \| null | Null for an API key. |
| `limits` | object |  |
| `limits.perTable` | integer | At most this many rows per table. |
| `limits.auditEvents` | integer | At most this many activity log events. |
| `limits.auditWindowDays` | integer | Activity log events from this many days back. |
| `truncated` | object | The tables cut at the limit; empty when none. |
| `complete` | boolean | False when a table was cut at the limit. |
| `tables` | object |  |
| `tables.workspace` | object \| null | The workspace record, every column (the Screen Lock PIN hash replaced by `kioskHasGlobalPin` inside `prefs`). |
| `tables.users` | array of object |  |
| `tables.users[].id` | string |  |
| `tables.users[].name` | string |  |
| `tables.users[].email` | string |  |
| `tables.users[].title` | string \| null |  |
| `tables.users[].phone` | string \| null |  |
| `tables.users[].status` | string |  |
| `tables.users[].lastLoginAt` | string \| null |  |
| `tables.users[].emailVerifiedAt` | string \| null |  |
| `tables.users[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `tables.users[].updatedAt` | string | ISO-8601 timestamp (UTC). |
| `tables.users[].deletedAt` | string \| null |  |
| `tables.screens` | array of object | Every column; credentials withheld, and `kioskHasCustomPin` / `kioskHasRecovery` added. |
| `tables.media` | array of object |  |
| `tables.playlists` | array of object |  |
| `tables.schedules` | array of object |  |
| `tables.layouts` | array of object |  |
| `tables.creatives` | array of object |  |
| `tables.appInstances` | array of object |  |
| `tables.dataSources` | array of object | `config` is `[encrypted; not exported]` (or null). |
| `tables.banners` | array of object |  |
| `tables.auditEvents` | array of object | Newest first. |

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, or in a franchise workspace, 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. |
