# Audit

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

## GET /v1/audit

List activity log events

Returns the workspace's append-only activity log, newest first, with each event's actor shown by name rather than raw id, whether the actor was a person, an API key, or the system. Page with `limit` and `cursor`; `nextCursor` sits beside `data`. There are no filters: use the export for a time window. A caller limited to some locations sees only the events at those locations.

**Notes.**
- Newest first. There are no filters: page with `cursor`.
- A caller limited to some locations sees only events at those locations, so a page can hold fewer than `limit` events while `nextCursor` is still set.

Auth: Bearer token. Permission: `audit-log.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (default 200, max 500). |
| `cursor` | query | string | no | `nextCursor` of the previous page. |

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

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of AuditEvent |  |
| `data[].id` | string |  |
| `data[].at` | string | ISO-8601 timestamp (UTC). |
| `data[].workspaceId` | string |  |
| `data[].actor` | string | `user:<id>`, `apikey:<id>`, `system`, or another internal actor. |
| `data[].actorKind` | "user" \| "api-key" \| "system" |  |
| `data[].actorName` | string | The person's or key's name; `System` for the system. |
| `data[].actorEmail` | string | The person's email; `n/a` for a key; `system@brix` for the system. |
| `data[].action` | string | What happened, e.g. `screen.renamed`. |
| `data[].target` | string \| null | The id of the thing acted on. |
| `data[].detail` | string \| null |  |
| `data[].nodeId` | string \| null | The location the event belongs to; null for the workspace. |
| `data[].nodeName` | string \| null |  |
| `data[].ipAddress` | string \| null |  |
| `data[].userAgent` | string \| null |  |
| `nextCursor` | string \| null | Pass as `cursor` for the next (older) page; null on the last page. |

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: The token lacks the permission this operation needs (see `x-brix-permission`).

| 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/audit

Add activity log event

Appends one client event to the workspace's activity log, for example that a help panel was opened. The `action` must be in the `cms.` namespace, so only events of that kind can be added this way. The actor is always the caller, never taken from the request body. The activity log is append-only: events can be added but never updated or deleted. Limited to 120 events a minute.

**Notes.**
- Needs only `audit-log.view`. The actor is always the caller; `ipAddress` and `userAgent` come from the request.

Auth: Bearer token. Permission: `audit-log.view`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `action` | string | yes | Must be in the `cms.` namespace: `cms.` then lower-case letters, digits, `.`, `_` or `-` (up to 122 more characters). |
| `target` | string | no |  |
| `detail` | string | no |  |
| `nodeId` | string \| null | no | A location you can see. |

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

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | AuditEvent | One activity log event, with the actor resolved to a name. |
| `data.id` | string |  |
| `data.at` | string | ISO-8601 timestamp (UTC). |
| `data.workspaceId` | string |  |
| `data.actor` | string | `user:<id>`, `apikey:<id>`, `system`, or another internal actor. |
| `data.actorKind` | "user" \| "api-key" \| "system" |  |
| `data.actorName` | string | The person's or key's name; `System` for the system. |
| `data.actorEmail` | string | The person's email; `n/a` for a key; `system@brix` for the system. |
| `data.action` | string | What happened, e.g. `screen.renamed`. |
| `data.target` | string \| null | The id of the thing acted on. |
| `data.detail` | string \| null |  |
| `data.nodeId` | string \| null | The location the event belongs to; null for the workspace. |
| `data.nodeName` | string \| null |  |
| `data.ipAddress` | string \| null |  |
| `data.userAgent` | string \| null |  |

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: The token lacks the permission this operation needs (see `x-brix-permission`).

| 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: `action` is missing or outside the `cms.` namespace, or `nodeId` is not a location you can see.

| 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 429: More than 120 events a minute.

| 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/audit/export

Export activity log feed

Returns the activity log as a feed suitable for a log collector or SIEM tool. Unlike the regular activity log listing, results are returned oldest first, so a collector can page forward from a saved position without missing or re-reading events. Returns newline-delimited JSON by default; pass `?format=json` for a human-readable array. Use the `from` and `to` parameters for a half-open time window, and the `x-brix-next-cursor` and `x-brix-has-more` response headers to page through results.

**Notes.**
- Oldest first. The default body is NDJSON: one `AuditEvent` object per line. The next-page cursor is in the `X-Brix-Next-Cursor` header (empty on the last page) and `X-Brix-Has-More` is `1` or `0`.
- With `format=json` the body is JSON: `{ data: AuditEvent[], nextCursor: string | null }`.

Auth: Bearer token. Permission: `audit-log.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (default 500, max 1000). |
| `cursor` | query | string | no | The `X-Brix-Next-Cursor` header (or `nextCursor`) of the previous page. |
| `from` | query | string | no | ISO-8601; events at or after this time. |
| `to` | query | string | no | ISO-8601; events before this time. |
| `format` | query | "json" | no | `json` answers `{ data: AuditEvent[], nextCursor }` instead of NDJSON. |

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

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: The token lacks the permission this operation needs (see `x-brix-permission`).

| 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: `from` or `to` is not an ISO-8601 timestamp.

| 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/audit/integrity

Get activity log integrity chain

Returns the tamper-evidence chain for the workspace's activity log: a digest to record for later comparison, and the daily checkpoints behind it. Each complete UTC day of events is combined into one checkpoint linked to the day before. The current, still-open day is deliberately not yet included, since a checkpoint over data still being written would have to be recalculated.

Auth: Bearer token. Permission: `audit-log.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Checkpoints to return, newest first (default 90, max 400). |

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

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.latestDigest` | string \| null | The value to record. Null before the first day is covered. |
| `data.coveredThrough` | string \| null | The end of the last covered day. |
| `data.canonicalVersion` | string | The version of the row encoding the hashes use. |
| `data.note` | string |  |
| `data.checkpoints` | array of object |  |
| `data.checkpoints[].seq` | integer |  |
| `data.checkpoints[].periodStart` | string | ISO-8601 timestamp (UTC). |
| `data.checkpoints[].periodEnd` | string | ISO-8601 timestamp (UTC). |
| `data.checkpoints[].rowCount` | integer |  |
| `data.checkpoints[].firstEventId` | string \| null |  |
| `data.checkpoints[].lastEventId` | string \| null |  |
| `data.checkpoints[].merkleRoot` | string |  |
| `data.checkpoints[].prevDigest` | string | The previous checkpoint's digest, or `genesis`. |
| `data.checkpoints[].digest` | string |  |

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: The token lacks the permission this operation needs (see `x-brix-permission`).

| 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/audit/integrity/verify

Verify activity log integrity

Re-reads every event behind the integrity chain and recomputes it, to prove the activity log has not been altered since a digest was recorded. Returns HTTP 200 with `ok: false` when verification fails, rather than an error status, so a monitoring script can distinguish "the log was altered" from "the check itself failed". This is rate-limited because it re-reads the full log.

**Notes.**
- Answers 200 with `ok: false` when verification fails.

Auth: Bearer token. Permission: `audit-log.view`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | no | Checkpoints to check, newest first (default 90). |

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

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.checkpoints` | array of object |  |
| `data.checkpoints[].id` | string |  |
| `data.checkpoints[].seq` | integer |  |
| `data.checkpoints[].periodStart` | string | ISO-8601 timestamp (UTC). |
| `data.checkpoints[].periodEnd` | string | ISO-8601 timestamp (UTC). |
| `data.checkpoints[].storedRowCount` | integer |  |
| `data.checkpoints[].actualRowCount` | integer |  |
| `data.checkpoints[].recomputedRoot` | string \| null |  |
| `data.checkpoints[].rootMatches` | boolean |  |
| `data.checkpoints[].digestMatches` | boolean |  |
| `data.checkpoints[].chainIntact` | boolean |  |
| `data.checkpoints[].ok` | boolean |  |
| `data.ok` | boolean | False when any checkpoint failed: the log was changed. |
| `data.latestDigest` | string \| null |  |

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: The token lacks the permission this operation needs (see `x-brix-permission`).

| 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 429: More than 10 checks in 5 minutes.

| 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. |
