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

# API Reference

> Complete REST API reference for the audit-trail record surface — envelope ingestion, record and sub-resource reads, finalization, the checkpoint feed, and the versioned audit log.

The audit-trail record endpoints are mounted under `/record/*`, with the versioned audit-log read at `/v1/record/audit-log`. The base URL is `https://api.visiqlabs.com`.

These endpoints manage the record surface behind the [Audit Trail](/record/introduction): every governance decision emits a record envelope, envelopes are grouped into records, and records carry events, artifacts, and attestations. For the cryptographic verification of a single envelope, see [Decision Receipts](/record/receipts).

## Authentication

All endpoints require a Bearer credential: `Authorization: Bearer <key>`. Requests without a valid credential receive `401 Unauthorized`.

Two credential audiences exist:

* **Harness keys** — the operational credential your SDK or harness runs with. On this surface a harness key covers exactly `POST /record/envelopes` (the envelope ingestion the record SDK calls); every other endpoint here is management-only and returns `403 {"error": "harness_key_not_permitted"}` for a harness key.
* **Management keys** — general automation credentials governed by explicit permission grants. They can call every endpoint on this page. Management keys are **launching soon**: they are visible in the dashboard under **Settings → API Keys**, but creating one is not yet enabled. Until then, drive the management endpoints from the dashboard, which authenticates with your session.

Each endpoint below names the exact permission it requires. Permission-gated management keys must carry that permission; scope-gated routes additionally check the coarse `record:read` / `record:write` scope, and `full_access` satisfies everything.

### Rate limiting

Every API-key request passes a per-key sliding-window rate limit (default 600 requests per 60 seconds). Responses carry `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers; exceeding the window returns `429` with a `Retry-After` header and body `{"error": "rate_limited", "detail": "API key rate limit exceeded.", "retryAfter": <seconds>}`.

### List responses

Every list endpoint on this page returns the same envelope:

```json theme={null}
{
  "data": [ ... ],
  "total": 128,
  "page": 1,
  "limit": 50
}
```

<Note>
  The record list envelope names the page size field `limit` (echoing the request parameter), where the action and retrieval list endpoints name it `pageSize`. The values are the same; only the key differs.
</Note>

Pagination is controlled by `page` (default `1`) and `limit` (default `50`, max `200`) query parameters.

***

## Ingestion

***

### POST /record/envelopes

Ingest a record envelope. The platform upserts the parent record by `correlationId` (creating it on first sight, appending to it thereafter), appends the event with the next sequence number and a SHA-256 content hash, and inserts any attached artifacts and attestations — atomically.

**Permission:** `record_records:create` · **Scope:** `record:write`

**Request body:**

```json theme={null}
{
  "source": "action",
  "tenantId": "b2e6d0c4-5a1f-4e8b-9c3d-7f2a1b4c5d6e",
  "correlationId": "session-8f2b4c1d",
  "actor": { "type": "agent", "id": "billing-agent" },
  "event": {
    "type": "tool_call_authorized",
    "occurredAt": "2026-07-03T17:22:41.118Z",
    "payload": { "target_app": "stripe", "action": "issue_refund" }
  }
}
```

| Field              | Type                | Required | Description                                                                                                                                     |
| ------------------ | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `source`           | `string`            | Yes      | One of `execute`, `browser_proxy`, `session_events`, `action`, `isolate`, `app`, `retrieval` — determines the record's `kind` on first creation |
| `tenantId`         | `string`            | Yes      | Must equal your authenticated vendor id, or the request is `403` (1–255 chars)                                                                  |
| `correlationId`    | `string`            | Yes      | Groups events into one record (1–255 chars)                                                                                                     |
| `authorization`    | `object`            | No       | `{ type?, principal?, scope? }`                                                                                                                 |
| `actor`            | `object`            | No       | `{ type?, id?, name?, metadata? }` — `actor.id` seeds the record's `root_session_id`                                                            |
| `subject`          | `object`            | No       | `{ type?, id?, name?, metadata? }`                                                                                                              |
| `event.type`       | `string`            | Yes      | Event type (1–255 chars)                                                                                                                        |
| `event.occurredAt` | `string` (ISO 8601) | Yes      | When the event occurred                                                                                                                         |
| `event.payload`    | `object`            | Yes      | The event payload — the content the signed receipt later attests                                                                                |
| `event.signature`  | `object`            | No       | `{ algorithm, value, keyId?, signedAt? }`                                                                                                       |
| `artifacts`        | `array`             | No       | Each `{ artifactType, contentHash, mimeType?, storageRef?, metadata? }`                                                                         |
| `attestations`     | `array`             | No       | Each `{ attestationType, issuerType, issuerId, statement, hash, signature? }`                                                                   |

**Response (201):**

```json theme={null}
{
  "record_id": "9f4c1a3e-8f2b-4c1d-9e5a-2b7c8d0f1a42",
  "event_id": "4d7a2c1b-3e5f-4a8c-b1d2-9e0f3a6c7b8d"
}
```

**Status codes:** `201 Created`, `400 Bad Request` (invalid JSON or body), `401 Unauthorized`, `403 Forbidden` (harness-key on a management route, or `tenantId` does not match the authenticated vendor), `500 Internal Server Error`

***

## Records

Management endpoints — a harness key receives `403 harness_key_not_permitted` here. Each read is vendor-scoped; a record that isn't yours is `404`.

***

### GET /record/records

List records, newest first.

**Permission:** `record_records:view`

**Query parameters:**

| Parameter               | Type                                  | Description                                |
| ----------------------- | ------------------------------------- | ------------------------------------------ |
| `kind`                  | `string`                              | Filter by record kind                      |
| `status`                | `open` \| `finalized` \| `superseded` | Filter by lifecycle status                 |
| `correlationId`         | `string`                              | Filter by correlation id                   |
| `startDate` / `endDate` | ISO 8601 datetime                     | Bounds on `created_at`                     |
| `page`                  | `number`                              | Page index (default `1`)                   |
| `limit`                 | `number`                              | Records per page (default `50`, max `200`) |

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "record-uuid",
      "kind": "decision",
      "status": "open",
      "correlation_id": "session-8f2b4c1d",
      "root_session_id": "billing-agent",
      "integrity_state": "unverified",
      "created_at": "2026-07-03T10:30:00Z",
      "finalized_at": null
    }
  ],
  "total": 421,
  "page": 1,
  "limit": 50
}
```

**Status codes:** `200 OK`, `400 Bad Request` (invalid query), `401 Unauthorized`, `500 Internal Server Error`

***

### GET /record/records/:id

Get a single record by UUID.

**Permission:** `record_records:view`

**Path parameter:** `:id` — the record UUID (must be a valid UUID, else `400`)

**Response:** the record object (as in the list response).

**Status codes:** `200 OK`, `400 Bad Request` (invalid record ID), `401 Unauthorized`, `404 Not Found`, `500 Internal Server Error`

***

### PATCH /record/records/:id/finalize

Finalize a record — seal it so no further events are expected. Idempotent: finalizing an already-finalized record returns it unchanged.

**Permission:** `record_records:finalize`

**Path parameter:** `:id` — the record UUID

**Response:** the record object with `status: "finalized"` and a populated `finalized_at`.

**Status codes:** `200 OK`, `400 Bad Request` (invalid record ID), `401 Unauthorized`, `404 Not Found`, `409 Conflict` (the record is `superseded` and cannot be finalized), `500 Internal Server Error`

***

### GET /record/records/:id/events

List the events on a record, in sequence order.

**Permission:** `record_records:view`

**Query parameters:** `page` (default `1`), `limit` (default `50`, max `200`)

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "event-uuid",
      "event_type": "tool_call_authorized",
      "event_source": "action",
      "sequence_no": 1,
      "occurred_at": "2026-07-03T17:22:41.118Z",
      "payload_json": { "target_app": "stripe", "action": "issue_refund" },
      "hash": "3f1c...",
      "signature": null,
      "created_at": "2026-07-03T17:22:41.200Z"
    }
  ],
  "total": 3,
  "page": 1,
  "limit": 50
}
```

**Status codes:** `200 OK`, `400 Bad Request`, `401 Unauthorized`, `404 Not Found` (unknown or non-owned record), `500 Internal Server Error`

***

### GET /record/records/:id/artifacts

List the artifacts attached to a record.

**Permission:** `record_records:view`

**Query parameters:** `page` (default `1`), `limit` (default `50`, max `200`)

**Response:** the standard list envelope whose items carry `id`, `artifact_type`, `mime_type`, `storage_ref`, `content_hash`, `metadata_json`, and `created_at`.

**Status codes:** `200 OK`, `400 Bad Request`, `401 Unauthorized`, `404 Not Found`, `500 Internal Server Error`

***

### GET /record/records/:id/attestations

List the attestations attached to a record.

**Permission:** `record_records:view`

**Query parameters:** `page` (default `1`), `limit` (default `50`, max `200`)

**Response:** the standard list envelope whose items carry `id`, `attestation_type`, `issuer_type`, `issuer_id`, `statement_json`, `hash`, `signature`, and `created_at`.

**Status codes:** `200 OK`, `400 Bad Request`, `401 Unauthorized`, `404 Not Found`, `500 Internal Server Error`

***

## Verification

***

### GET /record/envelopes/:id/verify

Run a live, independent verification of one envelope's full attestation chain — integrity, Ed25519 leaf signature, Merkle inclusion, the AWS-KMS root signature (with a live KMS round-trip), and the RFC 3161 timestamp. The response carries a per-check result and evidence; [Audit Trail](/record/introduction#verify-a-record-in-one-call) documents the six checks.

**Permission:** `record_records:view` · **Scope:** `record:read`

**Path parameter:** `:id` — the envelope UUID (vendor-scoped; an envelope that isn't yours is `404`, indistinguishable from not-found to prevent enumeration)

**Status codes:** `200 OK`, `400 Bad Request` (invalid record ID), `401 Unauthorized`, `404 Not Found`, `500 Internal Server Error`

***

## Checkpoints

The public transparency-log checkpoints — the signed, hash-chained Merkle roots. These are **tenant-neutral**: a checkpoint exposes only roots, signatures, counts, timestamp, and witness status, never any per-tenant data.

***

### GET /record/checkpoints/:seq

Fetch one checkpoint by its 1-based batch sequence number.

**Permission:** `record_records:view` · **Scope:** `record:read`

**Path parameter:** `:seq` — the checkpoint's `batch_seq` (integer ≥ 1)

**Query parameters:** `domain` — `production` (default) or `sandbox`, selecting the transparency chain

**Response:** the tenant-free checkpoint view (roots, signatures, counts, timestamp-authority time, and the derived external-witness status).

**Status codes:** `200 OK`, `400 Bad Request` (invalid sequence), `401 Unauthorized`, `404 Not Found` (unknown sequence), `500 Internal Server Error`

<Note>
  The sibling `GET /record/checkpoints` lists checkpoints newest-first, seq-cursor paginated via `before` and `limit` (default 50, max 200), under the same permission and scope — walk it to audit the published roots and detect a split view or rewrite.
</Note>

***

## Audit Log

***

### GET /v1/record/audit-log

Query the record event log for your organization — the events across all your records, filterable by source, correlation id, event type, and date. The join is vendor-scoped server-side, so it never leaks another tenant's events.

**Permission:** `record_audit_log:view` · **Scope:** `record:read`

**Query parameters:**

| Parameter                 | Type              | Description                                                                                                    |
| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------- |
| `source`                  | `string`          | Filter by event source (`execute`, `browser_proxy`, `session_events`, `action`, `isolate`, `app`, `retrieval`) |
| `correlation_id`          | `string`          | Filter by correlation id                                                                                       |
| `event_type`              | `string`          | Filter by event type                                                                                           |
| `start_date` / `end_date` | ISO 8601 datetime | Bounds on `occurred_at`                                                                                        |
| `page`                    | `number`          | Page index (default `1`)                                                                                       |
| `limit`                   | `number`          | Records per page (default `50`, max `200`)                                                                     |

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "event-uuid",
      "record_id": "record-uuid",
      "event_type": "tool_call_authorized",
      "event_source": "action",
      "sequence_no": 1,
      "occurred_at": "2026-07-03T17:22:41.118Z",
      "payload_json": { "target_app": "stripe", "action": "issue_refund" },
      "hash": "3f1c...",
      "signature": null,
      "created_at": "2026-07-03T17:22:41.200Z"
    }
  ],
  "total": 1843,
  "page": 1,
  "limit": 50
}
```

**Status codes:** `200 OK`, `400 Bad Request` (invalid query), `401 Unauthorized`, `403 Forbidden` (insufficient permission or scope), `500 Internal Server Error`

***

## Errors & conventions

Every endpoint on this page follows the platform-wide REST conventions — the validation-error body shape, the `/v1/` vs unversioned split, and the API stability policy. See [REST API conventions](/reference/rest-conventions).
