# Views

## View Model

### Fields

- **`mapping_policy_revision`** `number | null`

- **`mapping_policy_nullable_inputs`** `string[]`

- **`mapping_policy_fallback_groups`** `string[][]`

- **`mapping_policy_allow_drops`** `boolean`

- **`mapping_policy_output_bindings`** `Record<string, string[]>`

- **`mapping_generation`** `number`

- **`object`** `"view"`

- **`id`** `string`

   Unique identifier, prefixed with `viw_`.

- **`team_id`** `string`

- **`project_id`** `string`

- **`collection_id`** `string | null`

   The collection this view reads from; null when it reads from another view.

- **`collection_name`** `string | null`

   Display name of that collection.

- **`parent_view_id`** `string | null`

   The view this one is derived from; null when it reads from a collection.

- **`parent_view_name`** `string | null`

   Display name of that view.

- **`view_mode`** `string`

   Where the view's records come from: a collection, another view, or a join of several.

- **`transform_mode`** [`ViewTransformMode`](/api/views#view-transform-mode)

   Whether transform scripts are generated for new schema versions or written by you.

- **`name`** `string`

- **`hint`** `string | null`

   Plain-language guidance the generator uses when it writes transform scripts.

- **`output_schema`** `ViewOutputField[]`

   The fields this view's records contain.

- **`status`** [`ViewStatus`](/api/views#view-status)

   Whether the view is producing records, still being set up, or failed.

- **`error_message`** `string | null`

   Why the view failed, when its status is `error`.

- **`waiting_shapes_count`** `number`

   How many event shapes arrived that no transform serves yet. Their records are stored and catch
   up automatically once a script covers them; the shapes are listed by the view's deferrals
   endpoint.

- **`deleted_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the view is scheduled to be deleted; null when it is not.

- **`created_at`** [`ISODateString`](/api/views#iso-date-string)

- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)

- **`join_base_view_id`** `string | null`

   The base view on a join view; null on other views.

- **`join_base_view_name`** `string | null`

- **`join_status`** [`ViewJoinStatus | null`](/api/views#view-join-status)

- **`join_backfill_cutoff_at`** [`ISODateString | null`](/api/views#iso-date-string)

   Records after this instant are joined live.

- **`join_live_started_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the join started matching new records.

- **`join_lookup_progress`** `LookupProgress | null`

   Lookup progress keyed by clause alias.

- **`join_base_file_count`** `number | null`

   Number of base-view files to backfill.

- **`join_base_files_done`** `number | null`

   Number of base-view files already processed.

- **`join_error_message`** `string | null`

- **`join_created_at`** [`ISODateString | null`](/api/views#iso-date-string)

- **`join_updated_at`** [`ISODateString | null`](/api/views#iso-date-string)

- **`joins`** `ViewJoinClause[]`

   The views joined onto the base view, on a join view.

- **`metrics_count`** `number`

   How many metrics read from this view.

- **`derived_views_count`** `number`

   How many views are derived from this one.

- **`join_views_count`** `number`

   How many join views use this one as an input.

- **`transform_count`** `number`

   How many transforms this view has.

- **`drains_count`** `number`

   How many drains forward this view's records.

- **`backfill_file_count`** `number`

   How many files every transform on this view has to process between them.

- **`backfill_files_done`** `number`

   How many of those files they have processed.

- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the earliest of those backfills began. This is the earliest start across the whole view,
   which is what a throughput estimate has to measure elapsed time from.

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

#### ViewTransformMode

`"auto" | "manual"`

#### ViewStatus

`"empty" | "building" | "needs_transform" | "active" | "error" | "paused" | "cancelled"`

#### ViewJoinStatus

`"waiting_for_inputs" | "building_lookups" | "backfilling" | "active" | "error"`

## View Transform Model

### Fields

- **`object`** `"view_transform"`

- **`id`** `string`

   Unique identifier, prefixed with `vtr_`.

- **`view_id`** `string`

   The view this transform produces records for.

- **`transform_script`** `string | null`

   The TGL script that maps incoming records onto the view's fields; null until one is written.

- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)

   Whether the transform is running, waiting for a script, or failed.

- **`error_message`** `string | null`

   Why the transform failed, when its status is `error`.

- **`tgl_retry_count`** `number`

   How many times script generation has been retried for this transform.

- **`max_read_depth`** `number | null`

   How many levels deep into a record the script reads.

- **`evidence_notice`** `ViewEvidenceNotice | null`

   Set when a path this mapping read as empty at authoring time has since carried a value with a
   type, listing those paths as `L<depth>:<path>` read keys. Rows keep flowing; re-author the
   mapping to use the new column. Cleared when a new script is generated.

- **`backfill_status`** [`ViewTransformBackfillStatus`](/api/views#view-transform-backfill-status)

   Whether existing records have been reprocessed through this transform yet.

- **`backfill_cutoff_at`** [`ISODateString | null`](/api/views#iso-date-string)

   Records after this time are handled live rather than by the backfill.

- **`backfill_start_at`** [`ISODateString | null`](/api/views#iso-date-string)

   The earliest record the backfill will reach.

- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the backfill began.

- **`backfill_completed_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the backfill finished.

- **`backfill_file_count`** `number`

   How many files the backfill has to process.

- **`backfill_files_done`** `number`

   How many of those files it has processed.

- **`backfill_error`** `string | null`

   Why the backfill failed, when it did.

- **`assignments`** `ViewTransformAssignment[]`

   Backfill progress for each source schema version this transform covers.

- **`created_at`** [`ISODateString`](/api/views#iso-date-string)

- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

#### ViewTransformStatus

`"queued" | "analyzing" | "active" | "error" | "draft"`

#### ViewTransformBackfillStatus

`"pending" | "enqueuing" | "in_progress" | "completed" | "failed" | "cancelled"`

## View Version Model

### Fields

- **`object`** `"view_version"`

- **`id`** `string`

   Unique identifier, prefixed with `vta_`.

- **`view_id`** `string`

- **`version`** `number`

   The source schema version this row covers. Versions rise as the source's shape changes.

- **`transform_id`** `string`

   The transform whose script serves this version. Several versions can share one transform.

- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)

   Whether the transform is running, waiting for a script, or failed.

- **`error_message`** `string | null`

   Why the transform failed, when its status is `error`.

- **`evidence_notice`** `ViewEvidenceNotice | null`

   Set when a path the serving script read as empty at authoring time has since carried a value
   with a type; lists those paths as `L<depth>:<path>` read keys. Re-author the mapping to use the
   new column. Cleared when a new script is generated.

- **`script_hash`** `string | null`

   Identity of the script text. Versions sharing a hash run the identical script.

- **`script`** `string | null`

   The TGL script serving this version; null until one is written.

- **`backfill_status`** [`ViewTransformBackfillStatus`](/api/views#view-transform-backfill-status)

   Whether the records already on this schema version have been reprocessed yet.

- **`backfill_start_at`** [`ISODateString | null`](/api/views#iso-date-string)

   The earliest record the backfill will reach.

- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the backfill began.

- **`backfill_completed_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the backfill finished.

- **`backfill_file_count`** `number`

   How many files the backfill has to process for this version.

- **`backfill_files_done`** `number`

   How many of those files it has processed.

- **`backfill_error`** `string | null`

   Why the backfill failed, when it did.

- **`created_at`** [`ISODateString`](/api/views#iso-date-string)

   When the view started covering this schema version.

- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)

   When the transform serving this version last changed.

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

#### ViewTransformStatus

`"queued" | "analyzing" | "active" | "error" | "draft"`

#### ViewTransformBackfillStatus

`"pending" | "enqueuing" | "in_progress" | "completed" | "failed" | "cancelled"`

## Waiting Shape Model

### Fields

- **`episode`** `number`

- **`observation_count`** `string | null`

- **`retained_rows`** `number`

- **`first_observed_at`** [`ISODateString | null`](/api/views#iso-date-string)

- **`last_observed_at`** [`ISODateString | null`](/api/views#iso-date-string)

- **`next_action_at`** [`ISODateString | null`](/api/views#iso-date-string)

- **`hint_question`** `string | null`

- **`hint_reason`** `string | null`

- **`hint_paths`** `string[]`

- **`hint_requested_at`** `string | null`

- **`hint_mapping_generation`** `number | null`

- **`exclusion_reason`** `string | null`

- **`excluded_at`** [`ISODateString | null`](/api/views#iso-date-string)

- **`transform_id`** `string | null`

- **`object`** `"view_shape_deferral"`

- **`id`** `string`

   Unique identifier, prefixed with `vsd_`.

- **`view_id`** `string`

- **`full_signature`** `string`

   The typed signature identifying the event shape.

- **`event_count`** `number`

   Roughly how many events of the shape arrived while it was waiting; retries can count an event
   more than once.

- **`first_event_at`** [`ISODateString | null`](/api/views#iso-date-string)

   The earliest event time seen for the shape. Completeness reporting pins to this instant.

- **`first_deferred_at`** [`ISODateString`](/api/views#iso-date-string)

- **`last_deferred_at`** [`ISODateString`](/api/views#iso-date-string)

- **`resolved_at`** [`ISODateString | null`](/api/views#iso-date-string)

   When the shape became served; null while it is still waiting.

- **`reason`** [`ViewShapeDeferralReason`](/api/views#view-shape-deferral-reason)

   Why the shape is waiting: `collecting_samples` until enough evidence is available to author a
   mapping, `awaiting_script` until a transform covers it, `generation_failed` when script
   generation gave up, `catching_up` while its records backfill, `served` once resolved.

- **`schema_version`** `number | null`

   The schema version the shape belongs to. Null while no version is linked to it.

- **`created_at`** [`ISODateString`](/api/views#iso-date-string)

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

#### ViewShapeDeferralReason

`"collecting_samples" | "needs_decision" | "needs_hint" | "manual_mapping" | "generating" | "budget_limited" | "shape_overflow" | "runtime_error" | "excluded" | "recovery_pending" | "awaiting_script" | "generation_failed" | "catching_up" | "served"`

What unblocks a waiting shape.

## Shape Inspection Model

### Fields

- **`view_id`** `string`

- **`deferral_id`** `string`

- **`episode`** `number`

- **`mapping_generation`** `number`

- **`version`** `number | null`

- **`signatures`** `Record<string, string | null>`

- **`decision`** `ViewMappingReuseDecision`

- **`candidates`** `{ transform_id: string; verdict: string; reason: string; exact_paths: string[]; present_null_paths: string[]; absent_paths: string[]; incompatible_paths: string[]; substitution_paths: string[]; unknown_paths: string[]; bindings: Record<...>; }[]`

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

## Waiting Shape Hint Model

### Fields

- **`question`** `string`

- **`reason`** `string`

- **`paths`** `string[]`

   Verified JSON bracket paths such as $["account"]["id"], with literal key characters escaped.

- **`requested_at`** `string`

- **`mapping_generation`** `number`

- **`view_id`** `string`

- **`deferral_id`** `string`

- **`episode`** `number`

- **`transform_id`** `string`

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

## List Views

### Endpoint

Retrieve a list of views for a project.

```http
GET /v1/projects/:project_id/views
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

### Query Parameters

- **`order_by`** `string`
  Field the results are sorted by. Optional. Defaults to `"created_at"`. Allowed values: `"created_at"`, `"name"`, `"updated_at"`.

- **`project_id`** `string`
  Return only views in this project. Optional.

- **`collection_id`** `string`
  Return only views reading from this collection. Optional.

- **`parent_view_id`** `string`
  Return only views derived from this view. Optional.

- **`deleted_at`** [`NullableDateFilter`](/api/views#nullable-date-filter)
  Filter on deletion state. Soft-deleted views are excluded unless you ask for them. Optional.

- **`limit`** `number`
  Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.

- **`after`** `string`
  Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.

- **`before`** `string`
  Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.

- **`sort`** `string`
  Optional. Defaults to `"asc"`. Allowed values: `"asc"`, `"desc"`.

### Response

```ts
{
  message: string;
  data: View[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- `after` and `before` are mutually exclusive.

## Retrieve View

### Endpoint

Retrieve a single view.

```http
GET /v1/projects/:project_id/views/:view_id
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Response

View retrieved

```ts
{
  message: string;
  data: View;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

## Create View

### Endpoint

Create a view that transforms records from a collection or another view.

```http
POST /v1/projects/:project_id/views
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

### Request Body

- **`name`** `string` -- **Required**
  Name for the view. Minimum length: `1`. Maximum length: `128`.

- **`output_schema`** `object[]`
  The fields this view produces. Can be left until transforms define it. Optional.

- **`hint`** `string | null`
  Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.

- **`mapping_policy`** `object`
  Optional.

- **`project_id`** `string`
  Project ID. Optional when using the /projects/:project/views route. Optional.

- **`collection_id`** `string`
  Collection to derive from, when reading raw ingested records. Optional.

- **`parent_view_id`** `string`
  View to derive from, when deriving from a view. Optional.

- **`transform_script`** `string`
  TGL transform script. When set, the view skips automatic TGL generation. Optional. Minimum length: `1`. Maximum length: `65536`.

- **`transform_mode`** `string`
  Whether a new schema version that no existing transform covers gets a script generated for it, or waits for you to write one. Defaults to auto. Optional. Allowed values: `"auto"`, `"manual"`.

### Response

View created

```ts
{
  message: string;
  data: View;
  status: 201;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- A view reads from exactly one source: a collection, or another view to derive from.
- The output schema defines the fields the view produces. A transform is created alongside the view to populate them.
- Leaving the transform script empty queues it for generation instead of rejecting it, so a view can be created before its TGL is written.
- Backfilling existing records starts shortly after creation and runs in the background. The view reports its progress while it catches up.
- A view reads from exactly one place: set `collection_id` to derive from raw ingested records, or `parent_view_id` to derive from another view. Setting both, or neither, is rejected.
- There is no `source_id` on this endpoint. One source can hold many collections, and the collection is what owns the schema and the records a view transforms. List a source's collections with `GET /v1/projects/:project/collections?source_id=...` and pass the one you want.
- `output_schema` field names must be unique by name, cannot conflict on type, and cannot collide with a reserved query key such as `limit` or `cursor`.
- `transform_script` cannot be whitespace-only. Supplying one activates the view's first transform immediately and skips automatic TGL generation.
- Use `hint` for rules the generator cannot read off the field names: a formatting rule ("display_name should be Name (Type)"), a business threshold ("classify as high above 500"), or a disambiguation when a source field could map more than one way. Skip it for obvious mappings like renames and type conversions, which the generator already handles.

## Create Join View

### Endpoint

Create a view that joins two existing views on a shared key.

```http
POST /v1/projects/:project_id/views/join
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

### Request Body

- **`name`** `string` -- **Required**
  Name shown for this join view. Minimum length: `1`. Maximum length: `128`.

- **`project_id`** `string`
  Project the join view belongs to. Optional.

- **`base_view_id`** `string` -- **Required**
  View every output record starts from. Minimum length: `1`.

- **`joins`** `object[]` -- **Required**
  Views joined onto the base view, each with its own match fields.

- **`output_schema`** `object[]`
  Fields the join produces. Leave empty to have one inferred. Optional. Defaults to `[]`.

- **`transform_script`** `string`
  TGL transform script. Omit to have one generated. Optional. Minimum length: `1`. Maximum length: `65536`.

- **`hint`** `string`
  Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional. Maximum length: `4096`.

### Response

Join view created

```ts
{
  message: string;
  data: View;
  status: 201;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Both sides must be views in this project. Join a collection by creating a view over it first.
- The join key must exist in the output schema of both sides.
- A join view rebuilds when either side produces new records, so it trails its inputs rather than updating in the same instant.
- Records are enriched with each joined view's latest value, by record timestamp, at the time they are processed. Records already produced are not revisited.
- Each join clause's `alias` must be lowercase letters, digits and underscores, and start with a letter. It is how the clause's fields are addressed in the transform script.
- `transform_script` cannot be whitespace-only. Omit it entirely to have one generated.
- Use `hint` for rules the generator cannot read off the field names: a formatting rule ("display_name should be Name (Type)"), a business threshold ("classify as high above 500"), or a disambiguation when an input field could map more than one way. Skip it for obvious mappings like renames and type conversions, which the generator already handles.

## Update View

### Endpoint

Update a view's name or output schema.

```http
POST /v1/projects/:project_id/views/:view_id
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Request Body

- **`mapping_policy`** `object`
  Optional.

- **`name`** `string`
  Name for the view. Optional. Minimum length: `1`. Maximum length: `128`.

- **`output_schema`** `object[]`
  The fields this view produces. Can be left until transforms define it. Optional.

- **`hint`** `string | null`
  Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.

- **`transform_mode`** `string`
  Whether a new schema version that no existing transform covers gets a script generated for it, or waits for you to write one. Optional. Allowed values: `"auto"`, `"manual"`.

- **`view_transform_id`** `string`
  Which transform adopts the schema versions still waiting for a script when switching transform_mode to manual. Required only when the view has more than one distinct transform script. Optional.

- **`deleted_at`** `null`
  Send `null` to restore a view that is scheduled for deletion. The deletion date itself is set by the delete endpoint and cannot be written here. Optional.

### Response

View updated

```ts
{
  message: string;
  data: View;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Output schema changes are accepted only before a mapping or stored output exists. Changing an existing view's output schema requires a recovery transition that is not yet supported.
- Mapping policy changes on an existing mapping require reviewed coverage recovery. Hint changes preserve accepted mappings and retry undecided work with the new hint.
- What a view reads from is fixed at creation, so `collection_id` and `parent_view_id` are not accepted here. Create a new view to read from something else.
- `output_schema` field names must be unique by name, cannot conflict on type, and cannot collide with a reserved query key such as `limit` or `cursor`.
- Use `hint` for rules the generator cannot read off the field names: a formatting rule ("display_name should be Name (Type)"), a business threshold ("classify as high above 500"), or a disambiguation when a source field could map more than one way. Skip it for obvious mappings like renames and type conversions, which the generator already handles.

## Delete View

### Endpoint

Delete a view and every view derived from it.

```http
DELETE /v1/projects/:project_id/views/:view_id
```

**Scope:** `views:delete`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Response

```ts
{
  message: string;
  data: View;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Views derived from this one are deleted with it.
- The delete is refused while an active drain reads from this view or from any view that would cascade with it. Delete those drains first. Draft drains are removed silently.
- Backfill and indexing work for the whole cascade is cancelled immediately, so a deleted view stops consuming capacity before its rows are removed.

## Force Delete View

### Endpoint

Delete a view permanently, without waiting out its restore window.

```http
DELETE /v1/projects/:project_id/views/:view_id/force
```

**Scope:** `views:delete`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Response

View queued for permanent deletion.

```ts
{
  message: string;
  data: null;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Works on a live view as well as one already scheduled for deletion.
- Views derived from this one go with it, and the rollups of any metric reading them are destroyed. The records they were built from are not affected, so both can be rebuilt.

## List View Records

### Endpoint

Retrieve the transformed records a view has produced.

```http
GET /v1/projects/:project_id/views/:view_id/records
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Response

```ts
{
  message: string;
  data: ViewRecord[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- Records are only filterable on fields that have a facet. Filtering on any other field is rejected, so create a facet for a field before querying it.
- Paging is forward-only. Pass `pagination.next_cursor` back as `after` to get the next page; a null `next_cursor` means there is nothing more to read. No `prev_cursor` is issued, and `before` is ignored.
- A cursor is only valid for the query that produced it. Changing the sort direction, the filter, or (on a filtered read) the time range and reusing an older cursor returns a 400 rather than a page of skipped or repeated records, so start again without a cursor after any of those change.
- `start_at` and `end_at` narrow a filtered read only. An unfiltered read returns the view's records regardless of the window, so pass a facet filter when you need a time-bounded result.
- A cursor stays valid while Tailglow reorganizes stored data in the background, so a paging client is never interrupted by storage maintenance. It is a position in this result set, not a snapshot: records that arrive while you page appear only if they sort after your position.
- `after` and `before` are mutually exclusive.

## List Waiting Shapes

### Endpoint

Retrieve the event shapes a view has deferred because no transform serves them yet.

```http
GET /v1/projects/:project_id/views/:view_id/deferrals
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Query Parameters

- **`resolved`** `string`
  Filter by resolution. `false` returns only shapes still waiting. Optional. Allowed values: `"true"`, `"false"`.

- **`order_by`** `string`
  Sort field. Defaults to `last_deferred_at`. Optional. Allowed values: `"first_deferred_at"`, `"last_deferred_at"`.

- **`limit`** `number`
  Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.

- **`after`** `string`
  Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.

- **`before`** `string`
  Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.

- **`sort`** `string`
  Sort direction for the result set. Optional. Defaults to `"desc"`. Allowed values: `"asc"`, `"desc"`.

### Response

```ts
{
  message: string;
  data: ViewShapeDeferral[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- Deferred records are stored, not lost. They backfill automatically once a script covers the shape, and each entry's `reason` says what unblocks it.
- `event_count` is approximate: ingest retries can count an event more than once.
- `after` and `before` are mutually exclusive.

## Inspect Waiting Shape

### Endpoint

Inspect structural differences and candidate output bindings for a waiting shape.

```http
GET /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`deferral_id`** `string` -- **Required**
  Unique identifier of the deferral.

### Response

Waiting shape inspected

```ts
{
  message: string;
  data: ViewShapeInspection;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

## Retrieve Waiting Shape Hint

### Endpoint

Retrieve the model's current binding question for a waiting shape.

```http
GET /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id/hint
```

**Scopes:** `views:read` + `sources:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`deferral_id`** `string` -- **Required**
  Unique identifier of the deferral.

### Response 200 (1)

No pending hint question

```ts
{
  message: string;
  data: null;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Response 200 (2)

Waiting shape hint retrieved

```ts
{
  message: string;
  data: ViewPendingHint;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Requires both views:read and sources:read because model prose may refer to source records. Suggested paths are verified source fields in unambiguous JSON bracket notation.
- Returns null when no current question remains. Answer using this question's episode, mapping_generation, requested_at, and transform_id.

## Decide Waiting Shape

### Endpoint

Resolve the next action for one view and schema version.

```http
POST /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id/decision
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`deferral_id`** `string` -- **Required**
  Unique identifier of the deferral.

### Request Body

- **`episode`** `integer` -- **Required**
  The deferral episode shown when the decision was inspected. Minimum: `1`.

- **`action`** `string` -- **Required**
  Authorize a bounded authoring attempt, answer a hint, or exclude this exact version in this view. Allowed values: `"author"`, `"hint"`, `"exclude"`.

- **`mapping_generation`** `integer` -- **Required**
  The mapping generation shown when the decision was inspected. Minimum: `1`.

- **`hint`** `string`
  Answer to the model's binding question. Optional. Minimum length: `1`. Maximum length: `4096`.

- **`hint_requested_at`** [`ISODateString`](/api/views#iso-date-string)
  Exact requested_at of the pending hint being answered. Optional.

- **`hint_transform_id`** `string`
  Transform that owns the pending hint being answered. Optional. Minimum length: `1`. Maximum length: `32`.

- **`reason`** `string`
  Required audit reason for an exclusion. Optional. Minimum length: `1`. Maximum length: `2000`.

- **`mapping_policy`** `object`
  Explicit null, fallback, drop, and source-binding rules. Optional.

### Response

```ts
{
  message: string;
  data: null;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Authoring and hint answers reserve a bounded model budget. Exclusion covers unprocessed and future records of this exact version in this view.
- Answering a hint requires `hint`; excluding a version requires `reason`. Exclusion covers unprocessed and future records with this exact schema version in this view.
- Answering a pending model question also requires its exact `hint_requested_at` and `hint_transform_id`. Superseded or answered questions return a conflict; manual guidance without a pending question may omit both fields.

## List View Transforms

### Endpoint

Retrieve a list of a view's transforms.

```http
GET /v1/projects/:project_id/views/:view_id/transforms
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Query Parameters

- **`order_by`** `string`
  Field the results are sorted by. Creation order matches schema version order. Optional. Defaults to `"created_at"`. Allowed values: `"created_at"`, `"updated_at"`.

- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)
  Return only transforms in this state. Optional.

- **`limit`** `number`
  Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.

- **`after`** `string`
  Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.

- **`before`** `string`
  Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.

- **`sort`** `string`
  Optional. Defaults to `"asc"`. Allowed values: `"asc"`, `"desc"`.

### Response

```ts
{
  message: string;
  data: ViewTransform[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- `after` and `before` are mutually exclusive.

## Retrieve View Transform

### Endpoint

Retrieve a single transform.

```http
GET /v1/projects/:project_id/views/:view_id/transforms/:transform_id
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`transform_id`** `string` -- **Required**
  Unique identifier of the transform.

### Response

Transform retrieved

```ts
{
  message: string;
  data: ViewTransform;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

## List View Versions

### Endpoint

Retrieve a list of the schema versions a view covers.

```http
GET /v1/projects/:project_id/views/:view_id/versions
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Query Parameters

- **`order_by`** `string`
  Field the results are sorted by. Optional. Defaults to `"version"`. Allowed values: `"version"`.

- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)
  Return only versions whose transform is in this state. Optional.

- **`script_hash`** `string`
  Return only versions served by this script. Take the value from a version's row. Optional. Minimum length: `64`. Maximum length: `64`.

- **`limit`** `number`
  Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.

- **`after`** `string`
  Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.

- **`before`** `string`
  Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.

- **`sort`** `string`
  Sort direction for the result set. Optional. Defaults to `"desc"`. Allowed values: `"asc"`, `"desc"`.

### Response

```ts
{
  message: string;
  data: ViewVersion[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- One row per source schema version the view covers, newest version first. A view that reads another view has no schema versions, so its list is empty.
- Versions sharing a `script_hash` run the same transform. Filter by it to see every version one script serves.
- `after` and `before` are mutually exclusive.

## Retrieve View Version

### Endpoint

Retrieve one schema version a view covers.

```http
GET /v1/projects/:project_id/views/:view_id/versions/:version_id
```

**Scope:** `views:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`version_id`** `string` -- **Required**
  Unique identifier of the version.

### Response

Version retrieved

```ts
{
  message: string;
  data: ViewVersion;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- `version_id` is the schema version NUMBER, the `version` a row carries, not a `vta_` id.

## Create View Transform

### Endpoint

Add a transform that maps incoming records into the view's output schema.

```http
POST /v1/projects/:project_id/views/:view_id/transforms
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Request Body

- **`collection_schema_version`** `integer | null`
  Collection schema version this transform covers. Null for view-to-view transforms. Optional.

- **`transform_script`** `string`
  TGL transform script. Omit to have one generated. Optional. Minimum length: `1`. Maximum length: `65536`.

### Response

Transform created

```ts
{
  message: string;
  data: ViewTransform;
  status: 201;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- `transform_script` is TGL and must compile. A script that does not parse is rejected rather than stored, because an unparseable script would fail on every record it later touched.
- The script must write every field the view's output schema declares.
- Omitting the script queues the transform for generation instead of rejecting it.
- A transform on a collection-backed view must name the `collection_schema_version` it covers. View-to-view transforms pass null, because they read another view's output rather than a collection schema.
- `transform_script` cannot be whitespace-only. Omit it to have TGL generated instead.

## Update View Transform

### Endpoint

Update a transform's script.

```http
POST /v1/projects/:project_id/views/:view_id/transforms/:transform_id
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`transform_id`** `string` -- **Required**
  Unique identifier of the transform.

### Request Body

- **`transform_script`** `string`
  TGL transform script. To clear and regenerate, use the regenerate endpoint instead. Optional. Minimum length: `1`. Maximum length: `65536`.

- **`conflict_resolution`** `string`
  How conflicting schema versions are handled when a script edit fans out. Optional. Defaults to `"strand"`. Allowed values: `"strand"`, `"keep"`.

- **`dry_run`** `boolean`
  Preview the edit: returns the per-version verdict report without changing anything. Optional. Defaults to `false`.

- **`preview_token`** `string`
  The `edit_report.preview_token` from this edit's dry run. Required to save: it pins the save to the set of schema versions the preview reported on, so a version assigned or reassigned in between is rejected instead of silently included. Optional. Minimum length: `1`. Maximum length: `128`.

### Response 200 (1)

```ts
{
  message: string;
  data: ViewTransform & { edit_report: { script_hash: string | null; version_verdicts: { collection_schema_version_id: string; version: number; verdict: "compatible" | "conflict"; action: "updated" | "kept" | "stranded"; null_paths: string[]; }[]; preview_token?: string | undefined; } };
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

#### Additional Response Fields

- **`edit_report`** `{ script_hash: string | null; version_verdicts: { collection_schema_version_id: string; version: number; verdict: "compatible" | "conflict"; action: "updated" | "kept" | "stranded"; null_paths: string[]; }[]; preview_token?: string | undefined; }` -- **Required**

### Response 200 (2)

Transform updated

```ts
{
  message: string;
  data: ViewTransform & { edit_report: { script_hash: string | null; version_verdicts: never[]; } };
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

#### Additional Response Fields

- **`edit_report`** `{ script_hash: string | null; version_verdicts: never[]; }` -- **Required**

### Comments

- `transform_script` is TGL and must compile, and must write every field the view's output schema declares.
- On collection-backed views, an edit fans out to every schema version using the same prior script and returns a verdict for each version. Additive reads report paths that produce null.
- Conflicting versions are moved to queued placeholders by default. Set `conflict_resolution` to `keep` to leave those versions on their prior script.
- Removing an output field is rejected while a metric, facet, child view, or join clause reads it. Consumer checks use the complete nested output path. Records already produced keep their existing values until the transform is re-backfilled.
- Set `dry_run` to preview the per-version verdict report without saving. A dry run changes nothing and triggers nothing, and returns a `preview_token` alongside the report.
- Saving requires the `preview_token` from that dry run. It pins the save to the set of schema versions the report described, so a version added or reassigned between the preview and the save cannot be silently included.
- A save with a missing or outdated token is rejected with 409 and changes nothing. The error carries the report as it stands now, so the next preview is already in hand.
- `transform_script` cannot be whitespace-only. To clear a script and have it regenerated, use the regenerate endpoint rather than sending an empty one.
- Saving is a two-step gesture: send `dry_run` to get the report and its `preview_token`, then send the same edit with that token. A save without one, or with a token whose set has since changed, is rejected with 409 and a fresh report.

## Generate View Transform Script

### Endpoint

Queue TGL generation for a transform from the view's input and output schemas.

```http
POST /v1/projects/:project_id/views/:view_id/transforms/:transform_id/generate
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`transform_id`** `string` -- **Required**
  Unique identifier of the transform.

### Response

TGL generation queued

```ts
{
  message: string;
  data: null;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Generation runs in the background. This returns once the work is queued, not once a script exists; poll the transform to see the result.
- The generated script is written to the transform and replaces whatever was there.

## Re-backfill View Transform

### Endpoint

Re-run a transform over every record the view has already read.

```http
POST /v1/projects/:project_id/views/:view_id/transforms/:transform_id/rebackfill
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`transform_id`** `string` -- **Required**
  Unique identifier of the transform.

### Response

Transform re-backfill started

```ts
{
  message: string;
  data: ViewTransform;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Use this after editing a script so existing records pick up the new logic. Without it, an edit only affects records that arrive afterwards.
- The re-backfill runs in the background and replaces the view's output as it progresses. Anything reading the view sees a mix of old and new values until it finishes.
- Metrics and views that depend on this one are rebuilt as well.

## Regenerate View

### Endpoint

Replace a view's output schema and queue every transform to be regenerated against it.

```http
POST /v1/projects/:project_id/views/:view_id/regenerate
```

**Scope:** `views:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

### Request Body

- **`output_schema`** `object[]`
  The fields the view should produce. Omit to regenerate against the current schema. Optional.

- **`hint`** `string | null`
  Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.

### Response

View schema updated and transforms queued for regeneration

```ts
{
  message: string;
  data: View;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- This is the schema-change path: it updates the output schema and re-queues the view's transforms in one step, so the scripts are rewritten to produce the new fields.
- Existing records keep their old shape until the regenerated transforms backfill over them.
- `output_schema` field names must be unique by name, cannot conflict on type, and cannot collide with a reserved query key such as `limit` or `cursor`.
- Use `hint` for rules the generator cannot read off the field names: a formatting rule ("display_name should be Name (Type)"), a business threshold ("classify as high above 500"), or a disambiguation when a source field could map more than one way. Skip it for obvious mappings like renames and type conversions, which the generator already handles.

## Delete View Transform

### Endpoint

Delete a transform from a view.

```http
DELETE /v1/projects/:project_id/views/:view_id/transforms/:transform_id
```

**Scope:** `views:delete`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`view_id`** `string` -- **Required**
  Unique identifier of the view.

- **`transform_id`** `string` -- **Required**
  Unique identifier of the transform.

### Response

Transform deleted

```ts
{
  message: string;
  data: null;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Records the transform already produced are left in place. Re-backfill the view's remaining transforms to rebuild its output without this one.

