@tailglow/cli 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/docs.generated.js
CHANGED
|
@@ -14,13 +14,13 @@ export const DOCS_INDEX = "# Tailglow\n\n> Analytics and monitoring platform. Se
|
|
|
14
14
|
*/
|
|
15
15
|
export const DOCS_PAGES = {
|
|
16
16
|
"api/introduction": "# Introduction\n\n## Base URL\n\nThe Tailglow API is built on REST principles. We enforce HTTPS in every request to improve data security, integrity, and privacy. The API does not support HTTP.\n\nAll requests contain the following base URL:\n\n```md\nhttps://api.tailglow.io\n```\n\n## Authentication\n\nTo authenticate with the Tailglow API, send your API key in the `Authorization` header using the `Bearer` scheme. An API key looks like `tg_api_` followed by 70 characters.\n\n```md\nAuthorization: Bearer tg_api_1a2B3c4D5e6F7g8H9i0J...\n```\n\n`Authorization: Key <your key>` is also accepted, as is sending the key on its own with no scheme. Keys issued before the `tg_api_` prefix begin with `sk_` and keep working indefinitely.\n\n## Filtering Data\n\nWhen filtering data, you'll create a query string that will be appended to the URL of your `GET` request. The query string will begin with a `?` and contain key-value pairs separated by `&`.\n\n```\nBe careful when using \"not\" filters. They can result in a lot of data being returned.\n```\n\nTailglow uses a single canonical operator vocabulary across every filter surface. URL syntax is flat: `?<field>=<op>:<value>`. The full list, per-type allowlists, and per-surface availability matrix live in the [Filter Operators enum](/api/enums#filter-operators).\n\n### Strings\n\n- **`equals`** - `?type=equals:expense`\n- **`not_equals`** - `?type=not_equals:expense`\n- **`starts_with`** - `?type=starts_with:expense`\n- **`not_starts_with`** - `?type=not_starts_with:expense`\n- **`ends_with`** - `?type=ends_with:expense`\n- **`not_ends_with`** - `?type=not_ends_with:expense`\n- **`contains`** - `?type=contains:expense`\n- **`not_contains`** - `?type=not_contains:expense`\n- **`in`** - `?type=in:expense,refund`\n- **`not_in`** - `?type=not_in:expense,refund`\n\nString operators compare case-insensitively, so `?type=equals:GET` and `?type=equals:get` include the same rows.\n\n### Numbers\n\n- **`equals`** - `?value=equals:100`\n- **`not_equals`** - `?value=not_equals:100`\n- **`gt`** (Greater Than) - `?value=gt:100`\n- **`gte`** (Greater Than or Equal) - `?value=gte:100`\n- **`lt`** (Less Than) - `?value=lt:100`\n- **`lte`** (Less Than or Equal) - `?value=lte:100`\n- **`between`** - `?value=between:100,200`\n- **`in`** / **`not_in`** - `?value=in:100,200`\n\n## Pagination\n\nMost `list` endpoints use cursor-based pagination. The first request omits the cursor; subsequent requests echo back the `next_cursor` (or `prev_cursor`) the previous response handed you.\n\n### Request parameters\n\n- **`limit`** `number`\n Maximum number of items to return per page. Defaults to `25`, max `200`.\n- **`after`** `string`\n Opaque cursor from a previous response's `pagination.next_cursor`. Returns the rows after the pivot.\n- **`before`** `string`\n Opaque cursor from a previous response's `pagination.prev_cursor`. Returns the rows before the pivot. Mutually exclusive with `after`.\n- **`sort`** `\"asc\" \\| \"desc\"`\n Sort direction. Cursors are minted under one direction; reusing them under a different sort returns a `400`.\n\n### Response shape\n\nThe `pagination` object on every list response carries:\n\n- **`limit`** `number`: echoed from the request.\n- **`count`** `number`: number of rows in this page (≤ `limit`).\n- **`offset`** `number`: 0-indexed position of the first row across the full result set. Use `offset + 1` for \"Showing 21\" labels.\n- **`sort`** `\"asc\" \\| \"desc\"`: echoed sort direction.\n- **`next_cursor`** `string \\| null`: cursor for the next page. `null` means there are no more rows.\n- **`prev_cursor`** `string \\| null`: cursor for the previous page. `null` means you're on the first page.\n- **`total`** `number \\| null`: exact row count for database-backed lists and records. `null` is returned only when the underlying feed cannot provide a total. For example, collection docs always return `null` because the underlying files aren't enumerated.\n\nCursors are server-minted opaque tokens. Don't construct them yourself; round-trip the values the API gave you.\n\n## Responses\n\nAll of our responses will follow a standard structure, regardless if it's successful or not. Here is an example of the response object.\n\n### Successful Response Example\n\nIf you are listing data, the `data` key will be an array of objects and a pagination object will be present.\nIf you are retrieving, creating, or writing a single object, the `data` key will be an object.\nIf you are deleting an object, the `data` key will be `null`.\n\n```json\n{\n \"message\": \"Data retrieved successfully\",\n \"data\": [],\n \"status\": 200,\n \"error\": null,\n \"pagination\": {\n \"object\": \"pagination\",\n \"limit\": 10,\n \"count\": 10,\n \"offset\": 0,\n \"sort\": \"desc\",\n \"next_cursor\": \"eyJ2IjoxLCJyZXNvdXJjZSI6...\",\n \"prev_cursor\": null,\n \"total\": 42\n },\n \"endpoint\": \"/v1/projects\"\n}\n```\n\n### Error Response Example\n\nYou'll typically see error when request validations fail. The `error` key will contain an object with a `code` key and a `properties` key that will contain an array of objects with an `id` and `message` key. The `id` corresponds to the field that failed validation and the `message` will contain the error message.\n\n```json\n{\n \"message\": \"The request is invalid\",\n \"data\": null,\n \"status\": 400,\n \"error\": {\n \"code\": \"request_invalid\",\n \"properties\": [\n {\n \"id\": \"name\",\n \"message\": \"Name is required\"\n }\n ]\n },\n \"pagination\": null,\n \"endpoint\": \"/v1/projects\"\n}\n```\n",
|
|
17
|
-
"api/errors": "# Errors\n\n## Variations\n\nWe follow standard HTTP status codes for errors. Here are the variations you might encounter:\n\n### `request_invalid`\n\n- **Status Code:** 400\n- **Message:** \"The request is invalid.\"\n- **How to Resolve:** Check the request payload for missing or incorrect fields.\n\n### `request_no_update`\n\n- **Status Code:** 400\n- **Message:** \"There was nothing new to update.\"\n- **How to Resolve:** Make sure you're submitting new or modified data.\n\n### `request_validation_failed`\n\n- **Status Code:** 400\n- **Message:** \"The request failed validation.\"\n- **How to Resolve:** Ensure all fields meet the required validation rules.\n\n### `unsupported_file_type`\n\n- **Status Code:** 400\n- **Message:** \"Unsupported file type.\"\n- **How to Resolve:** Use one of the supported file formats.\n\n### `auth_missing_credentials`\n\n- **Status Code:** 401\n- **Message:** \"You must provide credentials to access this resource.\"\n- **How to Resolve:** Include your API key in the `Authorization` header.\n\n### `auth_invalid_credentials`\n\n- **Status Code:** 401\n- **Message:** \"Your credentials are invalid.\"\n- **How to Resolve:** Double-check your API key or token.\n\n### `auth_account_disabled`\n\n- **Status Code:** 401\n- **Message:** \"Your account is disabled, please contact support.\"\n- **How to Resolve:** Reach out to support to restore access.\n\n### `auth_missing_permissions`\n\n- **Status Code:** 401\n- **Message:** \"You do not have permission to access this resource.\"\n- **How to Resolve:** Ensure your account has the appropriate permissions.\n\n### `auth_mfa_required`\n\n- **Status Code:** 401\n- **Message:** \"Multi-factor authentication is required.\"\n- **How to Resolve:** Complete MFA verification before accessing this resource.\n\n### `resource_limit_reached`\n\n- **Status Code:** 403\n- **Message:** \"You have reached the limit for this resource.\"\n- **How to Resolve:** Upgrade your plan or wait until the limit resets.\n\n### `forbidden`\n\n- **Status Code:** 403\n- **Message:** \"This action is forbidden.\"\n- **How to Resolve:** Ensure you have proper permissions for this action.\n\n### `resource_not_found`\n\n- **Status Code:** 404\n- **Message:** \"The requested resource was not found.\"\n- **How to Resolve:** Verify the resource ID or endpoint.\n\n### `request_timeout`\n\n- **Status Code:** 408\n- **Message:** \"The request timed out.\"\n- **How to Resolve:** Retry the request or check your connection.\n\n### `rate_limit_exceeded`\n\n- **Status Code:** 429\n- **Message:** \"You have exceeded the rate limit.\"\n- **How to Resolve:** Reduce the frequency of your requests and try again later.\n\n### `resource_locked`\n\n- **Status Code:** 409\n- **Message:** \"The resource is locked.\"\n- **How to Resolve:** Unlock the resource if possible or wait until it becomes available.\n\n### `payload_too_large`\n\n- **Status Code:** 413\n- **Message:** \"The payload was too large for the server to safely handle.\"\n- **How to Resolve:** Reduce the size of the request payload.\n\n### `ingest_spool_full`\n\n- **Status Code:** 503\n- **Message:** \"Server at capacity (REASON); retry shortly\", where REASON names the limiting resource, e.g. a disk-capacity reason such as \"N MiB free below the M MiB byte floor\" or \"inodes P% at/above the C% ceiling\", \"ingest queue full\", or \"storage write failed\".\n- **Returned by:** the ingest endpoint when the server's durable spool cannot accept more records. The response includes a `Retry-After` header (in seconds).\n- **How to Resolve:** Retry the request after the `Retry-After` interval. If the error persists, you've outgrown the server's allocated capacity for this project. Provision a larger spool volume or distribute traffic across additional servers.\n\n### `ingest_auth_unavailable`\n\n- **Status Code:** 503\n- **Message:** \"The ingest key could not be verified; retry shortly.\"\n- **Returned by:** the ingest endpoint when it cannot reach the service that verifies ingest keys. Your key has not been rejected, and nothing about the request needs to change. The response includes a `Retry-After` header (in seconds).\n- **How to Resolve:** Retry the request after the `Retry-After` interval. Tailglow SDKs do this automatically. A key that was verified recently keeps being accepted throughout the outage, so this only affects a key the server has not seen lately.\n\n### `database_error`\n\n- **Status Code:** 500\n- **Message:** \"A database error occurred.\"\n- **How to Resolve:** Try again later or contact support if the issue persists.\n\n### `invalid_server_state`\n\n- **Status Code:** 500\n- **Message:** \"The server is in an invalid state.\"\n- **How to Resolve:** Contact support if the issue persists.\n\n### `plan_limit_reached`\n\n- **Status Code:** 403\n- **Message:** \"You have reached the limit for your current plan.\"\n- **How to Resolve:** Upgrade your plan to increase your limits.\n\n### `server_error`\n\n- **Status Code:** 500\n- **Message:** \"An error occurred.\"\n- **How to Resolve:** Try again later or contact support if the issue persists.\n\n### `not_implemented`\n\n- **Status Code:** 501\n- **Message:** \"This route has not been implemented.\"\n- **How to Resolve:** Contact support or refer to the documentation for available routes.\n",
|
|
17
|
+
"api/errors": "# Errors\n\n## Variations\n\nWe follow standard HTTP status codes for errors. Here are the variations you might encounter:\n\n### `request_invalid`\n\n- **Status Code:** 400\n- **Message:** \"The request is invalid.\"\n- **How to Resolve:** Check the request payload for missing or incorrect fields.\n\n### `request_no_update`\n\n- **Status Code:** 400\n- **Message:** \"There was nothing new to update.\"\n- **How to Resolve:** Make sure you're submitting new or modified data.\n\n### `request_validation_failed`\n\n- **Status Code:** 400\n- **Message:** \"The request failed validation.\"\n- **How to Resolve:** Ensure all fields meet the required validation rules.\n\n### `unsupported_file_type`\n\n- **Status Code:** 400\n- **Message:** \"Unsupported file type.\"\n- **How to Resolve:** Use one of the supported file formats.\n\n### `auth_missing_credentials`\n\n- **Status Code:** 401\n- **Message:** \"You must provide credentials to access this resource.\"\n- **How to Resolve:** Include your API key in the `Authorization` header.\n\n### `auth_invalid_credentials`\n\n- **Status Code:** 401\n- **Message:** \"Your credentials are invalid.\"\n- **How to Resolve:** Double-check your API key or token.\n\n### `auth_account_disabled`\n\n- **Status Code:** 401\n- **Message:** \"Your account is disabled, please contact support.\"\n- **How to Resolve:** Reach out to support to restore access.\n\n### `auth_missing_permissions`\n\n- **Status Code:** 401\n- **Message:** \"You do not have permission to access this resource.\"\n- **How to Resolve:** Ensure your account has the appropriate permissions.\n\n### `auth_mfa_required`\n\n- **Status Code:** 401\n- **Message:** \"Multi-factor authentication is required.\"\n- **How to Resolve:** Complete MFA verification before accessing this resource.\n\n### `auth_unavailable`\n\n- **Status Code:** 503\n- **Message:** \"Your credentials could not be checked right now; retry shortly.\"\n- **Returned by:** any authenticated endpoint when Tailglow cannot reach a service it needs to check your credentials. Your credentials have not been rejected, and nothing about the request needs to change.\n- **How to Resolve:** Retry the request after a short wait. Do not sign out or replace your API key: this error is temporary.\n\n### `resource_limit_reached`\n\n- **Status Code:** 403\n- **Message:** \"You have reached the limit for this resource.\"\n- **How to Resolve:** Upgrade your plan or wait until the limit resets.\n\n### `forbidden`\n\n- **Status Code:** 403\n- **Message:** \"This action is forbidden.\"\n- **How to Resolve:** Ensure you have proper permissions for this action.\n\n### `resource_not_found`\n\n- **Status Code:** 404\n- **Message:** \"The requested resource was not found.\"\n- **How to Resolve:** Verify the resource ID or endpoint.\n\n### `request_timeout`\n\n- **Status Code:** 408\n- **Message:** \"The request timed out.\"\n- **How to Resolve:** Retry the request or check your connection.\n\n### `rate_limit_exceeded`\n\n- **Status Code:** 429\n- **Message:** \"You have exceeded the rate limit.\"\n- **How to Resolve:** Reduce the frequency of your requests and try again later.\n\n### `resource_locked`\n\n- **Status Code:** 409\n- **Message:** \"The resource is locked.\"\n- **How to Resolve:** Unlock the resource if possible or wait until it becomes available.\n\n### `payload_too_large`\n\n- **Status Code:** 413\n- **Message:** \"The payload was too large for the server to safely handle.\"\n- **How to Resolve:** Reduce the size of the request payload.\n\n### `ingest_spool_full`\n\n- **Status Code:** 503\n- **Message:** \"Server at capacity (REASON); retry shortly\", where REASON names the limiting resource, e.g. a disk-capacity reason such as \"N MiB free below the M MiB byte floor\" or \"inodes P% at/above the C% ceiling\", \"ingest queue full\", or \"storage write failed\".\n- **Returned by:** the ingest endpoint when the server's durable spool cannot accept more records. The response includes a `Retry-After` header (in seconds).\n- **How to Resolve:** Retry the request after the `Retry-After` interval. If the error persists, you've outgrown the server's allocated capacity for this project. Provision a larger spool volume or distribute traffic across additional servers.\n\n### `ingest_auth_unavailable`\n\n- **Status Code:** 503\n- **Message:** \"The ingest key could not be verified; retry shortly.\"\n- **Returned by:** the ingest endpoint when it cannot reach the service that verifies ingest keys. Your key has not been rejected, and nothing about the request needs to change. The response includes a `Retry-After` header (in seconds).\n- **How to Resolve:** Retry the request after the `Retry-After` interval. Tailglow SDKs do this automatically. A key that was verified recently keeps being accepted throughout the outage, so this only affects a key the server has not seen lately.\n\n### `database_error`\n\n- **Status Code:** 500\n- **Message:** \"A database error occurred.\"\n- **How to Resolve:** Try again later or contact support if the issue persists.\n\n### `invalid_server_state`\n\n- **Status Code:** 500\n- **Message:** \"The server is in an invalid state.\"\n- **How to Resolve:** Contact support if the issue persists.\n\n### `plan_limit_reached`\n\n- **Status Code:** 403\n- **Message:** \"You have reached the limit for your current plan.\"\n- **How to Resolve:** Upgrade your plan to increase your limits.\n\n### `server_error`\n\n- **Status Code:** 500\n- **Message:** \"An error occurred.\"\n- **How to Resolve:** Try again later or contact support if the issue persists.\n\n### `not_implemented`\n\n- **Status Code:** 501\n- **Message:** \"This route has not been implemented.\"\n- **How to Resolve:** Contact support or refer to the documentation for available routes.\n",
|
|
18
18
|
"api/enums": "# Enums\n\nThis page lists common enum values used across the API.\n\n## Metric Group By\n\nThe `group_by` field accepts any string field names from your view's output schema. There is no fixed set of allowed values.\n\n## Chart Types\n\n- `line`\n- `area`\n- `bar`\n- `pie`\n- `scatter`\n- `radar`\n- `stat`\n- `gauge`\n- `calendar`\n- `uptime`\n\n## Chart Color Modes\n\nHow a chart picks colors. The chart type must allow the chosen mode (e.g., gauge supports `by_series` and `by_value`; calendar supports `by_series` and `by_intensity`).\n\n- `by_series`: Each series gets its own color. Colors are auto-assigned from a deterministic series-hash → palette projection (stable across drill-down). Per-series overrides via [Update Series](/api/metrics#update-series).\n- `by_intensity`: One base color (`ui_chart_color_base`); the renderer modulates lightness/opacity by value. Used by calendar / heatmap-style charts.\n- `by_value`: Ordered rules (`ui_chart_color_rules`) map each value to a color. First matching rule wins; no match falls back to system default. Used by gauges and uptime strips.\n\n## Metric Chart Values\n\nDetermines which aggregation is displayed by default on a chart.\n\n- `count`: Number of records.\n- `average`: Average of the record `value` field.\n- `sum`: Sum of the record `value` field.\n- `min`: Minimum of the record `value` field.\n- `max`: Maximum of the record `value` field.\n- `last`: Latest reading in each time bucket per group_by combination. Within the requested range, the last observed value carries forward across complete empty intervals. Use for entity/snapshot data (e.g., deals, users, inventory).\n- `cumulative_sum`: Cumulative sum of the record `value` field over the time range.\n- `cumulative_count`: Cumulative count of records over the time range.\n- `p50`: 50th percentile (median) of the record `value` field.\n- `p95`: 95th percentile of the record `value` field.\n- `p99`: 99th percentile of the record `value` field.\n- `count_unique`: Number of distinct values of the `unique_field`. Exact for small cardinalities (up to 8,192), approximate for larger sets.\n\n## Empty Bucket Handling\n\nControls how time intervals with no data render on a metric's chart. This is a display setting: changing it applies instantly to all historical data with no recalculation.\n\n- `zero`: Empty intervals render as a true 0. The right choice for additive aggregations (`count`, `sum`, `cumulative_*`, `count_unique`) where \"nothing happened\" means zero. Monitors evaluate an empty window as 0, so conditions like `count == 0` can trigger.\n- `gaps`: Empty intervals render as gaps (`null` values in `series.records`) and are excluded from trend line fitting. The right choice for observational aggregations (`average`, `min`, `max`, percentiles) where an empty interval has no defined value. Monitors skip evaluation when a series has no data in the window: nothing fires and open alerts stay open until data returns. The `last` chart value is the one exception on charts: it carries the latest measured value forward through empty intervals in both modes.\n\nNew metrics default based on their `ui_chart_value`: additive aggregations get `zero`, observational aggregations get `gaps`.\n\n## Chart Colors\n\nPossible values for the `color` property within `ui_chart_colors`.\n\n- `blue`\n- `red`\n- `amber`\n- `green`\n- `teal`\n- `purple`\n- `pink`\n\n## Analytics Time Ranges\n\nUsed for `time_range` (in aggregation requests) and `ui_chart_time_range` (in the metric object).\n\n- `last_hour`\n- `last_6_hours`\n- `today`\n- `last_24_hours`\n- `yesterday`\n- `this_week`\n- `last_7_days`\n- `last_week`\n- `this_month`\n- `last_30_days`\n- `last_month`\n- `this_quarter`\n- `last_90_days`\n- `last_quarter`\n- `this_year`\n- `last_365_days`\n- `last_year`\n- `all_time`\n- `custom` (Requires specific start/end dates)\n- `next_7_days`\n- `next_30_days`\n- `next_90_days`\n\nA `next_*` range reaches the same distance into the past as it does into the future, so a chart has history to fit a forecast from.\n\n## Forecast Horizons\n\nUsed for `ui_chart_forecast_horizon` (in the metric object). Relative values resolve when the chart is viewed.\n\n- `next_7_days`\n- `next_30_days`\n- `next_90_days`\n- `end_of_quarter`\n- `end_of_year`\n- `next_year`\n\n## Forecast Models\n\nUsed for `ui_chart_forecast_model` (in the metric object). `auto` follows whichever model fits the data best.\n\n- `auto`\n- `linear`\n- `exponential`\n- `logarithmic`\n- `logistic`\n- `sinusoidal`\n\n## Metric Data Intervals\n\nUsed for `interval` (in aggregation requests) and `ui_chart_interval` (in the metric object).\n\n- `minute`\n- `hour`\n- `day`\n- `month`\n\n## View Transform Modes\n\nHow a [View](/api/views#model) acquires a [TGL](/guides/tgl) transform for schema versions discovered after it was created.\n\nBoth modes first try to reuse one of the view's existing scripts. Reuse requires a typed candidate signature and at least one of the script's read paths to be present. A present path whose value is null counts as evidence; an absent path does not. An unknown-only signature is not eligible for reuse. The modes differ only when no existing script can read the new shape:\n\n- `auto`: Tailglow generates a script for the new version.\n- `manual`: The version waits for you to supply a compatible script instead of generating one automatically. A supplied script can wait for input before validation.\n\n## View Transform Conflict Resolutions\n\nControls what happens when editing a collection-backed transform would make the new script incompatible with some schema versions that used the prior script.\n\n- `strand`: Default. Compatible versions adopt the edit; conflicting versions move to queued placeholders so they can receive their own script.\n- `keep`: Compatible versions adopt the edit; conflicting versions remain on the prior script.\n\n## Schema Version States\n\nThe lifecycle of a collection's [schema version](/guides/concepts#schema-versions), from first sight to trust.\n\n- `candidate`: The shape has been seen once. Its records are stored and served immediately by any view mapping that already fits them, but no mapping is written for it yet.\n- `settled`: The shape was seen again after the collection's recurrence gap, arrived in one batch with at least the bulk row count, or a person settled it. Only settled versions are offered to views for authoring, and a settled version never goes back.\n- `stale`: A candidate that was not seen again within the stale window. Its rows stay stored but unclassified. A stale shape that returns settles.\n\n## Schema Settlements\n\nWhat settled a schema version.\n\n- `recurrence`: The shape was seen again after the collection's recurrence gap.\n- `bulk`: One batch carried at least the collection's bulk row count of the shape.\n- `person`: Someone settled the version from the collection page or through the API.\n\n## Schema Rule Modes\n\nHow a collection applies each of its three identity rules: nulls and absent values, optional keys, and the detector. See [Schema settings](/guides/sources#schema-settings).\n\n- `auto`: Tailglow decides. Null and absent values are accepted at any path a mapping reads, a record whose keys are a subset of a version's keys joins that version, and a safe opaque-path proposal from the detector is applied on its own.\n- `manual`: A person decides at each of those points.\n\n## Metric Statuses\n\nThe current status of the metric.\n\n- `initializing`: Metric is being created.\n- `waiting_for_transforms`: Metric is waiting for its source transforms to finish processing.\n- `backfilling`: Historical data is being backfilled (also covers re-backfill triggered by data-feed changes).\n- `active`: Metric is up-to-date and collecting new data.\n- `error`: An error occurred during processing.\n- `cancelled`: The metric, or the view it reads, was deleted while a backfill was still running, so the backfill was cancelled instead of being allowed to finish. Restoring the metric within its 24-hour window leaves it here; re-run the backfill to finish what it missed.\n\n## View Statuses\n\nThe current status of a [View](/api/views#model), derived from its transforms.\n\n- `empty`: The view has no transforms yet.\n- `building`: A transform is waiting for input or processing data, including indexing history.\n- `needs_transform`: A transform script needs to be written or corrected. This can occur in either transform mode.\n- `active`: At least one transform is finished and serving data.\n- `cancelled`: The view was deleted while it was still indexing, so the indexing was cancelled rather than allowed to finish. Deleted views remain readable until permanent removal 24 hours later, and they report this status for that window.\n- `error`: A transform failed to compile, or its indexing failed.\n\n## Metric Values\n\nIndicates which aggregations were calculated in the [Metric Aggregation Model](/api/metrics#aggregation-model).\n\n- `count`\n- `sum`\n- `min`\n- `max`\n\n## Filter Operators\n\nThe canonical operator vocabulary used everywhere filters appear: list endpoint query parameters (`?<field>=<op>:<value>`), metric `filters` (applied at rollup time), facet record queries against indexed views, and the UI filter components.\n\nURL syntax is flat: `?type_1=equals:Fire&limit=50`. Operator names are long-form snake*case except for the industry-standard `gt`/`gte`/`lt`/`lte`/`in`. Every negation has a `not*` prefix. String operators compare case-insensitively (`equals:Get`matches`GET`); numeric, date, and existence operators are unaffected.\n\n| Operator | Semantics |\n| ----------------- | --------------------------------------------- |\n| `equals` | Exact match |\n| `not_equals` | Inverse of `equals` |\n| `gt` / `gte` | Greater than (strict / inclusive) |\n| `lt` / `lte` | Less than (strict / inclusive) |\n| `between` | Inclusive range; value is `min,max` |\n| `contains` | Substring match (strings) |\n| `not_contains` | Inverse of `contains` |\n| `starts_with` | Prefix match (strings) |\n| `not_starts_with` | Inverse of `starts_with` |\n| `ends_with` | Suffix match (strings) |\n| `not_ends_with` | Inverse of `ends_with` |\n| `in` | Scalar field, value in a comma-separated list |\n| `not_in` | Inverse of `in` |\n| `has` | Array field contains the given scalar |\n| `not_has` | Inverse of `has` |\n| `has_any` | Array field intersects a comma-separated list |\n| `exists` | Field has a non-null value |\n| `not_exists` | Field is null or absent |\n\n### Per-type allowlist (UI filter components)\n\n| Field type | Allowed operators |\n| ---------- | ---------------------------------------------------------------------------------------------------------------------------------- |\n| `string` | `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `not_starts_with`, `ends_with`, `not_ends_with`, `in`, `not_in` |\n| `number` | `equals`, `not_equals`, `gt`, `gte`, `lt`, `lte`, `between`, `in`, `not_in` |\n| `date` | `equals`, `not_equals`, `gt`, `gte`, `lt`, `lte`, `between` |\n| `array` | `has`, `not_has`, `has_any` |\n| `boolean` | `equals`, `not_equals` |\n\n### Surface availability\n\nNot every operator is supported by every surface. The metric filter pipeline supports the full vocabulary; CRUD list endpoints reject existence operators in v1 (the per-field nullable-aware validators land separately); facet record queries are limited to the SQLite-indexed subset.\n\n| Operator | Metric filters | CRUD list | Facet records |\n| ----------------- | :------------: | :-------: | :-----------: |\n| `equals` | ✓ | ✓ | ✓ |\n| `not_equals` | ✓ | ✓ | |\n| `gt` | ✓ | ✓ | |\n| `gte` | ✓ | ✓ | ✓ |\n| `lt` | ✓ | ✓ | |\n| `lte` | ✓ | ✓ | ✓ |\n| `between` | ✓ | ✓ | |\n| `contains` | ✓ | ✓ | |\n| `not_contains` | ✓ | ✓ | |\n| `starts_with` | ✓ | ✓ | ✓ |\n| `not_starts_with` | ✓ | ✓ | |\n| `ends_with` | ✓ | ✓ | |\n| `not_ends_with` | ✓ | ✓ | |\n| `in` / `not_in` | ✓ | ✓ | |\n| `has` | ✓ | | |\n| `not_has` | ✓ | | |\n| `has_any` | ✓ | | |\n| `exists` | ✓ | | |\n| `not_exists` | ✓ | | |\n\n### Reserved query parameter keys\n\nSeveral keys are reserved as query-string control parameters across every endpoint. View `output_schema` fields and facet `field_path` values cannot use any of these names:\n\n`limit`, `cursor`, `page`, `sort`, `order_by`, `after`, `before`, `start_at`, `end_at`, `timezone`, `interval`, `search`\n\n## Monitor Condition Types\n\n| Value | Meaning |\n| --- | --- |\n| `threshold` | Compare the selected metric value with a threshold. |\n| `sustained` | Require that comparison to hold for a duration. |\n| `existence` | Fire when the selected value is greater than zero. |\n| `absence` | Fire when a complete evaluation window contains no observations, even on a chart that displays gaps. Requires Count and a finite window. |\n\nFor absence, Total watches the whole filtered feed. Any series and Each series watch known individual series. A measured value of zero is still an observation. Unknown completeness postpones evaluation.\n\n## Drain Statuses\n\nLifecycle status of a Drain.\n\n- `draft`: Created but never verified. No outbound traffic.\n- `pending_verification`: A verification marker has been sent to the destination and we're waiting for the customer to paste it back via Confirm Verification.\n- `active`: Drain is verified, resumed, and forwarding records on the recurring schedule.\n- `paused`: You stopped the drain, or it was freshly verified (verification intentionally lands paused so you can sample before going live). Only you start it again. Resume via Resume Drain.\n- `disabled`: Tailglow stopped the drain after 72 hours of consecutive failures. Resume the same way after fixing the destination; resuming clears the failure counters.\n\n## Drain Body Formats\n\nWire format for the body of outbound drain POSTs.\n\n- `ndjson`: Newline-delimited JSON. Default. One record per line. Sent with `Content-Type: application/x-ndjson`.\n- `json`: A single JSON array of records. Required by Datadog Logs and similar receivers. Sent with `Content-Type: application/json`.\n\n## Drain Compressions\n\nWire compression for outbound drain POSTs.\n\n- `none`: Default. Body is sent uncompressed and works with any receiver.\n- `gzip`: Body is gzipped and `Content-Encoding: gzip` is added. Receivers MUST decompress `Content-Encoding` for this to work.\n\n## Check Statuses\n\nLifecycle status of a Check ([`status`](/api/checks#model)).\n\n- `active`: Running on its schedule.\n- `paused`: You stopped the check. Nothing is recorded for the minutes a pause covers, so the uptime record shows a gap rather than downtime. Start it again with Resume Check.\n- `disabled`: Tailglow stopped the check because the team is no longer in good standing. A failing endpoint never lands here, however long the failure lasts, because the outage is the thing the check exists to record. Resume the same way once the account is settled.\n\n## Check Methods\n\nThe HTTP method a check uses to request its endpoint ([`method`](/api/checks#model)). Both are observational, so a check can never change anything at the endpoint it watches.\n\n- `get`: Default.\n- `post`: For health endpoints that only answer to POST. Only a `post` check may send a `request_body`.\n\n## Check Error Stages\n\nWhich step of the most recent failed run went wrong ([`last_error_stage`](/api/checks#model)). A check never reads the response body, so there is no parse stage.\n\n- `request`: No usable response arrived. The address was refused by the outbound guard, the request timed out, TLS failed, the connection was reset, or the body ran past the size cap before it finished.\n- `response`: The endpoint answered, but not with a 2xx status.\n\n## Appearance Palettes\n\nThe color palette for a user's app appearance ([`appearance_palette`](/api/users#model)). Sets the light or dark scheme.\n\n- `light`\n- `dark`\n- `dim`: Default. A softer dark scheme.\n- `midnight`\n- `paper`\n\n## Appearance Type Sets\n\nThe font pairing for a user's app appearance ([`appearance_type_set`](/api/users#model)).\n\n- `system`: Default. The operating system's native font stack.\n- `grotesk`\n- `editorial`\n- `geometric`\n\n## Appearance Accents\n\nThe accent color for a user's app appearance ([`appearance_accent`](/api/users#model)).\n\n- `orange`: Default.\n- `azure`\n- `burgundy`\n- `ink`\n- `emerald`\n- `violet`\n\n## Appearance Scales\n\nThe UI scale (density) for a user's app appearance ([`appearance_scale`](/api/users#model)). Applies at tablet and desktop widths; mobile always renders at full size.\n\n- `comfortable`: 100% scale.\n- `cozy`: 90% scale.\n- `compact`: Default. 80% scale, fits more on screen.\n\n## Ingest Pipeline Error Reasons\n\nThe reasons behind the Errors series on a server's Ingest Pipeline chart. Every reason is a refusal issued before acceptance: the sender received an error response and nothing was recorded from that request. Accepted data is never dropped. It is queued on the server and retried until it lands.\n\nReasons where retrying the same request delivers the data. Tailglow SDKs retry these automatically; if you send raw HTTP, retry on any 5xx response:\n\n- `connection_closed`: The sender closed the connection before the upload finished.\n- `server_error`: An unexpected error while handling the request.\n- `spool_full_disk_gate`: The server paused intake to protect its disk from filling.\n- `spool_full_storage_write`: A buffer write failed while accepting the request.\n- `spool_full_ingest_queue`: The intake queue was momentarily full.\n- `spool_full_wal_unavailable`: The write buffer was restarting.\n- `auth_unavailable`: The ingest key could not be verified because the verification service was unreachable. The key itself was not rejected.\n\nReasons where the request itself must change. Retrying the same request is refused again:\n\n- `auth_missing_credentials`: No ingest key was provided.\n- `auth_invalid_credentials`: The ingest key is wrong or revoked.\n- `payload_too_large`: The payload exceeds the size limit.\n- `request_validation_failed`: The request body or parameters are invalid.\n- `frame_invalid`: A frame in the payload could not be parsed.\n- `empty_payload`: The request carried no data.\n- `source_deleted`: The target source no longer exists.\n",
|
|
19
19
|
"api/projects": "# Projects\n\n## Project Model\n\n### Fields\n\n- **`object`** `\"project\"`\n\n- **`id`** `string`\n\n Project identifier prefixed with `prj_`.\n\n- **`team_id`** `string`\n\n- **`team_name`** `string`\n\n- **`trial_expires_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n This team's trial lifecycle window; promotional units are shared across its projects.\n\n- **`name`** `string`\n\n- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n Metrics collection begins at this time.\n\n- **`created_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n- **`deleted_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n Scheduled deletion time; `null` when deletion is not scheduled.\n\n- **`last_activity_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n Most recent authenticated ingest time; `null` if none has occurred.\n\n- **`late_data_window_minutes`** `number`\n\n How many minutes behind the completeness watermark an event with an overridden timestamp may\n arrive and still be included automatically. Later data is recorded and surfaced for manual\n re-backfill. 0 means the watermark is final as it advances. Data sent live is never affected.\n\n- **`server_endpoint`** `string | null`\n\n Project-specific ingest endpoint; `null` while unavailable.\n\n- **`queued_server_count`** `number | null`\n\n Server count a scale request asked for that is waiting to be applied, because a platform update\n is in progress or the project has not finished updating to the current platform version. `null`\n when nothing is queued.\n\n- **`storage_gb`** `number`\n\n Stored data for this project, in gigabytes of uncompressed data: raw ingested files and\n artifacts, plus view output and minute-level rollups. Recalculated hourly; `billing_updated_at`\n is the last refresh.\n\n- **`billing_period_server_months`** `number`\n\n Server-months this project's servers have accrued in the current calendar-month billing period,\n counted up to the last refresh.\n\n- **`billing_updated_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n Last billing-cache refresh; `null` before the first calculation.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Late Data Model\n\n### Fields\n\n- **`object`** `\"late_data_file\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `ldf_`.\n\n- **`view_id`** `string`\n\n- **`view_name`** `string`\n\n Display name of the view whose data carried the late events.\n\n- **`excluded_count`** `number`\n\n How many events were kept out of charts because their timestamps fell behind the project's late\n data window. The events themselves are stored; only their aggregation waits.\n\n- **`min_event_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n The earliest event time among the excluded events. A re-backfill folds back to this instant.\n\n- **`max_event_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n The latest event time among the excluded events.\n\n- **`exclusion_below`** [`ISODateString`](/api/projects#iso-date-string)\n\n The boundary the events fell behind. Events at or after it were included normally.\n\n- **`refold_queued_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n When a re-backfill was requested for these events; null until requested.\n\n- **`refolded_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n When the re-backfill finished folding the events into charts; null until complete.\n\n- **`created_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Live Pipeline Model\n\n### Fields\n\n- **`object`** `\"project_pipeline_live\"`\n\n- **`observed_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n- **`pending_records`** `number | null`\n\n Valid spooled records awaiting write, including processing. Null when the queue is unknown.\n\n- **`processing_records`** `number | null`\n\n Pending records currently being processed.\n\n- **`pending_observed_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n Oldest contributing queue observation, independent of the server heartbeat.\n\n- **`pending_is_estimated`** `boolean`\n\n The queue changed during inspection; available counts are estimates from durable records.\n\n- **`state`** `\"fresh\" | \"stale\" | \"unavailable\"`\n\n Queue freshness across all current project servers, including deployment surges.\n\n- **`contributing_servers`** `number`\n\n- **`fresh_servers`** `number`\n\n- **`window_start_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n Inclusive start of the last completed minute.\n\n- **`window_end_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n Exclusive end of the last completed minute.\n\n- **`throughput_records`** `number | null`\n\n Records processed in the completed minute. Null when any contributor lacks telemetry.\n\n- **`errors`** `number | null`\n\n Rejected requests and dropped frames in the completed minute, not a record count.\n\n- **`pipeline_error_counts`** `Partial | null`\n\n Map from each `PipelineErrorReason` key to its error count for the completed minute. Omitted\n keys had no errors; `null` means telemetry is incomplete.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### PipelineErrorReason\n\n`\"auth_missing_credentials\" | \"auth_invalid_credentials\" | \"payload_too_large\" | \"request_validation_failed\" | \"server_error\" | \"spool_full_disk_gate\" | \"spool_full_storage_write\" | \"spool_full_ingest_queue\" | \"spool_full_wal_unavailable\" | \"frame_invalid\" | \"empty_payload\" | \"source_deleted\" | \"connection_closed\" | \"auth_unavailable\"`\n\n## Usage Sample Model\n\n### Fields\n\n- **`object`** `\"usage_sample\"`\n\n- **`sampled_at`** [`ISODateString`](/api/projects#iso-date-string)\n\n When the reading was taken. Readings are hourly, on the hour. With `interval=day` this is the\n timestamp of the day's last reading, not midnight.\n\n- **`collection_gb`** `number`\n\n Gigabytes of raw event data held in the project's collections, measured uncompressed, exactly\n as it is billed.\n\n- **`artifact_gb`** `number`\n\n Gigabytes of artifacts extracted from the project's events, measured uncompressed.\n\n- **`view_gb`** `number`\n\n Gigabytes of view records the project's views produced, measured uncompressed.\n\n- **`rollup_gb`** `number`\n\n Gigabytes of rollup data behind the project's charts, measured uncompressed. Counts the minute\n tier only, matching what invoices charge for.\n\n- **`total_gb`** `number`\n\n The four components added together: everything the project stored at this reading.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Attention Item Model\n\n### Fields\n\n- **`object`** `\"attention_item\"`\n\n- **`type`** [`AttentionItemType`](/api/projects#attention-item-type)\n\n- **`tier`** [`AttentionTier`](/api/projects#attention-tier)\n\n- **`resource_id`** `string | null`\n\n The resource to open to act on the condition: the metric behind a firing monitor, the view\n behind a failing transform, otherwise the check, view, metric, drain, pull or server itself.\n Null when the item covers the project as a whole, which is the case for refused requests.\n\n- **`resource_name`** `string | null`\n\n The name shown for the condition, which can belong to a related resource: a firing monitor's\n item names the monitor while `resource_id` opens its metric. Null whenever `resource_id` is\n null.\n\n- **`count`** `number | null`\n\n How much of the condition there is, in the unit that fits it: requests refused (not records)\n over the last 24 hours, or the last hour for capacity, series a monitor is firing on, a view's\n failing transforms, events waiting on a shape change, consecutive failures of a check, pull, or\n drain, or a hot server's average load in percent. Null when the condition has no quantity.\n\n- **`since`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n When the condition started, as accurately as Tailglow knows. Null when nothing on record marks\n a beginning: a check that has never once succeeded, or a server under sustained load, which is\n judged from an average rather than a start.\n\n- **`ended_at`** [`ISODateString | null`](/api/projects#iso-date-string)\n\n When the condition stopped, for a `resolved` item: to the minute within the last hour, to the\n end of the hour before that. Null while the condition is still true, including while a server\n that was refusing has not reported since.\n\n- **`detail`** `string | null`\n\n A short machine-readable qualifier that narrows the type: the refusal reason group, the firing\n monitor's id, or `cpu` or `mem` for the load that made a server hot. A stable identifier meant\n for branching in code, never a sentence to display. Null when the type needs no qualifier.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### AttentionItemType\n\n`\"ingest_refusals\" | \"capacity_refusals\" | \"monitor_firing\" | \"check_down\" | \"transform_error\" | \"metric_error\" | \"join_error\" | \"shape_deferral\" | \"pull_suspended\" | \"drain_suspended\" | \"drain_unverified\" | \"servers_hot\"`\n\nThe condition an attention item reports. Every value is a state Tailglow entered on its own, so\nnothing a project deliberately turned off is ever reported here.\n\n#### AttentionTier\n\n`\"losing_data\" | \"firing\" | \"stalled\" | \"suspended\" | \"resolved\"`\n\nHow urgent an attention item is, most urgent first. `losing_data` means requests are being\nrefused right now in a way that loses data unless the sender acts, or were until a server that\nwas refusing stopped reporting. `firing` means something the project asked to be told about is\ncurrently true, or a server has run under sustained heavy load. `stalled` means a pipeline\nstopped making progress and existing data is going stale. `suspended` means Tailglow switched\nsomething off and it stays off until it is fixed. `resolved` means refusals that lost data have\nstopped; the item stays for a while so the loss is still visible, and says when it ended.\n\n## List Projects\n\n### Endpoint\n\nRetrieve a list of projects for the current team.\n\n```http\nGET /v1/projects\n```\n\n**Scope:** `projects:read`\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the projects. Optional. Defaults to `\"name\"`. Allowed values: `\"created_at\"`, `\"name\"`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Project[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- When neither `sort` nor `order_by` is provided, the route defaults `sort` to `\"asc\"`.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Project\n\n### Endpoint\n\nRetrieve a single project.\n\n```http\nGET /v1/projects/:project_id\n```\n\n**Scope:** `projects:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Response\n\nProject retrieved\n\n```ts\n{\n message: string;\n data: Project;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Retrieve Project Pipeline\n\n### Endpoint\n\nRetrieve ingest activity across all of a project's servers.\n\n```http\nGET /v1/projects/:project_id/pipeline\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)\n Start time (ISO format, default: 12 hours ago). Optional.\n\n- **`end_at`** [`ISODateString`](/api/projects#iso-date-string)\n End time (ISO format, default: now). Optional.\n\n- **`interval`** `string`\n Aggregation interval. Optional. Defaults to `\"minute\"`. Allowed values: `\"minute\"`, `\"hour\"`.\n\n### Response\n\nProject pipeline retrieved\n\n```ts\n{\n message: string;\n data: TimeseriesAggregation;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Includes temporary deployment servers and their archived history. Pending averages valid queued records; throughput counts drained records. Older pending history without record counts is unknown.\n\n## Retrieve Live Project Pipeline\n\n### Endpoint\n\nRetrieve the current project queue and ingest totals for the last completed minute.\n\n```http\nGET /v1/projects/:project_id/pipeline/live\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Response\n\nLive project pipeline retrieved\n\n```ts\n{\n message: string;\n data: ProjectPipelineLive;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Includes temporary deployment servers. Queue freshness is independent of heartbeat freshness. Observations taken while queues change return estimated counts from durable records; unreadable observations or unknown write outcomes return null counts.\n- Throughput and error totals cover the last completed minute. Errors count requests or frames, and totals are null when telemetry is incomplete.\n\n## Create Project\n\n### Endpoint\n\nCreate a new project for the current team.\n\n```http\nPOST /v1/projects\n```\n\n**Scope:** `projects:write`\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Display name for the project. Minimum length: `1`. Maximum length: `60`.\n\n- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)\n Date and time when the project began collecting data. Optional.\n\n### Response\n\nYour project has been created.\n\n```ts\n{\n message: string;\n data: Project;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Update Project\n\n### Endpoint\n\nUpdate an existing project.\n\n```http\nPOST /v1/projects/:project_id\n```\n\n**Scope:** `projects:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string`\n Display name for the project. Optional. Minimum length: `1`. Maximum length: `60`.\n\n- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)\n Date and time when the project began collecting data. Optional.\n\n- **`late_data_window_minutes`** `integer`\n How many minutes behind the completeness watermark an event with an overridden timestamp may arrive and still be included automatically. Later data is recorded and surfaced for manual re-backfill instead of being included. 0 means the watermark is final as it advances. Only events with overridden timestamps can be late; data sent live is never affected. Maximum 129600 (90 days). Optional. Minimum: `0`. Maximum: `129600`.\n\n- **`deleted_at`** `null`\n Set to null to cancel a scheduled project deletion. Optional.\n\n### Response\n\nYour project has been updated.\n\n```ts\n{\n message: string;\n data: Project;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Delete Project\n\n### Endpoint\n\nSchedule a project for deletion.\n\n```http\nDELETE /v1/projects/:project_id\n```\n\n**Scope:** `projects:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Response\n\n```ts\n{\n message: string;\n data: Project;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The project is hidden immediately and permanently removed 7 days later. Restore it before then by updating it with `deleted_at` set to null.\n\n## Force Delete Project\n\n### Endpoint\n\nDelete a project permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/force\n```\n\n**Scope:** `projects:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Response\n\nProject queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live project as well as one already scheduled for deletion.\n- Every source, collection, view, metric and record in the project is destroyed immediately, and its servers are torn down. Nothing here can be restored.\n\n## List Late Data\n\n### Endpoint\n\nRetrieve the events held out of charts by the project's late data window, grouped per data file.\n\n```http\nGET /v1/projects/:project_id/late_data\n```\n\n**Scope:** `projects:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the late data records. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"min_event_at\"`.\n\n- **`refolded`** `string`\n Filter by re-backfill state: false returns records whose events are still excluded from charts, true returns records a completed re-backfill has folded in. Optional. Allowed values: `\"true\"`, `\"false\"`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: LateDataFile[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Events with overridden timestamps beyond the late data window are stored and counted here instead of entering charts. Data sent live is never held.\n- Records with `refolded_at` set have already been folded into charts by a completed re-backfill; filter with `refolded=false` for records still waiting.\n- `after` and `before` are mutually exclusive.\n\n## Re-backfill Late Data\n\n### Endpoint\n\nStart a re-backfill that folds one late data record's events into charts.\n\n```http\nPOST /v1/projects/:project_id/late_data/:late_data_file_id/re_backfill\n```\n\n**Scope:** `projects:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`late_data_file_id`** `string` -- **Required**\n Unique identifier of the late data.\n\n### Response\n\nRe-backfill queued. The events will appear in charts as the fold completes.\n\n```ts\n{\n message: string;\n data: LateDataFile;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Idempotent: requesting a re-backfill that is already queued or complete returns the record unchanged.\n- The fold is bounded to the excluded events, so data already in charts is never counted twice. `refolded_at` is set once the fold's durable receipts land.\n\n## Retrieve Project Usage\n\n### Endpoint\n\nRetrieve how much the project stored over a time range.\n\n```http\nGET /v1/projects/:project_id/usage\n```\n\n**Scope:** `projects:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`start_at`** [`ISODateString`](/api/projects#iso-date-string) -- **Required**\n Start of the range, inclusive (ISO format).\n\n- **`end_at`** [`ISODateString`](/api/projects#iso-date-string)\n End of the range, inclusive (ISO format, default: now). Optional.\n\n- **`interval`** `string`\n Reading cadence returned: every hourly reading, or one reading per UTC day. Optional. Defaults to `\"hour\"`. Allowed values: `\"hour\"`, `\"day\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: UsageSample[];\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Readings are taken hourly and are the same measurements the project is billed on. Gigabytes are logical (uncompressed) bytes.\n- `interval=day` returns the last reading of each UTC day rather than an average, so a row is what the project held when the day closed. Its `sampled_at` is that reading's own hour.\n- Readings start when Tailglow began recording them, so a range reaching further back returns nothing for the hours before that.\n- `start_at` must be earlier than `end_at`, and the range cannot be longer than 400 days.\n\n## Retrieve Project Attention\n\n### Endpoint\n\nRetrieve everything in the project that is asking for a human right now.\n\n```http\nGET /v1/projects/:project_id/attention\n```\n\n**Scope:** `projects:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Response\n\n```ts\n{\n message: string;\n data: AttentionItem[];\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- An item appears only when Tailglow entered a state on its own. Anything a project chose, such as a paused pull, a paused drain, or a paused monitor, is never reported here.\n- `tier` orders the list. `losing_data` means requests are being refused right now, within the last 5 minutes: for a reason a retry cannot fix, or for capacity after at least 5 minutes of unbroken refusals. `firing` means a monitor or check the project set up is currently triggered, or a server has run under sustained heavy load. `stalled` means a pipeline stopped making progress and its data is going stale. `suspended` means Tailglow switched something off and it stays off until it is fixed. `resolved` means refusals that lost data have stopped; `ended_at` says when.\n- Items disappear the moment their condition clears, with one exception so a loss is not missed: refusals that stop stay as `resolved`, for up to 24 hours for a key or payload refusal and up to an hour for capacity. A refusal item's `count` covers that whole window, not only the current spell. Refusals only count as stopped once every running server that was refusing has reported since; until then the item stays `losing_data`. Readings from the project's servers are cached briefly, so a condition that just started can take up to a minute to appear.\n- Items cover only resources the caller's key or role can read: without a resource's read permission, its conditions are left out. Refusal and server load items need `servers:read`, and firing monitors need `alerts:read`.\n- Items are already ranked, most urgent first. One item covers each firing monitor, and each view's failing transforms, joins, or shape waits. The response is capped per condition so a project in a bad state returns a readable list rather than every affected resource.\n\n",
|
|
20
20
|
"api/sources": "# Sources\n\n## Source Model\n\n### Fields\n\n- **`object`** `\"source\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `src_`.\n\n- **`project_id`** `string`\n\n- **`project_name`** `string`\n\n- **`name`** `string`\n\n Unique within its project.\n\n- **`storage_gb`** `number`\n\n Data this source has ingested, in gigabytes of uncompressed data: collection files plus\n uploaded artifacts. Recalculated hourly.\n\n- **`schema_count`** `number`\n\n How many schema versions this source has produced.\n\n- **`collections_count`** `number`\n\n How many collections this source feeds.\n\n- **`deleted_at`** [`ISODateString | null`](/api/sources#iso-date-string)\n\n When the source is scheduled to be deleted; null when it is not.\n\n- **`created_at`** [`ISODateString`](/api/sources#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/sources#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Sources\n\n### Endpoint\n\nRetrieve a list of sources for a project.\n\n```http\nGET /v1/projects/:project_id/sources\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the sources. Optional. Defaults to `\"name\"`. Allowed values: `\"created_at\"`, `\"name\"`, `\"updated_at\"`.\n\n- **`project_id`** `string`\n Filter to sources belonging to a single project. Optional.\n\n- **`deleted_at`** [`NullableDateFilter`](/api/sources#nullable-date-filter)\n Filter by scheduled deletion date. Use `null` for sources that are not scheduled for deletion, `not:null` for sources that are. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Source[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Source\n\n### Endpoint\n\nRetrieve a single source.\n\n```http\nGET /v1/projects/:project_id/sources/:source_id\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n### Response\n\nSource retrieved\n\n```ts\n{\n message: string;\n data: Source;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Source\n\n### Endpoint\n\nCreate a source in a project.\n\n```http\nPOST /v1/projects/:project_id/sources\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Display name for the source. Minimum length: `1`. Maximum length: `128`.\n\n- **`project_id`** `string`\n Project the source belongs to. Ignored when the nested path supplies it. Optional.\n\n### Response\n\nSource created\n\n```ts\n{\n message: string;\n data: Source;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The project is supplied by the path.\n- Source names are unique within a project.\n\n## Update Source\n\n### Endpoint\n\nUpdate a source.\n\n```http\nPOST /v1/projects/:project_id/sources/:source_id\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n### Request Body\n\n- **`name`** `string`\n Display name for the source. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`deleted_at`** `null`\n Send `null` to restore a source that is scheduled for deletion. The deletion date itself is set by the delete endpoint and cannot be written here. Optional.\n\n### Response\n\nSource updated\n\n```ts\n{\n message: string;\n data: Source;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Send `deleted_at` as `null` to restore a source that is scheduled for deletion.\n\n## Delete Source\n\n### Endpoint\n\nSchedule a source for deletion.\n\n```http\nDELETE /v1/projects/:project_id/sources/:source_id\n```\n\n**Scope:** `sources:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n### Response\n\n```ts\n{\n message: string;\n data: Source;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The source is hidden immediately and permanently removed 7 days later. Restore it before then by updating it with `deleted_at` set to null.\n\n## Force Delete Source\n\n### Endpoint\n\nDelete a source permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/sources/:source_id/force\n```\n\n**Scope:** `sources:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n### Response\n\nSource queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live source as well as one already scheduled for deletion.\n- Every collection, record and file under the source is destroyed immediately. Nothing here can be restored.\n\n",
|
|
21
21
|
"api/ingest_keys": "# Ingest Keys\n\n## Ingest Key Model\n\n### Fields\n\n- **`object`** `\"ingest_key\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `ik_`.\n\n- **`source_id`** `string`\n\n The source this key writes into.\n\n- **`source_name`** `string | null`\n\n- **`url`** `string | null`\n\n The Tailglow-issued address to send records to, ready to use. POST your payload to it and\n append `&collection=<name>` to choose where the records land; a collection is created the first\n time data arrives for it. Lasts as long as the project does. Null until the project has a\n provisioned server, which is the only window in which no address reaches it.\n\n- **`custom_urls`** `string[]`\n\n The same source reached through the team's verified custom ingest domains, one entry each,\n every one carrying the key exactly as `url` does. Empty when none are configured. Prefer these\n for browser traffic, where a first-party host survives the blockers that reject a shared\n analytics domain. An entry disappears if its domain is removed or stops resolving.\n\n- **`key`** `string`\n\n The credential embedded in `url`. Publishable, in the sense that it belongs in client-side code\n the way an analytics site tag does. It permits writing records into this source and nothing\n else: it cannot read, and it carries no access to anything outside this source.\n\n- **`status`** [`IngestKeyStatus`](/api/ingest_keys#ingest-key-status)\n\n Whether records sent to this URL are still accepted.\n\n- **`created_at`** [`ISODateString`](/api/ingest_keys#iso-date-string)\n\n- **`last_used_at`** [`ISODateString | null`](/api/ingest_keys#iso-date-string)\n\n When data was last received at this URL; null if it has never been used.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### IngestKeyStatus\n\n`\"active\" | \"revoked\" | \"disabled\"`\n\n## List Ingest Keys\n\n### Endpoint\n\nRetrieve every address that can send records to a source.\n\n```http\nGET /v1/projects/:project_id/sources/:source_id/ingest_keys\n```\n\n**Scope:** `ingest_keys:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the ingest keys. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"status\"`.\n\n- **`status`** [`IngestKeyStatus`](/api/ingest_keys#ingest-key-status)\n Filter by whether the URL still accepts records. Optional.\n\n- **`project_id`** `string`\n Filter to URLs whose source belongs to a project. Optional.\n\n- **`source_id`** `string`\n Filter to URLs belonging to a single source. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: IngestKey[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Ingest Key\n\n### Endpoint\n\nRetrieve a single ingest key.\n\n```http\nGET /v1/projects/:project_id/sources/:source_id/ingest_keys/:ingest_key_id\n```\n\n**Scope:** `ingest_keys:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n- **`ingest_key_id`** `string` -- **Required**\n Unique identifier of the ingest key.\n\n### Response\n\nIngest key retrieved\n\n```ts\n{\n message: string;\n data: IngestKey;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Ingest Key\n\n### Endpoint\n\nCreate an ingest key for a source. Use this to rotate: create the replacement, deploy it, then delete the old one.\n\n```http\nPOST /v1/projects/:project_id/sources/:source_id/ingest_keys\n```\n\n**Scope:** `ingest_keys:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n### Request Body\n\n- **`source_id`** `string`\n Source the URL writes into. Ignored when the nested path supplies it. Optional.\n\n### Response\n\nIngest key created.\n\n```ts\n{\n message: string;\n data: IngestKey;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The source is supplied by the path.\n- The source has to belong to your team.\n- The full `key` is returned on create and never again. Store it when you receive it.\n\n## Delete Ingest Key\n\n### Endpoint\n\nDelete an ingest key. Records already sent to it are unaffected; the address stops being accepted.\n\n```http\nDELETE /v1/projects/:project_id/sources/:source_id/ingest_keys/:ingest_key_id\n```\n\n**Scope:** `ingest_keys:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`source_id`** `string` -- **Required**\n Unique identifier of the source.\n\n- **`ingest_key_id`** `string` -- **Required**\n Unique identifier of the ingest key.\n\n### Response\n\nIngest key deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Deletion is immediate. Anything still sending with this key starts failing straight away.\n\n",
|
|
22
|
-
"api/collections": "# Collections\n\n## Collection Model\n\n### Fields\n\n- **`object`** `\"collection\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `col_`.\n\n- **`source_id`** `string`\n\n- **`source_name`** `string | null`\n\n- **`project_id`** `string`\n\n- **`slug`** `string`\n\n Permanent identifier used in endpoints. Cannot be changed after creation.\n\n- **`name`** `string`\n\n- **`storage_gb`** `number`\n\n Data stored in this collection's files, in gigabytes of uncompressed data. Recalculated hourly.\n\n- **`schema_count`** `number`\n\n How many schema versions this collection has, in every state.\n\n- **`views_count`** `number`\n\n How many views read from this collection.\n\n- **`drains_count`** `number`\n\n How many drains export from this collection.\n\n- **`schema_opaque_paths`** `string[][]`\n\n Object paths whose contents do not take part in schema identity, one key segment per entry.\n Records that differ only inside an opaque path share one schema version. Changing this re-keys\n the collection.\n\n- **`schema_tracked_paths`** `string[][]`\n\n Children of opaque paths that stay part of schema identity. Changing this re-keys the\n collection.\n\n- **`schema_max_depth`** `number`\n\n How many levels of nesting take part in schema identity. Changing this re-keys the collection.\n\n- **`schema_nulls`** [`SchemaRuleMode`](/api/collections#schema-rule-mode)\n\n `auto` accepts null and absent values where a mapping reads; `manual` waits for a person per\n view.\n\n- **`schema_optional_keys`** [`SchemaRuleMode`](/api/collections#schema-rule-mode)\n\n `auto` lets a record with a subset of a version's keys join it; `manual` treats absence as a\n new shape.\n\n- **`schema_detector`** [`SchemaRuleMode`](/api/collections#schema-rule-mode)\n\n `auto` applies a safe opaque-path proposal on its own; `manual` holds every proposal for a\n person.\n\n- **`schema_recurrence_gap_ms`** `number`\n\n How long after first sight a shape must recur before its version settles, in milliseconds.\n\n- **`schema_bulk_rows`** `number`\n\n A single batch carrying this many rows of a shape settles its version at once; `0` disables it.\n\n- **`schema_stale_after_ms`** `number`\n\n How long a candidate version waits to recur before it goes stale, in milliseconds.\n\n- **`schema_identity_limit`** `number`\n\n How many settled versions the collection may hold; past it, new shapes stay unclassified.\n\n- **`schema_candidate_pool`** `number`\n\n How many candidate and stale versions the collection may hold; past it, new shapes stay\n unclassified.\n\n- **`schema_alias_pool`** `number`\n\n How many exact signature hashes the collection caches for lookup.\n\n- **`schema_writer_limit`** `number`\n\n How many collection files one ingest batch may open for this collection. Shapes past the\n candidate pool get the same number of files per batch, the shapes with the most rows first; the\n rows of shapes past that budget stay unclassified and cannot be linked to a version later.\n\n- **`schema_unclassified_retention_days`** `number | null`\n\n How many days rows without a settled version are kept; null keeps them forever.\n\n- **`deleted_at`** [`ISODateString | null`](/api/collections#iso-date-string)\n\n When the collection is scheduled to be deleted; null when it is not.\n\n- **`created_at`** [`ISODateString`](/api/collections#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/collections#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### SchemaRuleMode\n\n`\"auto\" | \"manual\"`\n\n## Collection Schema Report\n\n### Fields\n\n- **`object`** `\"collection_schema_report\"`\n\n- **`collection_id`** `string`\n\n- **`rules`** `{ opaque_paths: string[][]; tracked_paths: string[][]; max_depth: number; }`\n\n The rules the report reads the collection's shapes under: the collection's own for the\n retrieve, the rules sent for a preview.\n\n- **`shapes`** `{ before: number; after: number; }`\n\n How many shapes the collection holds under its current rules (`before`) and under the report's\n rules (`after`). Equal unless the report previews a rule change; the difference is what a\n re-key would merge.\n\n- **`proposals`** [`CollectionSchemaProposal[]`](/api/collections#schema-proposal)\n\n Where the detector sees keys being invented, with what applying each proposal would do.\n\n- **`keys_look_like_data`** `boolean`\n\n Whether every shape's top-level keys are its own, such as records keyed by a date. Those keys\n are values, and no opaque path can bring the shapes together.\n\n- **`unclassified`** `{ shapes: number; rows: number; }`\n\n Shapes past the collection's pools, stored with their signature but no version, and the rows\n they carry. Nothing reads them until the pool frees or a rule brings them under a version.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Collection Schema Proposal\n\n### Fields\n\n- **`object`** `\"collection_schema_proposal\"`\n\n- **`path`** `string[]`\n\n The path the detector proposes opaque, as key segments.\n\n- **`tracked`** `string[]`\n\n Children of the path every shape carries with one type, which stay in schema identity as\n tracked paths when the proposal is applied.\n\n- **`content_children`** `number`\n\n How many children of the path read as content: keys that keep being invented.\n\n- **`read_by_mapping`** `boolean`\n\n Whether a bound mapping reads inside the path. Applying the proposal would leave that mapping\n unverified, so the detector never applies such a proposal on its own; a person can.\n\n- **`shapes_after`** `number`\n\n How many shapes the collection would hold with this proposal applied.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Collections\n\n### Endpoint\n\nRetrieve a list of collections for a project.\n\n```http\nGET /v1/projects/:project_id/collections\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the collections. Optional. Defaults to `\"name\"`. Allowed values: `\"created_at\"`, `\"name\"`, `\"updated_at\"`.\n\n- **`source_id`** `string`\n Filter to collections under a single source. Optional.\n\n- **`project_id`** `string`\n Filter to collections in a single project. Optional.\n\n- **`deleted_at`** [`NullableDateFilter`](/api/collections#nullable-date-filter)\n Filter by scheduled deletion date. Use `null` for collections that are not scheduled for deletion, `not:null` for collections that are. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Collection[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Collection\n\n### Endpoint\n\nRetrieve a single collection.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\nCollection retrieved\n\n```ts\n{\n message: string;\n data: Collection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Collection\n\n### Endpoint\n\nCreate a collection under a source.\n\n```http\nPOST /v1/projects/:project_id/collections\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Display name for the collection. Minimum length: `1`. Maximum length: `128`.\n\n- **`slug`** `string`\n Permanent identifier used in ingest URLs. Derived from `name` when omitted. Optional. Minimum length: `1`. Maximum length: `64`.\n\n- **`source_id`** `string` -- **Required**\n Source the collection is created under.\n\n### Response\n\nCollection created\n\n```ts\n{\n message: string;\n data: Collection;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `source_id` is required.\n- `slug` is permanent. It appears in the ingest URL, so pick it deliberately.\n\n## Update Collection\n\n### Endpoint\n\nUpdate a collection.\n\n```http\nPOST /v1/projects/:project_id/collections/:collection_id\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Request Body\n\n- **`name`** `string`\n Display name for the collection. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`expected_updated_at`** [`ISODateString`](/api/collections#iso-date-string)\n Reject this update if the collection changed since this inspected update time. Optional.\n\n- **`schema_opaque_paths`** `string[][]`\n Object paths whose contents do not take part in schema identity, each written as its key segments (`[\"metadata\", \"id\"]`). A path that names an array covers its elements and their children. Records that differ only inside an opaque path share one schema version; the raw values are still stored. Changing this re-keys the collection. Optional.\n\n- **`schema_tracked_paths`** `string[][]`\n Children of opaque paths that stay part of schema identity, written like `schema_opaque_paths`. Changing this re-keys the collection. Optional.\n\n- **`schema_max_depth`** `integer`\n How many levels of nesting take part in schema identity. Changing this re-keys the collection. Optional. Minimum: `1`. Maximum: `10`.\n\n- **`schema_nulls`** `string`\n `auto` accepts null and absent values at any path a mapping reads; `manual` waits for a person to approve each nullable input per view. Optional. Allowed values: `\"auto\"`, `\"manual\"`.\n\n- **`schema_optional_keys`** `string`\n `auto` lets a record whose keys are a subset of a version's keys join that version; `manual` treats every absent key as a different shape. Optional. Allowed values: `\"auto\"`, `\"manual\"`.\n\n- **`schema_detector`** `string`\n `auto` applies a safe opaque-path proposal from the detector on its own; `manual` holds every proposal for a person. Optional. Allowed values: `\"auto\"`, `\"manual\"`.\n\n- **`schema_recurrence_gap_ms`** `integer`\n How long after first sight a shape must be seen again before its version settles, in milliseconds. Optional. Minimum: `60000`. Maximum: `604800000`.\n\n- **`schema_bulk_rows`** `integer`\n A single batch carrying at least this many rows of a shape settles its version immediately. `0` disables the bulk path. Optional. Minimum: `0`. Maximum: `1000000`.\n\n- **`schema_stale_after_ms`** `integer`\n How long a candidate version waits to be seen again before it goes stale, in milliseconds. Optional. Minimum: `3600000`. Maximum: `7776000000`.\n\n- **`schema_identity_limit`** `integer`\n How many settled schema versions the collection may hold. Past it, new shapes stay unclassified with their signatures kept. Optional. Minimum: `1`. Maximum: `4096`.\n\n- **`schema_candidate_pool`** `integer`\n How many candidate and stale versions the collection may hold. Past it, new shapes stay unclassified. Optional. Minimum: `1`. Maximum: `8192`.\n\n- **`schema_alias_pool`** `integer`\n How many exact signature hashes the collection remembers for fast lookup. Past it, a new hash is still resolved but not cached. Optional. Minimum: `16`. Maximum: `100000`.\n\n- **`schema_writer_limit`** `integer`\n How many collection files one ingest batch may open for this collection. Shapes past the candidate pool get the same number of files per batch, the shapes with the most rows first; the rows of shapes past that budget stay unclassified and cannot be linked to a version later. Optional. Minimum: `1`. Maximum: `256`.\n\n- **`schema_unclassified_retention_days`** `integer | null`\n How many days rows without a settled schema version are kept before they are deleted. `null` keeps them forever. Optional.\n\n- **`deleted_at`** `null`\n Send `null` to restore a collection that is scheduled for deletion. The deletion date itself is set by the delete endpoint and cannot be written here. Optional.\n\n### Response\n\nCollection updated\n\n```ts\n{\n message: string;\n data: Collection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `slug` cannot be changed after creation.\n- Send `deleted_at` as `null` to restore a collection that is scheduled for deletion.\n\n## Delete Collection\n\n### Endpoint\n\nSchedule a collection for deletion.\n\n```http\nDELETE /v1/projects/:project_id/collections/:collection_id\n```\n\n**Scope:** `sources:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\n```ts\n{\n message: string;\n data: Collection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The collection is hidden immediately and permanently removed 7 days later. Restore it before then by updating it with `deleted_at` set to null.\n\n## Force Delete Collection\n\n### Endpoint\n\nDelete a collection permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/collections/:collection_id/force\n```\n\n**Scope:** `sources:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\nCollection queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live collection as well as one already scheduled for deletion.\n- Every record, schema version and file in the collection is destroyed immediately.\n\n## List Collection Schemas\n\n### Endpoint\n\nRetrieve the schema versions for a collection.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id/schemas\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n- **`state`** `string`\n Return only schema versions in this state. Optional. Allowed values: `\"candidate\"`, `\"settled\"`, `\"stale\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: CollectionSchemaVersion[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `state` is `candidate` from first sight, `settled` once the shape recurs or arrives in bulk, and `stale` when a candidate is not seen again. Only a settled version is offered to views for authoring.\n- `after` and `before` are mutually exclusive.\n\n## Update Collection Schema\n\n### Endpoint\n\nSettle a schema version by hand, so views can author against it before its shape recurs.\n\n```http\nPOST /v1/projects/:project_id/collections/:collection_id/schemas/:schema_version_id\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n- **`schema_version_id`** `string` -- **Required**\n Unique identifier of the schema.\n\n### Request Body\n\n- **`state`** `string` -- **Required**\n The only state a person sets. `settled` trusts the version for view authoring now, without waiting for its shape to recur; a settled version never goes back. Allowed values: `\"settled\"`.\n\n### Response\n\nSchema version settled\n\n```ts\n{\n message: string;\n data: CollectionSchemaVersion;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `state` accepts only `settled`; a settled version never returns to candidate or stale.\n- A version that is already settled is left as it is and responds with `request_no_update`.\n\n## Retrieve Collection Schema Report\n\n### Endpoint\n\nRetrieve the detector's view of a collection: the opaque paths it proposes, whether the top-level keys are data, and how many shapes sit unclassified past the collection's pools.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id/schema_report\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\nCollection schema report retrieved\n\n```ts\n{\n message: string;\n data: CollectionSchemaReport;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A proposal a bound mapping reads inside is reported and never applied on its own, whatever the detector switch; apply it by adding its path to the collection's `schema_opaque_paths` and its tracked children to `schema_tracked_paths`.\n- `shapes.before` and `shapes.after` are equal here; the preview is where they differ.\n\n## Preview Collection Schema Rules\n\n### Endpoint\n\nPreview a rule change: the schema report the collection would read under the rules sent, with how many shapes it holds now and how many it would hold. Nothing is changed.\n\n```http\nPOST /v1/projects/:project_id/collections/:collection_id/schema_preview\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Request Body\n\n- **`schema_opaque_paths`** `string[][]`\n Opaque paths to preview, written like the collection's `schema_opaque_paths`. Omitted, the collection's current opaque paths apply. Optional.\n\n- **`schema_tracked_paths`** `string[][]`\n Tracked paths to preview, written like the collection's `schema_tracked_paths`. Omitted, the collection's current tracked paths apply. Optional.\n\n- **`schema_max_depth`** `integer`\n Max depth to preview. Omitted, the collection's current max depth applies. Optional. Minimum: `1`. Maximum: `10`.\n\n### Response\n\nCollection schema rules previewed\n\n```ts\n{\n message: string;\n data: CollectionSchemaReport;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A rule left out of the body keeps the collection's current value.\n- `shapes.before` counts the shapes under the collection's current rules and `shapes.after` under the rules sent; the difference is what updating the collection with those rules would merge.\n\n## List Collection Documents\n\n### Endpoint\n\nRetrieve raw documents from a collection.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id/docs\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: CollectionDoc[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Documents are returned exactly as they were sent, minus Tailglow's own reserved envelope.\n- Paging is forward-only. Pass `pagination.next_cursor` back as `after` to get the next page; no `prev_cursor` is issued, and `before` returns the first page.\n- `after` and `before` are mutually exclusive.\n\n",
|
|
23
|
-
"api/views": "# Views\n\n## View Model\n\n### Fields\n\n- **`mapping_policy_revision`** `number | null`\n\n- **`mapping_policy_nullable_inputs`** `string[]`\n\n- **`mapping_policy_fallback_groups`** `string[][]`\n\n- **`mapping_policy_allow_drops`** `boolean`\n\n- **`mapping_policy_output_bindings`** `Record<string, string[]>`\n\n- **`mapping_generation`** `number`\n\n- **`object`** `\"view\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `viw_`.\n\n- **`team_id`** `string`\n\n- **`project_id`** `string`\n\n- **`collection_id`** `string | null`\n\n The collection this view reads from; null when it reads from another view.\n\n- **`collection_name`** `string | null`\n\n Display name of that collection.\n\n- **`parent_view_id`** `string | null`\n\n The view this one is derived from; null when it reads from a collection.\n\n- **`parent_view_name`** `string | null`\n\n Display name of that view.\n\n- **`view_mode`** `string`\n\n Where the view's records come from: a collection, another view, or a join of several.\n\n- **`transform_mode`** [`ViewTransformMode`](/api/views#view-transform-mode)\n\n Whether transform scripts are generated for new schema versions or written by you.\n\n- **`name`** `string`\n\n- **`hint`** `string | null`\n\n Plain-language guidance the generator uses when it writes transform scripts.\n\n- **`output_schema`** `ViewOutputField[]`\n\n The fields this view's records contain.\n\n- **`status`** [`ViewStatus`](/api/views#view-status)\n\n Whether the view is producing records, still being set up, or failed.\n\n- **`error_message`** `string | null`\n\n Why the view failed, when its status is `error`.\n\n- **`waiting_shapes_count`** `number`\n\n How many event shapes arrived that no transform serves yet. Their records are stored and catch\n up automatically once a script covers them; the shapes are listed by the view's deferrals\n endpoint.\n\n- **`deleted_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the view is scheduled to be deleted; null when it is not.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`join_base_view_id`** `string | null`\n\n The base view on a join view; null on other views.\n\n- **`join_base_view_name`** `string | null`\n\n- **`join_status`** [`ViewJoinStatus | null`](/api/views#view-join-status)\n\n- **`join_backfill_cutoff_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n Records after this instant are joined live.\n\n- **`join_live_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the join started matching new records.\n\n- **`join_lookup_progress`** `LookupProgress | null`\n\n Lookup progress keyed by clause alias.\n\n- **`join_base_file_count`** `number | null`\n\n Number of base-view files to backfill.\n\n- **`join_base_files_done`** `number | null`\n\n Number of base-view files already processed.\n\n- **`join_error_message`** `string | null`\n\n- **`join_created_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`join_updated_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`joins`** `ViewJoinClause[]`\n\n The views joined onto the base view, on a join view.\n\n- **`metrics_count`** `number`\n\n How many metrics read from this view.\n\n- **`derived_views_count`** `number`\n\n How many views are derived from this one.\n\n- **`join_views_count`** `number`\n\n How many join views use this one as an input.\n\n- **`transform_count`** `number`\n\n How many transforms this view has.\n\n- **`drains_count`** `number`\n\n How many drains forward this view's records.\n\n- **`backfill_file_count`** `number`\n\n How many files every transform on this view has to process between them.\n\n- **`backfill_files_done`** `number`\n\n How many of those files they have processed.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the earliest of those backfills began. This is the earliest start across the whole view,\n which is what a throughput estimate has to measure elapsed time from.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewTransformMode\n\n`\"auto\" | \"manual\"`\n\n#### ViewStatus\n\n`\"empty\" | \"building\" | \"needs_transform\" | \"active\" | \"error\" | \"paused\" | \"cancelled\"`\n\n#### ViewJoinStatus\n\n`\"waiting_for_inputs\" | \"building_lookups\" | \"backfilling\" | \"active\" | \"error\"`\n\n## View Transform Model\n\n### Fields\n\n- **`object`** `\"view_transform\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `vtr_`.\n\n- **`view_id`** `string`\n\n The view this transform produces records for.\n\n- **`transform_script`** `string | null`\n\n The TGL script that maps incoming records onto the view's fields; null until one is written.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n\n Whether the transform is running, waiting for a script, or failed.\n\n- **`error_message`** `string | null`\n\n Why the transform failed, when its status is `error`.\n\n- **`tgl_retry_count`** `number`\n\n How many times script generation has been retried for this transform.\n\n- **`max_read_depth`** `number | null`\n\n How many levels deep into a record the script reads.\n\n- **`evidence_notice`** `ViewEvidenceNotice | null`\n\n Set when a path this mapping read as empty at authoring time has since carried a value with a\n type, listing those paths as `L<depth>:<path>` read keys. Rows keep flowing; re-author the\n mapping to use the new column. Cleared when a new script is generated.\n\n- **`backfill_status`** [`ViewTransformBackfillStatus`](/api/views#view-transform-backfill-status)\n\n Whether existing records have been reprocessed through this transform yet.\n\n- **`backfill_cutoff_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n Records after this time are handled live rather than by the backfill.\n\n- **`backfill_start_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n The earliest record the backfill will reach.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill began.\n\n- **`backfill_completed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill finished.\n\n- **`backfill_file_count`** `number`\n\n How many files the backfill has to process.\n\n- **`backfill_files_done`** `number`\n\n How many of those files it has processed.\n\n- **`backfill_error`** `string | null`\n\n Why the backfill failed, when it did.\n\n- **`assignments`** `ViewTransformAssignment[]`\n\n Backfill progress for each source schema version this transform covers.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewTransformStatus\n\n`\"queued\" | \"analyzing\" | \"active\" | \"error\" | \"draft\"`\n\n#### ViewTransformBackfillStatus\n\n`\"pending\" | \"enqueuing\" | \"in_progress\" | \"completed\" | \"failed\" | \"cancelled\"`\n\n## View Version Model\n\n### Fields\n\n- **`object`** `\"view_version\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `vta_`.\n\n- **`view_id`** `string`\n\n- **`version`** `number`\n\n The source schema version this row covers. Versions rise as the source's shape changes.\n\n- **`transform_id`** `string`\n\n The transform whose script serves this version. Several versions can share one transform.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n\n Whether the transform is running, waiting for a script, or failed.\n\n- **`error_message`** `string | null`\n\n Why the transform failed, when its status is `error`.\n\n- **`evidence_notice`** `ViewEvidenceNotice | null`\n\n Set when a path the serving script read as empty at authoring time has since carried a value\n with a type; lists those paths as `L<depth>:<path>` read keys. Re-author the mapping to use the\n new column. Cleared when a new script is generated.\n\n- **`script_hash`** `string | null`\n\n Identity of the script text. Versions sharing a hash run the identical script.\n\n- **`script`** `string | null`\n\n The TGL script serving this version; null until one is written.\n\n- **`backfill_status`** [`ViewTransformBackfillStatus`](/api/views#view-transform-backfill-status)\n\n Whether the records already on this schema version have been reprocessed yet.\n\n- **`backfill_start_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n The earliest record the backfill will reach.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill began.\n\n- **`backfill_completed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill finished.\n\n- **`backfill_file_count`** `number`\n\n How many files the backfill has to process for this version.\n\n- **`backfill_files_done`** `number`\n\n How many of those files it has processed.\n\n- **`backfill_error`** `string | null`\n\n Why the backfill failed, when it did.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n When the view started covering this schema version.\n\n- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)\n\n When the transform serving this version last changed.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewTransformStatus\n\n`\"queued\" | \"analyzing\" | \"active\" | \"error\" | \"draft\"`\n\n#### ViewTransformBackfillStatus\n\n`\"pending\" | \"enqueuing\" | \"in_progress\" | \"completed\" | \"failed\" | \"cancelled\"`\n\n## Waiting Shape Model\n\n### Fields\n\n- **`episode`** `number`\n\n- **`observation_count`** `string | null`\n\n- **`retained_rows`** `number`\n\n- **`first_observed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`last_observed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`next_action_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`hint_question`** `string | null`\n\n- **`hint_reason`** `string | null`\n\n- **`hint_paths`** `string[]`\n\n- **`hint_requested_at`** `string | null`\n\n- **`hint_mapping_generation`** `number | null`\n\n- **`exclusion_reason`** `string | null`\n\n- **`excluded_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`transform_id`** `string | null`\n\n- **`object`** `\"view_shape_deferral\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `vsd_`.\n\n- **`view_id`** `string`\n\n- **`full_signature`** `string`\n\n The typed signature identifying the event shape.\n\n- **`event_count`** `number`\n\n Roughly how many events of the shape arrived while it was waiting; retries can count an event\n more than once.\n\n- **`first_event_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n The earliest event time seen for the shape. Completeness reporting pins to this instant.\n\n- **`first_deferred_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`last_deferred_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`resolved_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the shape became served; null while it is still waiting.\n\n- **`reason`** [`ViewShapeDeferralReason`](/api/views#view-shape-deferral-reason)\n\n Why the shape is waiting: `collecting_samples` until enough evidence is available to author a\n mapping, `awaiting_script` until a transform covers it, `generation_failed` when script\n generation gave up, `catching_up` while its records backfill, `served` once resolved.\n\n- **`schema_version`** `number | null`\n\n The schema version the shape belongs to. Null while no version is linked to it.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewShapeDeferralReason\n\n`\"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\"`\n\nWhat unblocks a waiting shape.\n\n## Shape Inspection Model\n\n### Fields\n\n- **`view_id`** `string`\n\n- **`deferral_id`** `string`\n\n- **`episode`** `number`\n\n- **`mapping_generation`** `number`\n\n- **`version`** `number | null`\n\n- **`signatures`** `Record<string, string | null>`\n\n- **`decision`** `ViewMappingReuseDecision`\n\n- **`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<...>; }[]`\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Waiting Shape Hint Model\n\n### Fields\n\n- **`question`** `string`\n\n- **`reason`** `string`\n\n- **`paths`** `string[]`\n\n Verified JSON bracket paths such as $[\"account\"][\"id\"], with literal key characters escaped.\n\n- **`requested_at`** `string`\n\n- **`mapping_generation`** `number`\n\n- **`view_id`** `string`\n\n- **`deferral_id`** `string`\n\n- **`episode`** `number`\n\n- **`transform_id`** `string`\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Views\n\n### Endpoint\n\nRetrieve a list of views for a project.\n\n```http\nGET /v1/projects/:project_id/views\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"name\"`, `\"updated_at\"`.\n\n- **`project_id`** `string`\n Return only views in this project. Optional.\n\n- **`collection_id`** `string`\n Return only views reading from this collection. Optional.\n\n- **`parent_view_id`** `string`\n Return only views derived from this view. Optional.\n\n- **`deleted_at`** [`NullableDateFilter`](/api/views#nullable-date-filter)\n Filter on deletion state. Soft-deleted views are excluded unless you ask for them. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: View[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve View\n\n### Endpoint\n\nRetrieve a single view.\n\n```http\nGET /v1/projects/:project_id/views/:view_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Response\n\nView retrieved\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create View\n\n### Endpoint\n\nCreate a view that transforms records from a collection or another view.\n\n```http\nPOST /v1/projects/:project_id/views\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name for the view. Minimum length: `1`. Maximum length: `128`.\n\n- **`output_schema`** `object[]`\n The fields this view produces. Can be left until transforms define it. Optional.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`hint`** `string | null`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.\n\n- **`mapping_policy`** `object`\n Optional.\n\n - **`nullable_inputs`** `string[]`\n Read paths explicitly permitted to be null. Optional.\n\n - **`fallback_groups`** `string[][]`\n Approved ordered fallback read paths. Optional.\n\n - **`output_bindings`** `object`\n Required source path segments for each output binding. Optional.\n\n - **`allow_drops`** `boolean`\n Allow a validated mapping to emit no output for a record. Optional.\n\n- **`project_id`** `string`\n Project ID. Optional when using the /projects/:project/views route. Optional.\n\n- **`collection_id`** `string`\n Collection to derive from, when reading raw ingested records. Optional.\n\n- **`parent_view_id`** `string`\n View to derive from, when deriving from a view. Optional.\n\n- **`transform_script`** `string`\n TGL transform script. When set, the view skips automatic TGL generation. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n- **`transform_mode`** `string`\n 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\"`.\n\n### Response\n\nView created\n\n```ts\n{\n message: string;\n data: View;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A view reads from exactly one source: a collection, or another view to derive from.\n- The output schema defines the fields the view produces. A transform is created alongside the view to populate them.\n- Leaving the transform script empty queues it for generation instead of rejecting it, so a view can be created before its TGL is written.\n- Backfilling existing records starts shortly after creation and runs in the background. The view reports its progress while it catches up.\n- 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.\n- 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.\n- `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`.\n- `transform_script` cannot be whitespace-only. Supplying one activates the view's first transform immediately and skips automatic TGL generation.\n- 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.\n\n## Create Join View\n\n### Endpoint\n\nCreate a view that joins two existing views on a shared key.\n\n```http\nPOST /v1/projects/:project_id/views/join\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name shown for this join view. Minimum length: `1`. Maximum length: `128`.\n\n- **`project_id`** `string`\n Project the join view belongs to. Optional.\n\n- **`base_view_id`** `string` -- **Required**\n View every output record starts from. Minimum length: `1`.\n\n- **`joins`** `object[]` -- **Required**\n Views joined onto the base view, each with its own match fields.\n\n - **`lookup_view_id`** `string` -- **Required**\n View this clause looks values up in. Minimum length: `1`.\n\n - **`alias`** `string` -- **Required**\n Name this clause's fields are addressed by in the transform script. Minimum length: `1`. Maximum length: `64`.\n\n - **`join_type`** `string`\n How unmatched base records are handled. `left` keeps them with empty lookup fields, `inner` drops them. Optional. Defaults to `\"left\"`. Allowed values: `\"left\"`, `\"inner\"`.\n\n - **`base_field`** `string` -- **Required**\n Field on the base view to match on. Minimum length: `1`. Maximum length: `256`.\n\n - **`lookup_field`** `string` -- **Required**\n Field on the lookup view to match against. Minimum length: `1`. Maximum length: `256`.\n\n - **`match_strategy`** `string`\n Which lookup record wins when several match. `latest` keeps the newest record timestamp and is the only supported value today. Optional. Defaults to `\"latest\"`. Allowed values: `\"latest\"`.\n\n - **`priority`** `integer`\n Order this clause is applied in. Lower runs first. Optional. Defaults to `0`. Minimum: `0`.\n\n- **`output_schema`** `object[]`\n Fields the join produces. Leave empty to have one inferred. Optional. Defaults to `[]`.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`transform_script`** `string`\n TGL transform script. Omit to have one generated. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n- **`hint`** `string`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional. Maximum length: `4096`.\n\n### Response\n\nJoin view created\n\n```ts\n{\n message: string;\n data: View;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Both sides must be views in this project. Join a collection by creating a view over it first.\n- The join key must exist in the output schema of both sides.\n- A join view rebuilds when either side produces new records, so it trails its inputs rather than updating in the same instant.\n- 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.\n- 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.\n- `transform_script` cannot be whitespace-only. Omit it entirely to have one generated.\n- 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.\n\n## Update View\n\n### Endpoint\n\nUpdate a view's name or output schema.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Request Body\n\n- **`mapping_policy`** `object`\n Optional.\n\n - **`nullable_inputs`** `string[]`\n Read paths explicitly permitted to be null. Optional.\n\n - **`fallback_groups`** `string[][]`\n Approved ordered fallback read paths. Optional.\n\n - **`output_bindings`** `object`\n Required source path segments for each output binding. Optional.\n\n - **`allow_drops`** `boolean`\n Allow a validated mapping to emit no output for a record. Optional.\n\n- **`name`** `string`\n Name for the view. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`output_schema`** `object[]`\n The fields this view produces. Can be left until transforms define it. Optional.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`hint`** `string | null`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.\n\n- **`transform_mode`** `string`\n 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\"`.\n\n- **`view_transform_id`** `string`\n 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.\n\n- **`deleted_at`** `null`\n 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.\n\n### Response\n\nView updated\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Mapping policy changes on an existing mapping require reviewed coverage recovery. Hint changes preserve accepted mappings and retry undecided work with the new hint.\n- 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.\n- `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`.\n- 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.\n\n## Delete View\n\n### Endpoint\n\nDelete a view and every view derived from it.\n\n```http\nDELETE /v1/projects/:project_id/views/:view_id\n```\n\n**Scope:** `views:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Response\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Views derived from this one are deleted with it.\n- 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.\n- Backfill and indexing work for the whole cascade is cancelled immediately, so a deleted view stops consuming capacity before its rows are removed.\n\n## Force Delete View\n\n### Endpoint\n\nDelete a view permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/views/:view_id/force\n```\n\n**Scope:** `views:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Response\n\nView queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live view as well as one already scheduled for deletion.\n- 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.\n\n## List View Records\n\n### Endpoint\n\nRetrieve the transformed records a view has produced.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/records\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewRecord[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- A cursor is only valid for the query that produced it. Reusing one after changing the sort direction returns a 400 rather than a page of skipped or repeated records, so start again without a cursor.\n- 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.\n- `after` and `before` are mutually exclusive.\n\n## List Waiting Shapes\n\n### Endpoint\n\nRetrieve the event shapes a view has deferred because no transform serves them yet.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/deferrals\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`resolved`** `string`\n Filter by resolution. `false` returns only shapes still waiting. Optional. Allowed values: `\"true\"`, `\"false\"`.\n\n- **`order_by`** `string`\n Sort field. Defaults to `last_deferred_at`. Optional. Allowed values: `\"first_deferred_at\"`, `\"last_deferred_at\"`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewShapeDeferral[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Deferred records are stored, not lost. They backfill automatically once a script covers the shape, and each entry's `reason` says what unblocks it.\n- `event_count` is approximate: ingest retries can count an event more than once.\n- `after` and `before` are mutually exclusive.\n\n## Inspect Waiting Shape\n\n### Endpoint\n\nInspect structural differences and candidate output bindings for a waiting shape.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`deferral_id`** `string` -- **Required**\n Unique identifier of the deferral.\n\n### Response\n\nWaiting shape inspected\n\n```ts\n{\n message: string;\n data: ViewShapeInspection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Retrieve Waiting Shape Hint\n\n### Endpoint\n\nRetrieve the model's current binding question for a waiting shape.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id/hint\n```\n\n**Scopes:** `views:read` + `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`deferral_id`** `string` -- **Required**\n Unique identifier of the deferral.\n\n### Response 200 (1)\n\nNo pending hint question\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Response 200 (2)\n\nWaiting shape hint retrieved\n\n```ts\n{\n message: string;\n data: ViewPendingHint;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Returns null when no current question remains. Answer using this question's episode, mapping_generation, requested_at, and transform_id.\n\n## Decide Waiting Shape\n\n### Endpoint\n\nResolve the next action for one view and schema version.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id/decision\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`deferral_id`** `string` -- **Required**\n Unique identifier of the deferral.\n\n### Request Body\n\n- **`episode`** `integer` -- **Required**\n The deferral episode shown when the decision was inspected. Minimum: `1`.\n\n- **`action`** `string` -- **Required**\n Authorize a bounded authoring attempt, answer a hint, or exclude this exact version in this view. Allowed values: `\"author\"`, `\"hint\"`, `\"exclude\"`.\n\n- **`mapping_generation`** `integer` -- **Required**\n The mapping generation shown when the decision was inspected. Minimum: `1`.\n\n- **`hint`** `string`\n Answer to the model's binding question. Optional. Minimum length: `1`. Maximum length: `4096`.\n\n- **`hint_requested_at`** [`ISODateString`](/api/views#iso-date-string)\n Exact requested_at of the pending hint being answered. Optional.\n\n- **`hint_transform_id`** `string`\n Transform that owns the pending hint being answered. Optional. Minimum length: `1`. Maximum length: `32`.\n\n- **`reason`** `string`\n Required audit reason for an exclusion. Optional. Minimum length: `1`. Maximum length: `2000`.\n\n- **`mapping_policy`** `object`\n Explicit null, fallback, drop, and source-binding rules. Optional.\n\n - **`nullable_inputs`** `string[]`\n Read paths explicitly permitted to be null. Optional.\n\n - **`fallback_groups`** `string[][]`\n Approved ordered fallback read paths. Optional.\n\n - **`output_bindings`** `object`\n Required source path segments for each output binding. Optional.\n\n - **`allow_drops`** `boolean`\n Allow a validated mapping to emit no output for a record. Optional.\n\n### Response\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Authoring and hint answers reserve a bounded model budget. Exclusion covers unprocessed and future records of this exact version in this view.\n- Answering a hint requires `hint`; excluding a version requires `reason`. Exclusion covers unprocessed and future records with this exact schema version in this view.\n- 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.\n\n## List View Transforms\n\n### Endpoint\n\nRetrieve a list of a view's transforms.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/transforms\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Creation order matches schema version order. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"updated_at\"`.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n Return only transforms in this state. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewTransform[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve View Transform\n\n### Endpoint\n\nRetrieve a single transform.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/transforms/:transform_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTransform retrieved\n\n```ts\n{\n message: string;\n data: ViewTransform;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## List View Versions\n\n### Endpoint\n\nRetrieve a list of the schema versions a view covers.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/versions\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Optional. Defaults to `\"version\"`. Allowed values: `\"version\"`.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n Return only versions whose transform is in this state. Optional.\n\n- **`script_hash`** `string`\n Return only versions served by this script. Take the value from a version's row. Optional. Minimum length: `64`. Maximum length: `64`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewVersion[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Versions sharing a `script_hash` run the same transform. Filter by it to see every version one script serves.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve View Version\n\n### Endpoint\n\nRetrieve one schema version a view covers.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/versions/:version_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`version_id`** `integer` -- **Required**\n The schema version number: the `version` a view version carries.\n\n### Response\n\nVersion retrieved\n\n```ts\n{\n message: string;\n data: ViewVersion;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `version_id` is the schema version NUMBER, the `version` a row carries, not a `vta_` id.\n\n## Create View Transform\n\n### Endpoint\n\nAdd a transform that maps incoming records into the view's output schema.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Request Body\n\n- **`collection_schema_version`** `integer | null`\n Collection schema version this transform covers. Null for view-to-view transforms. Optional.\n\n- **`transform_script`** `string`\n TGL transform script. Omit to have one generated. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n### Response\n\nTransform created\n\n```ts\n{\n message: string;\n data: ViewTransform;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `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.\n- The script must write every field the view's output schema declares.\n- Omitting the script queues the transform for generation instead of rejecting it.\n- 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.\n- `transform_script` cannot be whitespace-only. Omit it to have TGL generated instead.\n\n## Update View Transform\n\n### Endpoint\n\nUpdate a transform's script.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms/:transform_id\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Request Body\n\n- **`transform_script`** `string`\n TGL transform script. To clear and regenerate, use the regenerate endpoint instead. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n- **`conflict_resolution`** `string`\n How conflicting schema versions are handled when a script edit fans out. Optional. Defaults to `\"strand\"`. Allowed values: `\"strand\"`, `\"keep\"`.\n\n- **`dry_run`** `boolean`\n Preview the edit: returns the per-version verdict report without changing anything. Optional. Defaults to `false`.\n\n- **`preview_token`** `string`\n 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`.\n\n### Response 200 (1)\n\n```ts\n{\n message: string;\n 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; } };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`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**\n\n### Response 200 (2)\n\nTransform updated\n\n```ts\n{\n message: string;\n data: ViewTransform & { edit_report: { script_hash: string | null; version_verdicts: never[]; } };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`edit_report`** `{ script_hash: string | null; version_verdicts: never[]; }` -- **Required**\n\n### Comments\n\n- `transform_script` is TGL and must compile, and must write every field the view's output schema declares.\n- 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.\n- Conflicting versions are moved to queued placeholders by default. Set `conflict_resolution` to `keep` to leave those versions on their prior script.\n- 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.\n- 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.\n- 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.\n- 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.\n- `transform_script` cannot be whitespace-only. To clear a script and have it regenerated, use the regenerate endpoint rather than sending an empty one.\n- 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.\n\n## Generate View Transform Script\n\n### Endpoint\n\nQueue TGL generation for a transform from the view's input and output schemas.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms/:transform_id/generate\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTGL generation queued\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Generation runs in the background. This returns once the work is queued, not once a script exists; poll the transform to see the result.\n- The generated script is written to the transform and replaces whatever was there.\n\n## Re-backfill View Transform\n\n### Endpoint\n\nRe-run a transform over every record the view has already read.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms/:transform_id/rebackfill\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTransform re-backfill started\n\n```ts\n{\n message: string;\n data: ViewTransform;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Use this after editing a script so existing records pick up the new logic. Without it, an edit only affects records that arrive afterwards.\n- 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.\n- Refused while a metric, derived view or join view depends on this view.\n\n## Regenerate View\n\n### Endpoint\n\nReplace a view's output schema and queue every transform to be regenerated against it.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/regenerate\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Request Body\n\n- **`output_schema`** `object[]`\n The fields the view should produce. Omit to regenerate against the current schema. Optional.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`hint`** `string | null`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.\n\n### Response\n\nView schema updated and transforms queued for regeneration\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Existing records keep their old shape until the regenerated transforms backfill over them.\n- Refused while a metric, derived view or join view depends on this view.\n- `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`.\n- 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.\n\n## Delete View Transform\n\n### Endpoint\n\nDelete a transform from a view.\n\n```http\nDELETE /v1/projects/:project_id/views/:view_id/transforms/:transform_id\n```\n\n**Scope:** `views:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTransform deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Records the transform already produced are left in place. Re-backfill the view's remaining transforms to rebuild its output without this one.\n\n",
|
|
22
|
+
"api/collections": "# Collections\n\n## Collection Model\n\n### Fields\n\n- **`object`** `\"collection\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `col_`.\n\n- **`source_id`** `string`\n\n- **`source_name`** `string | null`\n\n- **`project_id`** `string`\n\n- **`slug`** `string`\n\n Permanent identifier used in endpoints. Cannot be changed after creation.\n\n- **`name`** `string`\n\n- **`storage_gb`** `number`\n\n Data stored in this collection's files, in gigabytes of uncompressed data. Recalculated hourly.\n\n- **`schema_count`** `number`\n\n How many schema versions this collection has, in every state.\n\n- **`views_count`** `number`\n\n How many views read from this collection.\n\n- **`drains_count`** `number`\n\n How many drains export from this collection.\n\n- **`schema_opaque_paths`** `string[][]`\n\n Object paths whose contents do not take part in schema identity, one key segment per entry.\n Records that differ only inside an opaque path share one schema version. Changing this re-keys\n the collection.\n\n- **`schema_tracked_paths`** `string[][]`\n\n Children of opaque paths that stay part of schema identity. Changing this re-keys the\n collection.\n\n- **`schema_max_depth`** `number`\n\n How many levels of nesting take part in schema identity. Changing this re-keys the collection.\n\n- **`schema_nulls`** [`SchemaRuleMode`](/api/collections#schema-rule-mode)\n\n `auto` accepts null and absent values where a mapping reads; `manual` waits for a person per\n view.\n\n- **`schema_optional_keys`** [`SchemaRuleMode`](/api/collections#schema-rule-mode)\n\n `auto` lets a record with a subset of a version's keys join it; `manual` treats absence as a\n new shape.\n\n- **`schema_detector`** [`SchemaRuleMode`](/api/collections#schema-rule-mode)\n\n `auto` applies a safe opaque-path proposal on its own; `manual` holds every proposal for a\n person.\n\n- **`schema_recurrence_gap_ms`** `number`\n\n How long after first sight a shape must recur before its version settles, in milliseconds.\n\n- **`schema_bulk_rows`** `number`\n\n A single batch carrying this many rows of a shape settles its version at once; `0` disables it.\n\n- **`schema_stale_after_ms`** `number`\n\n How long a candidate version waits to recur before it goes stale, in milliseconds.\n\n- **`schema_identity_limit`** `number`\n\n How many settled versions the collection may hold; past it, new shapes stay unclassified.\n\n- **`schema_candidate_pool`** `number`\n\n How many candidate and stale versions the collection may hold; past it, new shapes stay\n unclassified.\n\n- **`schema_alias_pool`** `number`\n\n How many exact signature hashes the collection caches for lookup.\n\n- **`schema_writer_limit`** `number`\n\n How many collection files one ingest batch may open for this collection. Shapes past the\n candidate pool get the same number of files per batch, the shapes with the most rows first; the\n rows of shapes past that budget stay unclassified and cannot be linked to a version later.\n\n- **`schema_unclassified_retention_days`** `number | null`\n\n How many days rows without a settled version are kept; null keeps them forever.\n\n- **`deleted_at`** [`ISODateString | null`](/api/collections#iso-date-string)\n\n When the collection is scheduled to be deleted; null when it is not.\n\n- **`created_at`** [`ISODateString`](/api/collections#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/collections#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### SchemaRuleMode\n\n`\"auto\" | \"manual\"`\n\n## Collection Schema Report\n\n### Fields\n\n- **`object`** `\"collection_schema_report\"`\n\n- **`collection_id`** `string`\n\n- **`rules`** `{ opaque_paths: string[][]; tracked_paths: string[][]; max_depth: number; }`\n\n The rules the report reads the collection's shapes under: the collection's own for the\n retrieve, the rules sent for a preview.\n\n- **`shapes`** `{ before: number; after: number; }`\n\n How many shapes the collection holds under its current rules (`before`) and under the report's\n rules (`after`). Equal unless the report previews a rule change; the difference is what a\n re-key would merge.\n\n- **`proposals`** [`CollectionSchemaProposal[]`](/api/collections#schema-proposal)\n\n Where the detector sees keys being invented, with what applying each proposal would do.\n\n- **`keys_look_like_data`** `boolean`\n\n Whether every shape's top-level keys are its own, such as records keyed by a date. Those keys\n are values, and no opaque path can bring the shapes together.\n\n- **`unclassified`** `{ shapes: number; rows: number; }`\n\n Shapes past the collection's pools, stored with their signature but no version, and the rows\n they carry. Nothing reads them until the pool frees or a rule brings them under a version.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Collection Schema Proposal\n\n### Fields\n\n- **`object`** `\"collection_schema_proposal\"`\n\n- **`path`** `string[]`\n\n The path the detector proposes opaque, as key segments.\n\n- **`tracked`** `string[]`\n\n Children of the path every shape carries with one type, which stay in schema identity as\n tracked paths when the proposal is applied.\n\n- **`content_children`** `number`\n\n How many children of the path read as content: keys that keep being invented.\n\n- **`read_by_mapping`** `boolean`\n\n Whether a bound mapping reads inside the path. Applying the proposal would leave that mapping\n unverified, so the detector never applies such a proposal on its own; a person can.\n\n- **`shapes_after`** `number`\n\n How many shapes the collection would hold with this proposal applied.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Collections\n\n### Endpoint\n\nRetrieve a list of collections for a project.\n\n```http\nGET /v1/projects/:project_id/collections\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the collections. Optional. Defaults to `\"name\"`. Allowed values: `\"created_at\"`, `\"name\"`, `\"updated_at\"`.\n\n- **`source_id`** `string`\n Filter to collections under a single source. Optional.\n\n- **`project_id`** `string`\n Filter to collections in a single project. Optional.\n\n- **`deleted_at`** [`NullableDateFilter`](/api/collections#nullable-date-filter)\n Filter by scheduled deletion date. Use `null` for collections that are not scheduled for deletion, `not:null` for collections that are. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Collection[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Collection\n\n### Endpoint\n\nRetrieve a single collection.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\nCollection retrieved\n\n```ts\n{\n message: string;\n data: Collection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Collection\n\n### Endpoint\n\nCreate a collection under a source.\n\n```http\nPOST /v1/projects/:project_id/collections\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Display name for the collection. Minimum length: `1`. Maximum length: `128`.\n\n- **`slug`** `string`\n Permanent identifier used in ingest URLs. Derived from `name` when omitted. Optional. Minimum length: `1`. Maximum length: `64`.\n\n- **`source_id`** `string` -- **Required**\n Source the collection is created under.\n\n### Response\n\nCollection created\n\n```ts\n{\n message: string;\n data: Collection;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `source_id` is required.\n- `slug` is permanent. It appears in the ingest URL, so pick it deliberately.\n\n## Update Collection\n\n### Endpoint\n\nUpdate a collection.\n\n```http\nPOST /v1/projects/:project_id/collections/:collection_id\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Request Body\n\n- **`name`** `string`\n Display name for the collection. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`expected_updated_at`** [`ISODateString`](/api/collections#iso-date-string)\n Reject this update if the collection changed since this inspected update time. Optional.\n\n- **`schema_opaque_paths`** `string[][]`\n Object paths whose contents do not take part in schema identity, each written as its key segments (`[\"metadata\", \"id\"]`). A path that names an array covers its elements and their children. Records that differ only inside an opaque path share one schema version; the raw values are still stored. Changing this re-keys the collection. Optional.\n\n- **`schema_tracked_paths`** `string[][]`\n Children of opaque paths that stay part of schema identity, written like `schema_opaque_paths`. Changing this re-keys the collection. Optional.\n\n- **`schema_max_depth`** `integer`\n How many levels of nesting take part in schema identity. Changing this re-keys the collection. Optional. Minimum: `1`. Maximum: `10`.\n\n- **`schema_nulls`** `string`\n `auto` accepts null and absent values at any path a mapping reads; `manual` waits for a person to approve each nullable input per view. Optional. Allowed values: `\"auto\"`, `\"manual\"`.\n\n- **`schema_optional_keys`** `string`\n `auto` lets a record whose keys are a subset of a version's keys join that version; `manual` treats every absent key as a different shape. Optional. Allowed values: `\"auto\"`, `\"manual\"`.\n\n- **`schema_detector`** `string`\n `auto` applies a safe opaque-path proposal from the detector on its own; `manual` holds every proposal for a person. Optional. Allowed values: `\"auto\"`, `\"manual\"`.\n\n- **`schema_recurrence_gap_ms`** `integer`\n How long after first sight a shape must be seen again before its version settles, in milliseconds. Optional. Minimum: `60000`. Maximum: `604800000`.\n\n- **`schema_bulk_rows`** `integer`\n A single batch carrying at least this many rows of a shape settles its version immediately. `0` disables the bulk path. Optional. Minimum: `0`. Maximum: `1000000`.\n\n- **`schema_stale_after_ms`** `integer`\n How long a candidate version waits to be seen again before it goes stale, in milliseconds. Optional. Minimum: `3600000`. Maximum: `7776000000`.\n\n- **`schema_identity_limit`** `integer`\n How many settled schema versions the collection may hold. Past it, new shapes stay unclassified with their signatures kept. Optional. Minimum: `1`. Maximum: `4096`.\n\n- **`schema_candidate_pool`** `integer`\n How many candidate and stale versions the collection may hold. Past it, new shapes stay unclassified. Optional. Minimum: `1`. Maximum: `8192`.\n\n- **`schema_alias_pool`** `integer`\n How many exact signature hashes the collection remembers for fast lookup. Past it, a new hash is still resolved but not cached. Optional. Minimum: `16`. Maximum: `100000`.\n\n- **`schema_writer_limit`** `integer`\n How many collection files one ingest batch may open for this collection. Shapes past the candidate pool get the same number of files per batch, the shapes with the most rows first; the rows of shapes past that budget stay unclassified and cannot be linked to a version later. Optional. Minimum: `1`. Maximum: `256`.\n\n- **`schema_unclassified_retention_days`** `integer | null`\n How many days rows without a settled schema version are kept before they are deleted. `null` keeps them forever. Optional.\n\n- **`deleted_at`** `null`\n Send `null` to restore a collection that is scheduled for deletion. The deletion date itself is set by the delete endpoint and cannot be written here. Optional.\n\n### Response\n\nCollection updated\n\n```ts\n{\n message: string;\n data: Collection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `slug` cannot be changed after creation.\n- Send `deleted_at` as `null` to restore a collection that is scheduled for deletion.\n\n## Delete Collection\n\n### Endpoint\n\nSchedule a collection for deletion.\n\n```http\nDELETE /v1/projects/:project_id/collections/:collection_id\n```\n\n**Scope:** `sources:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\n```ts\n{\n message: string;\n data: Collection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The collection is hidden immediately and permanently removed 7 days later. Restore it before then by updating it with `deleted_at` set to null.\n\n## Force Delete Collection\n\n### Endpoint\n\nDelete a collection permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/collections/:collection_id/force\n```\n\n**Scope:** `sources:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\nCollection queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live collection as well as one already scheduled for deletion.\n- Every record, schema version and file in the collection is destroyed immediately.\n\n## List Collection Schemas\n\n### Endpoint\n\nRetrieve the schema versions for a collection.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id/schemas\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n- **`state`** `string`\n Return only schema versions in this state. Optional. Allowed values: `\"candidate\"`, `\"settled\"`, `\"stale\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: CollectionSchemaVersion[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `state` is `candidate` from first sight, `settled` once the shape recurs or arrives in bulk, and `stale` when a candidate is not seen again. Only a settled version is offered to views for authoring.\n- `after` and `before` are mutually exclusive.\n\n## Update Collection Schema\n\n### Endpoint\n\nSettle a schema version by hand, so views can author against it before its shape recurs.\n\n```http\nPOST /v1/projects/:project_id/collections/:collection_id/schemas/:schema_version_id\n```\n\n**Scope:** `sources:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n- **`schema_version_id`** `string` -- **Required**\n Unique identifier of the schema.\n\n### Request Body\n\n- **`state`** `string` -- **Required**\n The only state a person sets. `settled` trusts the version for view authoring now, without waiting for its shape to recur; a settled version never goes back. Allowed values: `\"settled\"`.\n\n### Response\n\nSchema version settled\n\n```ts\n{\n message: string;\n data: CollectionSchemaVersion;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `state` accepts only `settled`; a settled version never returns to candidate or stale.\n- A version that is already settled is left as it is and responds with `request_no_update`.\n\n## Retrieve Collection Schema Report\n\n### Endpoint\n\nRetrieve the detector's view of a collection: the opaque paths it proposes, whether the top-level keys are data, and how many shapes sit unclassified past the collection's pools.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id/schema_report\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Response\n\nCollection schema report retrieved\n\n```ts\n{\n message: string;\n data: CollectionSchemaReport;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A proposal a bound mapping reads inside is reported and never applied on its own, whatever the detector switch; apply it by adding its path to the collection's `schema_opaque_paths` and its tracked children to `schema_tracked_paths`.\n- `shapes.before` and `shapes.after` are equal here; the preview is where they differ.\n\n## Preview Collection Schema Rules\n\n### Endpoint\n\nPreview a rule change: the schema report the collection would read under the rules sent, with how many shapes it holds now and how many it would hold. Nothing is changed.\n\n```http\nPOST /v1/projects/:project_id/collections/:collection_id/schema_preview\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Request Body\n\n- **`schema_opaque_paths`** `string[][]`\n Opaque paths to preview, written like the collection's `schema_opaque_paths`. Omitted, the collection's current opaque paths apply. Optional.\n\n- **`schema_tracked_paths`** `string[][]`\n Tracked paths to preview, written like the collection's `schema_tracked_paths`. Omitted, the collection's current tracked paths apply. Optional.\n\n- **`schema_max_depth`** `integer`\n Max depth to preview. Omitted, the collection's current max depth applies. Optional. Minimum: `1`. Maximum: `10`.\n\n### Response\n\nCollection schema rules previewed\n\n```ts\n{\n message: string;\n data: CollectionSchemaReport;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A rule left out of the body keeps the collection's current value.\n- `shapes.before` counts the shapes under the collection's current rules and `shapes.after` under the rules sent; the difference is what updating the collection with those rules would merge.\n\n## List Collection Documents\n\n### Endpoint\n\nRetrieve raw documents from a collection.\n\n```http\nGET /v1/projects/:project_id/collections/:collection_id/docs\n```\n\n**Scope:** `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`collection_id`** `string` -- **Required**\n Unique identifier of the collection.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: CollectionDoc[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Documents are returned exactly as they were sent, minus Tailglow's own reserved envelope.\n- Paging is forward-only. Pass `pagination.next_cursor` back as `after` to get the next page; no `prev_cursor` is issued, and `before` returns the first page.\n- While the project's server is stopped this answers 409 `server_stopped`. Start the server and try again.\n- `after` and `before` are mutually exclusive.\n\n",
|
|
23
|
+
"api/views": "# Views\n\n## View Model\n\n### Fields\n\n- **`mapping_policy_revision`** `number | null`\n\n- **`mapping_policy_nullable_inputs`** `string[]`\n\n- **`mapping_policy_fallback_groups`** `string[][]`\n\n- **`mapping_policy_allow_drops`** `boolean`\n\n- **`mapping_policy_output_bindings`** `Record<string, string[]>`\n\n- **`mapping_generation`** `number`\n\n- **`object`** `\"view\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `viw_`.\n\n- **`team_id`** `string`\n\n- **`project_id`** `string`\n\n- **`collection_id`** `string | null`\n\n The collection this view reads from; null when it reads from another view.\n\n- **`collection_name`** `string | null`\n\n Display name of that collection.\n\n- **`parent_view_id`** `string | null`\n\n The view this one is derived from; null when it reads from a collection.\n\n- **`parent_view_name`** `string | null`\n\n Display name of that view.\n\n- **`view_mode`** `string`\n\n Where the view's records come from: a collection, another view, or a join of several.\n\n- **`transform_mode`** [`ViewTransformMode`](/api/views#view-transform-mode)\n\n Whether transform scripts are generated for new schema versions or written by you.\n\n- **`name`** `string`\n\n- **`hint`** `string | null`\n\n Plain-language guidance the generator uses when it writes transform scripts.\n\n- **`output_schema`** `ViewOutputField[]`\n\n The fields this view's records contain.\n\n- **`status`** [`ViewStatus`](/api/views#view-status)\n\n Whether the view is producing records, still being set up, or failed.\n\n- **`error_message`** `string | null`\n\n Why the view failed, when its status is `error`.\n\n- **`waiting_shapes_count`** `number`\n\n How many event shapes arrived that no transform serves yet. Their records are stored and catch\n up automatically once a script covers them; the shapes are listed by the view's deferrals\n endpoint.\n\n- **`deleted_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the view is scheduled to be deleted; null when it is not.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`join_base_view_id`** `string | null`\n\n The base view on a join view; null on other views.\n\n- **`join_base_view_name`** `string | null`\n\n- **`join_status`** [`ViewJoinStatus | null`](/api/views#view-join-status)\n\n- **`join_backfill_cutoff_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n Records after this instant are joined live.\n\n- **`join_live_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the join started matching new records.\n\n- **`join_lookup_progress`** `LookupProgress | null`\n\n Lookup progress keyed by clause alias.\n\n- **`join_base_file_count`** `number | null`\n\n Number of base-view files to backfill.\n\n- **`join_base_files_done`** `number | null`\n\n Number of base-view files already processed.\n\n- **`join_error_message`** `string | null`\n\n- **`join_created_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`join_updated_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`joins`** `ViewJoinClause[]`\n\n The views joined onto the base view, on a join view.\n\n- **`metrics_count`** `number`\n\n How many metrics read from this view.\n\n- **`derived_views_count`** `number`\n\n How many views are derived from this one.\n\n- **`join_views_count`** `number`\n\n How many join views use this one as an input.\n\n- **`transform_count`** `number`\n\n How many transforms this view has.\n\n- **`drains_count`** `number`\n\n How many drains forward this view's records.\n\n- **`backfill_file_count`** `number`\n\n How many files every transform on this view has to process between them.\n\n- **`backfill_files_done`** `number`\n\n How many of those files they have processed.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the earliest of those backfills began. This is the earliest start across the whole view,\n which is what a throughput estimate has to measure elapsed time from.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewTransformMode\n\n`\"auto\" | \"manual\"`\n\n#### ViewStatus\n\n`\"empty\" | \"building\" | \"needs_transform\" | \"active\" | \"error\" | \"paused\" | \"cancelled\"`\n\n#### ViewJoinStatus\n\n`\"waiting_for_inputs\" | \"building_lookups\" | \"backfilling\" | \"active\" | \"error\"`\n\n## View Transform Model\n\n### Fields\n\n- **`object`** `\"view_transform\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `vtr_`.\n\n- **`view_id`** `string`\n\n The view this transform produces records for.\n\n- **`transform_script`** `string | null`\n\n The TGL script that maps incoming records onto the view's fields; null until one is written.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n\n Whether the transform is running, waiting for a script, or failed.\n\n- **`error_message`** `string | null`\n\n Why the transform failed, when its status is `error`.\n\n- **`tgl_retry_count`** `number`\n\n How many times script generation has been retried for this transform.\n\n- **`max_read_depth`** `number | null`\n\n How many levels deep into a record the script reads.\n\n- **`evidence_notice`** `ViewEvidenceNotice | null`\n\n Set when a path this mapping read as empty at authoring time has since carried a value with a\n type, listing those paths as `L<depth>:<path>` read keys. Rows keep flowing; re-author the\n mapping to use the new column. Cleared when a new script is generated.\n\n- **`backfill_status`** [`ViewTransformBackfillStatus`](/api/views#view-transform-backfill-status)\n\n Whether existing records have been reprocessed through this transform yet.\n\n- **`backfill_cutoff_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n Records after this time are handled live rather than by the backfill.\n\n- **`backfill_start_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n The earliest record the backfill will reach.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill began.\n\n- **`backfill_completed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill finished.\n\n- **`backfill_file_count`** `number`\n\n How many files the backfill has to process.\n\n- **`backfill_files_done`** `number`\n\n How many of those files it has processed.\n\n- **`backfill_error`** `string | null`\n\n Why the backfill failed, when it did.\n\n- **`assignments`** `ViewTransformAssignment[]`\n\n Backfill progress for each source schema version this transform covers.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewTransformStatus\n\n`\"queued\" | \"analyzing\" | \"active\" | \"error\" | \"draft\"`\n\n#### ViewTransformBackfillStatus\n\n`\"pending\" | \"enqueuing\" | \"in_progress\" | \"completed\" | \"failed\" | \"cancelled\"`\n\n## View Version Model\n\n### Fields\n\n- **`object`** `\"view_version\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `vta_`.\n\n- **`view_id`** `string`\n\n- **`version`** `number`\n\n The source schema version this row covers. Versions rise as the source's shape changes.\n\n- **`transform_id`** `string`\n\n The transform whose script serves this version. Several versions can share one transform.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n\n Whether the transform is running, waiting for a script, or failed.\n\n- **`error_message`** `string | null`\n\n Why the transform failed, when its status is `error`.\n\n- **`evidence_notice`** `ViewEvidenceNotice | null`\n\n Set when a path the serving script read as empty at authoring time has since carried a value\n with a type; lists those paths as `L<depth>:<path>` read keys. Re-author the mapping to use the\n new column. Cleared when a new script is generated.\n\n- **`script_hash`** `string | null`\n\n Identity of the script text. Versions sharing a hash run the identical script.\n\n- **`script`** `string | null`\n\n The TGL script serving this version; null until one is written.\n\n- **`backfill_status`** [`ViewTransformBackfillStatus`](/api/views#view-transform-backfill-status)\n\n Whether the records already on this schema version have been reprocessed yet.\n\n- **`backfill_start_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n The earliest record the backfill will reach.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill began.\n\n- **`backfill_completed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the backfill finished.\n\n- **`backfill_file_count`** `number`\n\n How many files the backfill has to process for this version.\n\n- **`backfill_files_done`** `number`\n\n How many of those files it has processed.\n\n- **`backfill_error`** `string | null`\n\n Why the backfill failed, when it did.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n When the view started covering this schema version.\n\n- **`updated_at`** [`ISODateString`](/api/views#iso-date-string)\n\n When the transform serving this version last changed.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewTransformStatus\n\n`\"queued\" | \"analyzing\" | \"active\" | \"error\" | \"draft\"`\n\n#### ViewTransformBackfillStatus\n\n`\"pending\" | \"enqueuing\" | \"in_progress\" | \"completed\" | \"failed\" | \"cancelled\"`\n\n## Waiting Shape Model\n\n### Fields\n\n- **`episode`** `number`\n\n- **`observation_count`** `string | null`\n\n- **`retained_rows`** `number`\n\n- **`first_observed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`last_observed_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`next_action_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`hint_question`** `string | null`\n\n- **`hint_reason`** `string | null`\n\n- **`hint_paths`** `string[]`\n\n- **`hint_requested_at`** `string | null`\n\n- **`hint_mapping_generation`** `number | null`\n\n- **`exclusion_reason`** `string | null`\n\n- **`excluded_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n- **`transform_id`** `string | null`\n\n- **`object`** `\"view_shape_deferral\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `vsd_`.\n\n- **`view_id`** `string`\n\n- **`full_signature`** `string`\n\n The typed signature identifying the event shape.\n\n- **`event_count`** `number`\n\n Roughly how many events of the shape arrived while it was waiting; retries can count an event\n more than once.\n\n- **`first_event_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n The earliest event time seen for the shape. Completeness reporting pins to this instant.\n\n- **`first_deferred_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`last_deferred_at`** [`ISODateString`](/api/views#iso-date-string)\n\n- **`resolved_at`** [`ISODateString | null`](/api/views#iso-date-string)\n\n When the shape became served; null while it is still waiting.\n\n- **`reason`** [`ViewShapeDeferralReason`](/api/views#view-shape-deferral-reason)\n\n Why the shape is waiting: `collecting_samples` until enough evidence is available to author a\n mapping, `awaiting_script` until a transform covers it, `generation_failed` when script\n generation gave up, `catching_up` while its records backfill, `served` once resolved.\n\n- **`schema_version`** `number | null`\n\n The schema version the shape belongs to. Null while no version is linked to it.\n\n- **`created_at`** [`ISODateString`](/api/views#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ViewShapeDeferralReason\n\n`\"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\"`\n\nWhat unblocks a waiting shape.\n\n## Shape Inspection Model\n\n### Fields\n\n- **`view_id`** `string`\n\n- **`deferral_id`** `string`\n\n- **`episode`** `number`\n\n- **`mapping_generation`** `number`\n\n- **`version`** `number | null`\n\n- **`signatures`** `Record<string, string | null>`\n\n- **`decision`** `ViewMappingReuseDecision`\n\n- **`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<...>; }[]`\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Waiting Shape Hint Model\n\n### Fields\n\n- **`question`** `string`\n\n- **`reason`** `string`\n\n- **`paths`** `string[]`\n\n Verified JSON bracket paths such as $[\"account\"][\"id\"], with literal key characters escaped.\n\n- **`requested_at`** `string`\n\n- **`mapping_generation`** `number`\n\n- **`view_id`** `string`\n\n- **`deferral_id`** `string`\n\n- **`episode`** `number`\n\n- **`transform_id`** `string`\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Views\n\n### Endpoint\n\nRetrieve a list of views for a project.\n\n```http\nGET /v1/projects/:project_id/views\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"name\"`, `\"updated_at\"`.\n\n- **`project_id`** `string`\n Return only views in this project. Optional.\n\n- **`collection_id`** `string`\n Return only views reading from this collection. Optional.\n\n- **`parent_view_id`** `string`\n Return only views derived from this view. Optional.\n\n- **`deleted_at`** [`NullableDateFilter`](/api/views#nullable-date-filter)\n Filter on deletion state. Soft-deleted views are excluded unless you ask for them. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: View[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve View\n\n### Endpoint\n\nRetrieve a single view.\n\n```http\nGET /v1/projects/:project_id/views/:view_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Response\n\nView retrieved\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create View\n\n### Endpoint\n\nCreate a view that transforms records from a collection or another view.\n\n```http\nPOST /v1/projects/:project_id/views\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name for the view. Minimum length: `1`. Maximum length: `128`.\n\n- **`output_schema`** `object[]`\n The fields this view produces. Can be left until transforms define it. Optional.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`hint`** `string | null`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.\n\n- **`mapping_policy`** `object`\n Optional.\n\n - **`nullable_inputs`** `string[]`\n Read paths explicitly permitted to be null. Optional.\n\n - **`fallback_groups`** `string[][]`\n Approved ordered fallback read paths. Optional.\n\n - **`output_bindings`** `object`\n Required source path segments for each output binding. Optional.\n\n - **`allow_drops`** `boolean`\n Allow a validated mapping to emit no output for a record. Optional.\n\n- **`project_id`** `string`\n Project ID. Optional when using the /projects/:project/views route. Optional.\n\n- **`collection_id`** `string`\n Collection to derive from, when reading raw ingested records. Optional.\n\n- **`parent_view_id`** `string`\n View to derive from, when deriving from a view. Optional.\n\n- **`transform_script`** `string`\n TGL transform script. When set, the view skips automatic TGL generation. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n- **`transform_mode`** `string`\n 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\"`.\n\n### Response\n\nView created\n\n```ts\n{\n message: string;\n data: View;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A view reads from exactly one source: a collection, or another view to derive from.\n- The output schema defines the fields the view produces. A transform is created alongside the view to populate them.\n- Leaving the transform script empty queues it for generation instead of rejecting it, so a view can be created before its TGL is written.\n- Backfilling existing records starts shortly after creation and runs in the background. The view reports its progress while it catches up.\n- 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.\n- 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.\n- `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`.\n- `transform_script` cannot be whitespace-only. Supplying one activates the view's first transform immediately and skips automatic TGL generation.\n- 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.\n\n## Create Join View\n\n### Endpoint\n\nCreate a view that joins two existing views on a shared key.\n\n```http\nPOST /v1/projects/:project_id/views/join\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name shown for this join view. Minimum length: `1`. Maximum length: `128`.\n\n- **`project_id`** `string`\n Project the join view belongs to. Optional.\n\n- **`base_view_id`** `string` -- **Required**\n View every output record starts from. Minimum length: `1`.\n\n- **`joins`** `object[]` -- **Required**\n Views joined onto the base view, each with its own match fields.\n\n - **`lookup_view_id`** `string` -- **Required**\n View this clause looks values up in. Minimum length: `1`.\n\n - **`alias`** `string` -- **Required**\n Name this clause's fields are addressed by in the transform script. Minimum length: `1`. Maximum length: `64`.\n\n - **`join_type`** `string`\n How unmatched base records are handled. `left` keeps them with empty lookup fields, `inner` drops them. Optional. Defaults to `\"left\"`. Allowed values: `\"left\"`, `\"inner\"`.\n\n - **`base_field`** `string` -- **Required**\n Field on the base view to match on. Minimum length: `1`. Maximum length: `256`.\n\n - **`lookup_field`** `string` -- **Required**\n Field on the lookup view to match against. Minimum length: `1`. Maximum length: `256`.\n\n - **`match_strategy`** `string`\n Which lookup record wins when several match. `latest` keeps the newest record timestamp and is the only supported value today. Optional. Defaults to `\"latest\"`. Allowed values: `\"latest\"`.\n\n - **`priority`** `integer`\n Order this clause is applied in. Lower runs first. Optional. Defaults to `0`. Minimum: `0`.\n\n- **`output_schema`** `object[]`\n Fields the join produces. Leave empty to have one inferred. Optional. Defaults to `[]`.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`transform_script`** `string`\n TGL transform script. Omit to have one generated. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n- **`hint`** `string`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional. Maximum length: `4096`.\n\n### Response\n\nJoin view created\n\n```ts\n{\n message: string;\n data: View;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Both sides must be views in this project. Join a collection by creating a view over it first.\n- The join key must exist in the output schema of both sides.\n- A join view rebuilds when either side produces new records, so it trails its inputs rather than updating in the same instant.\n- 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.\n- 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.\n- `transform_script` cannot be whitespace-only. Omit it entirely to have one generated.\n- 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.\n\n## Update View\n\n### Endpoint\n\nUpdate a view's name or output schema.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Request Body\n\n- **`mapping_policy`** `object`\n Optional.\n\n - **`nullable_inputs`** `string[]`\n Read paths explicitly permitted to be null. Optional.\n\n - **`fallback_groups`** `string[][]`\n Approved ordered fallback read paths. Optional.\n\n - **`output_bindings`** `object`\n Required source path segments for each output binding. Optional.\n\n - **`allow_drops`** `boolean`\n Allow a validated mapping to emit no output for a record. Optional.\n\n- **`name`** `string`\n Name for the view. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`output_schema`** `object[]`\n The fields this view produces. Can be left until transforms define it. Optional.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`hint`** `string | null`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.\n\n- **`transform_mode`** `string`\n 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\"`.\n\n- **`view_transform_id`** `string`\n 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.\n\n- **`deleted_at`** `null`\n 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.\n\n### Response\n\nView updated\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Mapping policy changes on an existing mapping require reviewed coverage recovery. Hint changes preserve accepted mappings and retry undecided work with the new hint.\n- 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.\n- `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`.\n- 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.\n\n## Delete View\n\n### Endpoint\n\nDelete a view and every view derived from it.\n\n```http\nDELETE /v1/projects/:project_id/views/:view_id\n```\n\n**Scope:** `views:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Response\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Views derived from this one are deleted with it.\n- 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.\n- Backfill and indexing work for the whole cascade is cancelled immediately, so a deleted view stops consuming capacity before its rows are removed.\n\n## Force Delete View\n\n### Endpoint\n\nDelete a view permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/views/:view_id/force\n```\n\n**Scope:** `views:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Response\n\nView queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live view as well as one already scheduled for deletion.\n- 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.\n\n## List View Records\n\n### Endpoint\n\nRetrieve the transformed records a view has produced.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/records\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewRecord[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- A cursor is only valid for the query that produced it. Reusing one after changing the sort direction returns a 400 rather than a page of skipped or repeated records, so start again without a cursor.\n- 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.\n- While the project's server is stopped this answers 409 `server_stopped`. Start the server and try again.\n- `after` and `before` are mutually exclusive.\n\n## List Waiting Shapes\n\n### Endpoint\n\nRetrieve the event shapes a view has deferred because no transform serves them yet.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/deferrals\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`resolved`** `string`\n Filter by resolution. `false` returns only shapes still waiting. Optional. Allowed values: `\"true\"`, `\"false\"`.\n\n- **`order_by`** `string`\n Sort field. Defaults to `last_deferred_at`. Optional. Allowed values: `\"first_deferred_at\"`, `\"last_deferred_at\"`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewShapeDeferral[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Deferred records are stored, not lost. They backfill automatically once a script covers the shape, and each entry's `reason` says what unblocks it.\n- `event_count` is approximate: ingest retries can count an event more than once.\n- While the project's server is stopped this answers 409 `server_stopped`. Start the server and try again.\n- `after` and `before` are mutually exclusive.\n\n## Inspect Waiting Shape\n\n### Endpoint\n\nInspect structural differences and candidate output bindings for a waiting shape.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`deferral_id`** `string` -- **Required**\n Unique identifier of the deferral.\n\n### Response\n\nWaiting shape inspected\n\n```ts\n{\n message: string;\n data: ViewShapeInspection;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- While the project's server is stopped this answers 409 `server_stopped`. Start the server and try again.\n\n## Retrieve Waiting Shape Hint\n\n### Endpoint\n\nRetrieve the model's current binding question for a waiting shape.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id/hint\n```\n\n**Scopes:** `views:read` + `sources:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`deferral_id`** `string` -- **Required**\n Unique identifier of the deferral.\n\n### Response 200 (1)\n\nNo pending hint question\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Response 200 (2)\n\nWaiting shape hint retrieved\n\n```ts\n{\n message: string;\n data: ViewPendingHint;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Returns null when no current question remains. Answer using this question's episode, mapping_generation, requested_at, and transform_id.\n\n## Decide Waiting Shape\n\n### Endpoint\n\nResolve the next action for one view and schema version.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/deferrals/:deferral_id/decision\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`deferral_id`** `string` -- **Required**\n Unique identifier of the deferral.\n\n### Request Body\n\n- **`episode`** `integer` -- **Required**\n The deferral episode shown when the decision was inspected. Minimum: `1`.\n\n- **`action`** `string` -- **Required**\n Authorize a bounded authoring attempt, answer a hint, or exclude this exact version in this view. Allowed values: `\"author\"`, `\"hint\"`, `\"exclude\"`.\n\n- **`mapping_generation`** `integer` -- **Required**\n The mapping generation shown when the decision was inspected. Minimum: `1`.\n\n- **`hint`** `string`\n Answer to the model's binding question. Optional. Minimum length: `1`. Maximum length: `4096`.\n\n- **`hint_requested_at`** [`ISODateString`](/api/views#iso-date-string)\n Exact requested_at of the pending hint being answered. Optional.\n\n- **`hint_transform_id`** `string`\n Transform that owns the pending hint being answered. Optional. Minimum length: `1`. Maximum length: `32`.\n\n- **`reason`** `string`\n Required audit reason for an exclusion. Optional. Minimum length: `1`. Maximum length: `2000`.\n\n- **`mapping_policy`** `object`\n Explicit null, fallback, drop, and source-binding rules. Optional.\n\n - **`nullable_inputs`** `string[]`\n Read paths explicitly permitted to be null. Optional.\n\n - **`fallback_groups`** `string[][]`\n Approved ordered fallback read paths. Optional.\n\n - **`output_bindings`** `object`\n Required source path segments for each output binding. Optional.\n\n - **`allow_drops`** `boolean`\n Allow a validated mapping to emit no output for a record. Optional.\n\n### Response\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Authoring and hint answers reserve a bounded model budget. Exclusion covers unprocessed and future records of this exact version in this view.\n- While the project's server is stopped this answers 409 `server_stopped`. Start the server and try again.\n- Answering a hint requires `hint`; excluding a version requires `reason`. Exclusion covers unprocessed and future records with this exact schema version in this view.\n- 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.\n\n## List View Transforms\n\n### Endpoint\n\nRetrieve a list of a view's transforms.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/transforms\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Creation order matches schema version order. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"updated_at\"`.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n Return only transforms in this state. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewTransform[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve View Transform\n\n### Endpoint\n\nRetrieve a single transform.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/transforms/:transform_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTransform retrieved\n\n```ts\n{\n message: string;\n data: ViewTransform;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## List View Versions\n\n### Endpoint\n\nRetrieve a list of the schema versions a view covers.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/versions\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Optional. Defaults to `\"version\"`. Allowed values: `\"version\"`.\n\n- **`status`** [`ViewTransformStatus`](/api/views#view-transform-status)\n Return only versions whose transform is in this state. Optional.\n\n- **`script_hash`** `string`\n Return only versions served by this script. Take the value from a version's row. Optional. Minimum length: `64`. Maximum length: `64`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ViewVersion[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Versions sharing a `script_hash` run the same transform. Filter by it to see every version one script serves.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve View Version\n\n### Endpoint\n\nRetrieve one schema version a view covers.\n\n```http\nGET /v1/projects/:project_id/views/:view_id/versions/:version_id\n```\n\n**Scope:** `views:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`version_id`** `integer` -- **Required**\n The schema version number: the `version` a view version carries.\n\n### Response\n\nVersion retrieved\n\n```ts\n{\n message: string;\n data: ViewVersion;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `version_id` is the schema version NUMBER, the `version` a row carries, not a `vta_` id.\n\n## Create View Transform\n\n### Endpoint\n\nAdd a transform that maps incoming records into the view's output schema.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Request Body\n\n- **`collection_schema_version`** `integer | null`\n Collection schema version this transform covers. Null for view-to-view transforms. Optional.\n\n- **`transform_script`** `string`\n TGL transform script. Omit to have one generated. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n### Response\n\nTransform created\n\n```ts\n{\n message: string;\n data: ViewTransform;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `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.\n- The script must write every field the view's output schema declares.\n- Omitting the script queues the transform for generation instead of rejecting it.\n- 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.\n- `transform_script` cannot be whitespace-only. Omit it to have TGL generated instead.\n\n## Update View Transform\n\n### Endpoint\n\nUpdate a transform's script.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms/:transform_id\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Request Body\n\n- **`transform_script`** `string`\n TGL transform script. To clear and regenerate, use the regenerate endpoint instead. Optional. Minimum length: `1`. Maximum length: `65536`.\n\n- **`conflict_resolution`** `string`\n How conflicting schema versions are handled when a script edit fans out. Optional. Defaults to `\"strand\"`. Allowed values: `\"strand\"`, `\"keep\"`.\n\n- **`dry_run`** `boolean`\n Preview the edit: returns the per-version verdict report without changing anything. Optional. Defaults to `false`.\n\n- **`preview_token`** `string`\n 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`.\n\n### Response 200 (1)\n\n```ts\n{\n message: string;\n 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; } };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`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**\n\n### Response 200 (2)\n\nTransform updated\n\n```ts\n{\n message: string;\n data: ViewTransform & { edit_report: { script_hash: string | null; version_verdicts: never[]; } };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`edit_report`** `{ script_hash: string | null; version_verdicts: never[]; }` -- **Required**\n\n### Comments\n\n- `transform_script` is TGL and must compile, and must write every field the view's output schema declares.\n- 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.\n- Conflicting versions are moved to queued placeholders by default. Set `conflict_resolution` to `keep` to leave those versions on their prior script.\n- 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.\n- 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.\n- 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.\n- 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.\n- `transform_script` cannot be whitespace-only. To clear a script and have it regenerated, use the regenerate endpoint rather than sending an empty one.\n- 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.\n\n## Generate View Transform Script\n\n### Endpoint\n\nQueue TGL generation for a transform from the view's input and output schemas.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms/:transform_id/generate\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTGL generation queued\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Generation runs in the background. This returns once the work is queued, not once a script exists; poll the transform to see the result.\n- The generated script is written to the transform and replaces whatever was there.\n\n## Re-backfill View Transform\n\n### Endpoint\n\nRe-run a transform over every record the view has already read.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/transforms/:transform_id/rebackfill\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTransform re-backfill started\n\n```ts\n{\n message: string;\n data: ViewTransform;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Use this after editing a script so existing records pick up the new logic. Without it, an edit only affects records that arrive afterwards.\n- 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.\n- Refused while a metric, derived view or join view depends on this view.\n\n## Regenerate View\n\n### Endpoint\n\nReplace a view's output schema and queue every transform to be regenerated against it.\n\n```http\nPOST /v1/projects/:project_id/views/:view_id/regenerate\n```\n\n**Scope:** `views:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n### Request Body\n\n- **`output_schema`** `object[]`\n The fields the view should produce. Omit to regenerate against the current schema. Optional.\n\n - **`name`** `string` -- **Required**\n Field name as it appears on the view's records. Minimum length: `1`. Maximum length: `128`.\n\n - **`type`** `string` -- **Required**\n Value type the field holds. Allowed values: `\"string\"`, `\"number\"`, `\"boolean\"`, `\"date\"`, `\"array\"`, `\"null\"`.\n\n - **`description`** `string`\n What the field means. Used as guidance when a transform script is generated. Optional. Maximum length: `512`.\n\n- **`hint`** `string | null`\n Instructions the generator cannot infer from field names alone, such as a formatting rule or a business threshold. Optional.\n\n### Response\n\nView schema updated and transforms queued for regeneration\n\n```ts\n{\n message: string;\n data: View;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- 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.\n- Existing records keep their old shape until the regenerated transforms backfill over them.\n- Refused while a metric, derived view or join view depends on this view.\n- `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`.\n- 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.\n\n## Delete View Transform\n\n### Endpoint\n\nDelete a transform from a view.\n\n```http\nDELETE /v1/projects/:project_id/views/:view_id/transforms/:transform_id\n```\n\n**Scope:** `views:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`view_id`** `string` -- **Required**\n Unique identifier of the view.\n\n- **`transform_id`** `string` -- **Required**\n Unique identifier of the transform.\n\n### Response\n\nTransform deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Records the transform already produced are left in place. Re-backfill the view's remaining transforms to rebuild its output without this one.\n\n",
|
|
24
24
|
"api/metrics": "# Metrics\n\n## Metric Model\n\n### Fields\n\n- **`object`** `\"metric\"`\n\n- **`id`** `string`\n\n- **`name`** `string`\n\n- **`team_id`** `string`\n\n- **`project_id`** `string`\n\n- **`deleted_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n When the metric is scheduled to be deleted; null when it is not.\n\n- **`view_id`** `string`\n\n The view whose records the metric aggregates. Changing it to a compatible view in the same\n project rebuilds the metric's historical data.\n\n- **`status`** [`MetricStatus`](/api/metrics#metric-status)\n\n Current lifecycle state for live collection and historical backfill.\n\n- **`group_by`** `string[]`\n\n View fields whose value combinations define distinct metric series.\n\n- **`filters`** `string`\n\n Filter expression applied to records before aggregation.\n\n- **`timestamp_field`** `string`\n\n View field used as the aggregation timestamp.\n\n- **`value_field`** `string | null`\n\n View field used as the numeric aggregation value.\n\n- **`unique_field`** `string | null`\n\n View field used for distinct-value counts.\n\n- **`null_handling`** [`NullHandling`](/api/metrics#null-handling)\n\n How null or missing metric values are aggregated.\n\n- **`empty_bucket_handling`** [`EmptyBucketHandling`](/api/metrics#empty-bucket-handling)\n\n How chart buckets without observations are represented.\n\n- **`group_by_display_fields`** `Record<string, string> | null`\n\n Display labels for the fields in `group_by`.\n\n- **`backfill_strategy`** [`BackfillStrategy`](/api/metrics#backfill-strategy)\n\n Order used when historical data is rebuilt.\n\n- **`ui_chart_family`** [`ChartFamily`](/api/metrics#chart-family)\n\n Read-only: derived from `ui_chart_type`, grouping chart types by layout family.\n\n- **`ui_chart_type`** [`ChartType`](/api/metrics#chart-type)\n\n- **`ui_chart_color_mode`** [`ChartColorMode`](/api/metrics#chart-color-mode)\n\n Strategy used to assign chart colors.\n\n- **`ui_chart_color_base`** `string | null`\n\n Base color used by intensity mode, one of `CHART_COLORS`. Null when the color mode does not use\n one.\n\n- **`ui_chart_color_rules`** `MetricColorRule[] | null`\n\n Ordered value-to-color rules used by value mode.\n\n- **`ui_chart_curve`** [`ChartCurve`](/api/metrics#chart-curve)\n\n Line interpolation style.\n\n- **`ui_chart_show_alerts`** `boolean`\n\n Whether the chart displays alert markers.\n\n- **`ui_chart_compact_values`** `boolean`\n\n Whether chart tooltips round values to compact notation, such as 321.3M.\n\n- **`ui_chart_y_min`** `number | null`\n\n Fixed lower bound for the y axis. Null lets the axis fit the data.\n\n- **`ui_chart_y_max`** `number | null`\n\n Fixed upper bound for the y axis. Null lets the axis fit the data.\n\n- **`ui_chart_show_monitors`** `boolean`\n\n Whether the chart displays monitor thresholds.\n\n- **`ui_chart_show_trends`** `boolean`\n\n Whether the chart displays regression trends.\n\n- **`ui_chart_show_forecast`** `boolean`\n\n Whether the chart opens with its forecast shown, projecting the fitted trend past now to the\n saved horizon. Viewers in the app can change the forecast for their own view without changing\n this setting.\n\n- **`ui_chart_forecast_horizon`** [`ForecastHorizon`](/api/metrics#forecast-horizon)\n\n How far past now the chart projects when it draws a forecast. Relative values resolve against\n the moment the chart is viewed, not the moment they were saved.\n\n- **`ui_chart_forecast_model`** [`ForecastModel`](/api/metrics#forecast-model)\n\n Curve the forecast is fitted with. `auto` follows whichever model fits the series best at view\n time, so the projected shape can change as data arrives.\n\n- **`ui_value_unit`** `string | null`\n\n Unit label displayed with chart values.\n\n- **`ui_chart_value`** [`MetricChartValue`](/api/metrics#metric-chart-value)\n\n Aggregation value displayed by default.\n\n- **`ui_chart_time_range`** [`AnalyticsTimeRange`](/api/metrics#analytics-time-range)\n\n Window the chart covers by default.\n\n- **`ui_chart_interval`** [`ChartInterval`](/api/metrics#chart-interval)\n\n Bucket size the chart uses by default.\n\n- **`ui_chart_custom_range_start_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n Start of the custom chart range.\n\n- **`ui_chart_custom_range_end_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n End of the custom chart range.\n\n- **`ui_chart_max_series`** `number`\n\n Maximum number of individual series displayed by default.\n\n- **`created_at`** [`ISODateString`](/api/metrics#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/metrics#iso-date-string)\n\n- **`backfill_percent`** `number`\n\n Percentage of the view's data files the historical backfill has processed.\n\n- **`backfill_total_count`** `number`\n\n How many of the view's data files the historical backfill has to process. Seeded as 1 before\n the run is planned.\n\n- **`backfill_completed_count`** `number`\n\n How many of those files it has processed.\n\n- **`backfill_completed_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n Null until the backfill finishes.\n\n- **`backfill_started_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n Null until the backfill starts.\n\n- **`compact_percent`** `number`\n\n Percentage of rollup-building steps completed.\n\n- **`compact_total_count`** `number`\n\n How many rollup-building steps the run has planned; 0 until that pass starts.\n\n- **`compact_completed_count`** `number`\n\n How many of those steps are done.\n\n- **`compact_started_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n Null until the rollup-building pass starts.\n\n- **`has_triggered_monitors`** `boolean`\n\n Whether at least one alert is currently open. Ended alerts remain in history.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### MetricStatus\n\n`\"initializing\" | \"waiting_for_transforms\" | \"backfilling\" | \"active\" | \"error\" | \"cancelled\"`\n\n#### NullHandling\n\n`\"skip\" | \"count_as_zero\"`\n\n#### EmptyBucketHandling\n\n`\"zero\" | \"gaps\"`\n\n#### BackfillStrategy\n\n`\"newest_first\" | \"oldest_first\"`\n\n#### ChartFamily\n\n`\"cartesian\" | \"radial\" | \"geographic\" | \"temporal\" | \"hierarchical\"`\n\n#### ChartType\n\n`\"line\" | \"area\" | \"bar\" | \"pie\" | \"scatter\" | \"radar\" | \"stat\" | \"gauge\" | \"calendar\" | \"uptime\"`\n\n#### ChartColorMode\n\n`\"by_series\" | \"by_intensity\" | \"by_value\"`\n\n#### ChartColor\n\n`\"blue\" | \"red\" | \"amber\" | \"green\" | \"teal\" | \"purple\" | \"pink\"`\n\n#### ChartCurve\n\n`\"linear\" | \"step\" | \"smooth\"`\n\n#### ForecastHorizon\n\n`\"next_7_days\" | \"next_30_days\" | \"next_90_days\" | \"end_of_quarter\" | \"end_of_year\" | \"next_year\"`\n\nHow far past now a chart projects its forecast. Values are relative to the moment the chart is\nviewed, so a saved horizon keeps projecting the same distance ahead as time passes rather than\nexpiring on a fixed date. `end_of_quarter` and `end_of_year` run to the end of the calendar\nperiod that contains today, in UTC.\n\n#### ForecastModel\n\n`\"auto\" | \"linear\" | \"exponential\" | \"logarithmic\" | \"logistic\" | \"sinusoidal\"`\n\nThe curve a forecast is fitted with. `auto` follows the best-fitting model for the series, which\nis recalculated as data arrives and can therefore change between views; naming a model pins the\nprojection to that curve.\n\n#### MetricChartValue\n\n`\"count\" | \"average\" | \"sum\" | \"min\" | \"max\" | \"last\" | \"cumulative_sum\" | \"cumulative_count\" | \"p50\" | \"p95\" | \"p99\" | \"count_unique\"`\n\nAggregation applied to a metric's values. Use `last` when records are snapshots of a persistent\nthing such as a deal, user, or inventory item: each time bucket contains the latest reading for\nthat series in the bucket. Within the requested range, the last observed value carries forward\nacross complete empty intervals instead of reading them as zero.\n\n#### AnalyticsTimeRange\n\n`\"last_hour\" | \"last_6_hours\" | \"today\" | \"last_24_hours\" | \"yesterday\" | \"this_week\" | \"last_week\" | \"this_month\" | \"last_month\" | \"this_quarter\" | \"last_quarter\" | \"this_year\" | \"last_year\" | \"last_7_days\" | \"last_30_days\" | \"last_90_days\" | \"last_365_days\" | \"all_time\" | \"custom\" | \"next_7_days\" | \"next_30_days\" | \"next_90_days\"`\n\nThe window a chart reads. Stored as a plain string rather than a database enum: the set is\npresentation, not something any query filters on, and the forward ranges in particular are\nexpected to change as we learn what people forecast over.\n\nA `next_*` range ends after now, which is what turns a fitted trend into a visible forecast. The\nmeasured half of such a range is still measured; only the part past now is projected.\n\n#### ChartInterval\n\n`\"minute\" | \"hour\" | \"day\" | \"month\" | \"auto\"`\n\nThe Data Interval _setting_ (`Metric.ui_chart_interval` and the aggregation `interval` query\nparam). `\"auto\"` means the server picks the finest-safe tier for the current view, so brush-zoom\nnaturally drills into a finer bucket. The RESOLVED tier returned by aggregation is always a plain\n`DataInterval`.\n\n## Timeseries Aggregation Model\n\n### Fields\n\n- **`object`** `\"aggregation\"`\n\n- **`coverage_mode`** `\"strict\" | \"observed\" | null`\n\n Null for telemetry endpoints without a metric completeness contract.\n\n- **`coverage_reasons`** `{ view_id: string; view_name: string; reason: string; count: number | null; }[]`\n\n Count is null when the reason reports a status rather than a measured count.\n\n- **`start_at`** [`ISODateString`](/api/metrics#iso-date-string)\n\n- **`end_at`** [`ISODateString`](/api/metrics#iso-date-string)\n\n- **`interval`** [`DataInterval`](/api/metrics#data-interval)\n\n Bucket size each entry in a series' `records` covers.\n\n- **`values`** [`MetricChartValue[]`](/api/metrics#metric-chart-value)\n\n Declares what each number in a series' `records` arrays means; positions align.\n\n- **`series`** [`MetricSeries[]`](/api/metrics#aggregation-series-model)\n\n One entry per charted series. An explicit series filter returns exactly those series; otherwise\n pinned series are kept and every other series folds into a synthetic `__other__` entry, and\n with no pins the top series by value are kept up to the chart's limit.\n\n- **`series_total`** `number`\n\n Distinct real series permutations that had data in the requested range before the server folded\n the tail into the synthetic `__other__` series. Always present; equals the named series count\n when nothing folds. Clients subtract the number of named series they display to size the single\n \"Other (N)\" row and its warning, instead of counting the (already-folded) `series` array.\n\n- **`complete_through_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n Everything before this instant is final. Later buckets may still be filling in while ingest,\n transforms, or backfills catch up, so treat them as provisional. Null when no statement can be\n made, which is not the same as complete.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### DataInterval\n\n`\"minute\" | \"hour\" | \"day\" | \"month\"`\n\n#### MetricChartValue\n\n`\"count\" | \"average\" | \"sum\" | \"min\" | \"max\" | \"last\" | \"cumulative_sum\" | \"cumulative_count\" | \"p50\" | \"p95\" | \"p99\" | \"count_unique\"`\n\nAggregation applied to a metric's values. Use `last` when records are snapshots of a persistent\nthing such as a deal, user, or inventory item: each time bucket contains the latest reading for\nthat series in the bucket. Within the requested range, the last observed value carries forward\nacross complete empty intervals instead of reading them as zero.\n\n## Aggregation Series Model\n\n### Fields\n\n- **`details`** `{ label: string; hash: string; dimensions: Record<string, string | number | boolean | null>; color: string | null; }`\n\n The series' identity (`hash`, `dimensions`) and current presentation (`label`, `color`).\n\n- **`records`** `Record<string, (number | null)[]>`\n\n One entry per bucket, values aligned with the aggregation's `values` list. `null` means the\n bucket had no data and the metric's `empty_bucket_handling` is `gaps`.\n\n- **`regression`** `LinearRegression | ExponentialRegression | LogarithmicRegression | LogisticRegression | SinusoidalRegression | null`\n\n Trend line fitted over the series; null when none was computed.\n\n- **`forecast`** `RegressionForecast | null`\n\n The trend line's projected next value; null when no trend was computed.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Metric Series Model\n\n### Fields\n\n- **`object`** `\"metric_series\"`\n\n- **`id`** `string`\n\n- **`metric_id`** `string`\n\n- **`hash`** `string`\n\n Opaque identifier of the series' dimension combination, stable for the life of the metric.\n\n- **`dimensions`** `Record<string, string | number | boolean | null>`\n\n The `group_by` field values that define this series.\n\n- **`display_values`** `Record<string, string | null> | null`\n\n Readable value per dimension, read from the metric's `group_by_display_fields`; null when the\n metric defines none.\n\n- **`display_label`** `string | null`\n\n Readable name for the series, auto-derived at discovery or set explicitly. When null, charts\n build a label from the raw `dimensions`.\n\n- **`first_seen_at`** [`ISODateString`](/api/metrics#iso-date-string)\n\n When the series was registered. Stamped with the processing time, so a backfill registers\n historical combinations at run time.\n\n- **`last_seen_at`** [`ISODateString`](/api/metrics#iso-date-string)\n\n Also stamped at registration; not a recency signal, use `last_value_at` for that.\n\n- **`last_value`** `number | null`\n\n The most recent aggregated value observed for the series; used to rank series when none are\n pinned.\n\n- **`last_value_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n When `last_value` was recorded.\n\n- **`color`** `string | null`\n\n Chart color override; null means the color is picked automatically.\n\n- **`pinned_at`** [`ISODateString | null`](/api/metrics#iso-date-string)\n\n When the series was pinned; null means not pinned. Pins replace ranking: charts keep the pinned\n series and fold every other series into `__other__`, unless an explicit series filter is sent.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Metrics\n\n### Endpoint\n\nRetrieve a list of metrics for a project.\n\n```http\nGET /v1/projects/:project_id/metrics\n```\n\n**Scope:** `metrics:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`view_id`** `string`\n Return metrics that read from this view. Optional.\n\n- **`order_by`** `string`\n Field used to order the metrics. Optional. Defaults to `\"name\"`. Allowed values: `\"name\"`.\n\n- **`deleted_at`** [`NullableDateFilter`](/api/metrics#nullable-date-filter)\n Filter on deletion state. Pass `null` for metrics that are not scheduled for deletion. Optional.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Metric[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Metric\n\n### Endpoint\n\nRetrieve a single metric.\n\n```http\nGET /v1/projects/:project_id/metrics/:metric_id\n```\n\n**Scope:** `metrics:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Response\n\nMetric retrieved\n\n```ts\n{\n message: string;\n data: Metric;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Retrieve Metric Aggregation\n\n### Endpoint\n\nRetrieve aggregated time-series data for a metric.\n\n```http\nGET /v1/projects/:project_id/metrics/:metric_id/aggregation\n```\n\n**Scope:** `metrics:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Query Parameters\n\n- **`coverage_mode`** `string`\n Strict returns only final data. Observed includes partial data beyond complete_through_at without filling missing buckets. Optional. Defaults to `\"strict\"`. Allowed values: `\"strict\"`, `\"observed\"`.\n\n- **`start_at`** [`ISODateString`](/api/metrics#iso-date-string)\n Start of the aggregation range. Send together with `end_at`. Optional.\n\n- **`end_at`** [`ISODateString`](/api/metrics#iso-date-string)\n End of the aggregation range. Send together with `start_at`. Optional.\n\n- **`interval`** [`ChartInterval`](/api/metrics#chart-interval)\n Requested aggregation bucket interval. Optional.\n\n- **`time_range`** [`AnalyticsTimeRange`](/api/metrics#analytics-time-range)\n Preset used to calculate the aggregation range. Ignored when `start_at` and `end_at` are both sent. Defaults to the metric's saved chart range. Optional.\n\n- **`timezone`** `string`\n IANA timezone used to calculate date boundaries. Optional.\n\n- **`page_id`** `string`\n Page whose saved custom range should be used. Applies only when `time_range` is `custom`. Optional.\n\n- **`normalize`** `boolean`\n Whether the response includes empty time buckets. Optional. Defaults to `true`.\n\n- **`forecast_at`** [`ISODateString`](/api/metrics#iso-date-string)\n Future timestamp requested for a forecast. Optional.\n\n- **`max_series`** `integer`\n Maximum number of individual series to return. Optional. Minimum: `1`. Maximum: `50`.\n\n- **`series`** `string`\n Comma-separated series hashes to return. Optional.\n\n### Response\n\n```ts\n{\n message: string;\n data: TimeseriesAggregation;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `last` returns the latest reading in each time bucket within the requested range.\n- `coverage_mode=observed` returns partial observed rows beyond the strict boundary without filling missing buckets. `coverage_reasons` names unresolved view scopes; `excluded` is informational.\n- Record keys are UTC bucket starts. A bucket is final only when its UTC calendar end (start plus one `interval`) is at or before `complete_through_at`; strict omits all other buckets. A null cutoff means unknown completeness. Observed may include populated partial buckets even when the requested end lies inside one, and does not synthesize empty partial buckets.\n- The aggregation window is resolved in order: an explicit `start_at` plus `end_at` wins; otherwise `time_range` is used; otherwise the metric's saved chart range is used.\n- `start_at` and `end_at` only take effect when sent together. Sending one without the other has no effect on the window.\n- When both are provided, `start_at` must be earlier than `end_at`.\n- `time_range=custom` without explicit dates falls back to the saved custom range of `page_id`, then the metric, then the project.\n- `timezone` must be a valid IANA timezone name, or `UTC`.\n\n## Create Metric\n\n### Endpoint\n\nCreate a metric for a project.\n\n```http\nPOST /v1/projects/:project_id/metrics\n```\n\n**Scope:** `metrics:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n The subject only, without the aggregation. Charts title themselves as the aggregation followed by this name, so `API Latency` renders as `P95 of API Latency`. Putting the aggregation here produces `P95 of API P95 Latency`. Minimum length: `1`. Maximum length: `64`.\n\n- **`view_id`** `string` -- **Required**\n View that provides the metric's records. Maximum length: `32`.\n\n- **`group_by`** `string[]`\n View fields used to split records into series. Optional. Defaults to `[]`.\n\n- **`group_by_display_fields`** `object | null`\n Maps a field in `group_by` to another view field whose value labels the series, so a chart grouped on an opaque id reads as `api.example.com` instead of `chk_a1b2c3`. Each label is read from the first record of a series and kept from then on. Optional.\n\n- **`filters`** `string`\n A query string of `?field=operator:value` pairs joined by `&`, applied when records are aggregated. For example `?status=equals:200` or `?level=in:error,warn&path=starts_with:/api`. Comma-separate the values of `in`, `not_in`, and `between`. Every operator must be spelled in full; short forms such as `eq:` are rejected. Optional. Defaults to `\"\"`. Maximum length: `8192`.\n\n- **`timestamp_field`** `string`\n View field used as the aggregation timestamp. Optional. Defaults to `\"timestamp\"`. Minimum length: `1`. Maximum length: `256`.\n\n- **`value_field`** `string | null`\n View field used as the numeric aggregation value. Optional.\n\n- **`unique_field`** `string | null`\n View field used to count distinct values. Optional.\n\n- **`null_handling`** [`NullHandling`](/api/metrics#null-handling)\n How null or missing metric values are aggregated. Optional. Defaults to `\"skip\"`.\n\n- **`empty_bucket_handling`** [`EmptyBucketHandling`](/api/metrics#empty-bucket-handling)\n How chart buckets without observations are represented. Optional.\n\n- **`ui_chart_type`** [`ChartType`](/api/metrics#chart-type)\n Default chart type. Optional.\n\n- **`ui_chart_color_mode`** [`ChartColorMode`](/api/metrics#chart-color-mode)\n Strategy used to assign chart colors. Optional.\n\n- **`ui_chart_color_base`** [`ChartColor`](/api/metrics#chart-color)\n Base color used by intensity mode. Optional.\n\n- **`ui_chart_color_rules`** `object[] | null`\n Ordered value-to-color rules used by value mode. Optional.\n\n- **`ui_chart_show_alerts`** `boolean`\n Whether the chart displays alert markers. Optional.\n\n- **`ui_chart_show_monitors`** `boolean`\n Whether the chart displays monitor thresholds. Optional.\n\n- **`ui_chart_show_trends`** `boolean`\n Whether the chart displays regression trends. Optional.\n\n- **`ui_chart_compact_values`** `boolean`\n Whether chart tooltips round values to compact notation, such as 321.3M instead of the full number. Optional.\n\n- **`ui_chart_y_min`** `number | null`\n Fixed lower bound for the chart's y axis. Null lets the axis fit the data. Optional.\n\n- **`ui_chart_y_max`** `number | null`\n Fixed upper bound for the chart's y axis. Null lets the axis fit the data. Optional.\n\n- **`ui_chart_show_forecast`** `boolean`\n Whether the chart opens with its forecast shown, projecting the fitted trend past now to the saved horizon. Optional.\n\n- **`ui_chart_forecast_horizon`** [`ForecastHorizon`](/api/metrics#forecast-horizon)\n How far past now the chart projects its forecast. The value is relative to when the chart is viewed, so it keeps projecting the same distance ahead as time passes. Optional.\n\n- **`ui_chart_forecast_model`** [`ForecastModel`](/api/metrics#forecast-model)\n Curve the forecast is fitted with. Use `auto` to follow whichever model currently fits the series best. Optional.\n\n- **`ui_value_unit`** `string`\n Unit label displayed with chart values. Optional. Maximum length: `32`.\n\n- **`ui_chart_curve`** [`ChartCurve`](/api/metrics#chart-curve)\n Chart line interpolation style. Optional.\n\n- **`ui_chart_value`** [`MetricChartValue`](/api/metrics#metric-chart-value)\n Aggregation value displayed by default. Optional.\n\n- **`ui_chart_time_range`** [`AnalyticsTimeRange`](/api/metrics#analytics-time-range)\n Time range displayed by default. Optional.\n\n- **`ui_chart_interval`** [`ChartInterval`](/api/metrics#chart-interval)\n Aggregation interval displayed by default. Optional.\n\n- **`ui_chart_custom_range_start_at`** [`ISODateString`](/api/metrics#iso-date-string)\n Start of the custom chart range. Optional.\n\n- **`ui_chart_custom_range_end_at`** [`ISODateString`](/api/metrics#iso-date-string)\n End of the custom chart range. Optional.\n\n- **`ui_chart_max_series`** `integer`\n Maximum number of individual series displayed by default. Optional. Minimum: `1`. Maximum: `50`.\n\n- **`backfill_strategy`** [`BackfillStrategy`](/api/metrics#backfill-strategy)\n Order used to rebuild historical data. Optional.\n\n### Response\n\nYour metric has been created and will begin collecting data immediately.\n\n```ts\n{\n message: string;\n data: Metric;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `view_id` is required when creating a metric.\n- Field references and filters must be compatible with the selected view.\n- Custom chart ranges require both dates, with an interval of at least 30 minutes.\n- Color-mode configuration must match the selected chart color mode.\n- `filters` must be a valid filter expression.\n- `timestamp_field` cannot reference `__meta__` metadata.\n- `group_by_display_fields` is set at create and cannot be changed afterwards, because each series takes its label from the first record it sees. Map a `group_by` field to a view field that carries a readable value for the same record.\n\n## Update Metric\n\n### Endpoint\n\nUpdate an existing metric.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id\n```\n\n**Scope:** `metrics:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Request Body\n\n- **`name`** `string`\n The subject only, without the aggregation. Charts title themselves as the aggregation followed by this name, so `API Latency` renders as `P95 of API Latency`. Putting the aggregation here produces `P95 of API P95 Latency`. Optional. Minimum length: `1`. Maximum length: `64`.\n\n- **`view_id`** `string`\n View that provides the metric's records. Optional. Maximum length: `32`.\n\n- **`group_by`** `string[]`\n View fields used to split records into series. Optional.\n\n- **`filters`** `string`\n A query string of `?field=operator:value` pairs joined by `&`, applied when records are aggregated. For example `?status=equals:200` or `?level=in:error,warn&path=starts_with:/api`. Comma-separate the values of `in`, `not_in`, and `between`. Every operator must be spelled in full; short forms such as `eq:` are rejected. Optional. Maximum length: `8192`.\n\n- **`timestamp_field`** `string`\n View field used as the aggregation timestamp. Optional. Minimum length: `1`. Maximum length: `256`.\n\n- **`value_field`** `string | null`\n View field used as the numeric aggregation value. Optional.\n\n- **`unique_field`** `string | null`\n View field used to count distinct values. Optional.\n\n- **`null_handling`** [`NullHandling`](/api/metrics#null-handling)\n How null or missing metric values are aggregated. Optional.\n\n- **`empty_bucket_handling`** [`EmptyBucketHandling`](/api/metrics#empty-bucket-handling)\n How chart buckets without observations are represented. Optional.\n\n- **`ui_chart_type`** [`ChartType`](/api/metrics#chart-type)\n Default chart type. Optional.\n\n- **`ui_chart_color_mode`** [`ChartColorMode`](/api/metrics#chart-color-mode)\n Strategy used to assign chart colors. Optional.\n\n- **`ui_chart_color_base`** [`ChartColor`](/api/metrics#chart-color)\n Base color used by intensity mode. Optional.\n\n- **`ui_chart_color_rules`** `object[] | null`\n Ordered value-to-color rules used by value mode. Optional.\n\n- **`ui_chart_show_alerts`** `boolean`\n Whether the chart displays alert markers. Optional.\n\n- **`ui_chart_show_monitors`** `boolean`\n Whether the chart displays monitor thresholds. Optional.\n\n- **`ui_chart_show_trends`** `boolean`\n Whether the chart displays regression trends. Optional.\n\n- **`ui_chart_compact_values`** `boolean`\n Whether chart tooltips round values to compact notation, such as 321.3M instead of the full number. Optional.\n\n- **`ui_chart_y_min`** `number | null`\n Fixed lower bound for the chart's y axis. Null lets the axis fit the data. Optional.\n\n- **`ui_chart_y_max`** `number | null`\n Fixed upper bound for the chart's y axis. Null lets the axis fit the data. Optional.\n\n- **`ui_chart_show_forecast`** `boolean`\n Whether the chart opens with its forecast shown, projecting the fitted trend past now to the saved horizon. Optional.\n\n- **`ui_chart_forecast_horizon`** [`ForecastHorizon`](/api/metrics#forecast-horizon)\n How far past now the chart projects its forecast. The value is relative to when the chart is viewed, so it keeps projecting the same distance ahead as time passes. Optional.\n\n- **`ui_chart_forecast_model`** [`ForecastModel`](/api/metrics#forecast-model)\n Curve the forecast is fitted with. Use `auto` to follow whichever model currently fits the series best. Optional.\n\n- **`ui_value_unit`** `string`\n Unit label displayed with chart values. Optional. Maximum length: `32`.\n\n- **`ui_chart_curve`** [`ChartCurve`](/api/metrics#chart-curve)\n Chart line interpolation style. Optional.\n\n- **`ui_chart_value`** [`MetricChartValue`](/api/metrics#metric-chart-value)\n Aggregation value displayed by default. Optional.\n\n- **`ui_chart_time_range`** [`AnalyticsTimeRange`](/api/metrics#analytics-time-range)\n Time range displayed by default. Optional.\n\n- **`ui_chart_interval`** [`ChartInterval`](/api/metrics#chart-interval)\n Aggregation interval displayed by default. Optional.\n\n- **`ui_chart_custom_range_start_at`** [`ISODateString`](/api/metrics#iso-date-string)\n Start of the custom chart range. Optional.\n\n- **`ui_chart_custom_range_end_at`** [`ISODateString`](/api/metrics#iso-date-string)\n End of the custom chart range. Optional.\n\n- **`ui_chart_max_series`** `integer`\n Maximum number of individual series displayed by default. Optional. Minimum: `1`. Maximum: `50`.\n\n- **`backfill_strategy`** [`BackfillStrategy`](/api/metrics#backfill-strategy)\n Order used to rebuild historical data. Optional.\n\n- **`deleted_at`** `null`\n Send `null` to restore a metric that is scheduled for deletion. The deletion date itself is set by the delete endpoint and cannot be written here. Optional.\n\n### Response\n\nYour metric has been updated and is calculating historical data\n\n```ts\n{\n message: string;\n data: Metric;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Field references and filters must be compatible with the selected view.\n- Changing the metric's data feed rebuilds its historical rollups.\n- Custom chart ranges require both dates, with an interval of at least 30 minutes.\n- Color-mode configuration must match the selected chart color mode.\n- `filters` must be a valid filter expression.\n- `timestamp_field` cannot reference `__meta__` metadata.\n\n## Backfill Metric Data\n\n### Endpoint\n\nBackfill a metric's historical data from its view.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/backfill\n```\n\n**Scope:** `metrics:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Response\n\nYour metric data is being rebuilt from its view\n\n```ts\n{\n message: string;\n data: Metric;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Delete Metric\n\n### Endpoint\n\nDelete a metric.\n\n```http\nDELETE /v1/projects/:project_id/metrics/:metric_id\n```\n\n**Scope:** `metrics:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Response\n\n```ts\n{\n message: string;\n data: Metric;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The metric is hidden immediately and permanently removed once its restore window elapses. Restore it before then by updating it with `deleted_at` set to null.\n- Collection and backfill keep running for the whole window, so a restored metric resumes with no gap in its history. Its monitors stop evaluating until it is restored.\n\n## Force Delete Metric\n\n### Endpoint\n\nDelete a metric permanently, without waiting out its restore window.\n\n```http\nDELETE /v1/projects/:project_id/metrics/:metric_id/force\n```\n\n**Scope:** `metrics:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Response\n\nMetric queued for permanent deletion.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Works on a live metric as well as one already scheduled for deletion.\n- Its rollups and monitors are destroyed. The view it reads is not affected, so the metric can be recreated and backfilled.\n\n## List Metric Series\n\n### Endpoint\n\nRetrieve the series discovered for a metric.\n\n```http\nGET /v1/projects/:project_id/metrics/:metric_id/series\n```\n\n**Scope:** `metrics:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Query Parameters\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n- **`order_by`** `string`\n Field used to order the metric series. Optional. Defaults to `\"first_seen_at\"`. Allowed values: `\"first_seen_at\"`, `\"last_seen_at\"`.\n\n- **`search`** `string`\n Substring matched against labels, hashes, and dimension values. Optional. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\n```ts\n{\n message: string;\n data: MetricSeriesRecord[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Update Metric Series\n\n### Endpoint\n\nUpdate the appearance overrides for one metric series.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/series/:series_id\n```\n\n**Scope:** `metrics:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n- **`series_id`** `string` -- **Required**\n Unique identifier of the sery.\n\n### Request Body\n\n- **`color`** [`ChartColor`](/api/metrics#chart-color)\n Color override; null restores automatic color selection. Optional.\n\n- **`pinned`** `boolean`\n Whether the series is pinned ahead of ranked series. Optional.\n\n- **`display_label`** `string | null`\n Display label override; null restores the derived label. Optional.\n\n### Response\n\nSeries appearance updated.\n\n```ts\n{\n message: string;\n data: MetricSeriesRecord;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- At least one of `color`, `pinned`, or `display_label` must be provided.\n\n",
|
|
25
25
|
"api/monitors": "# Monitors\n\n## Monitor Model\n\n### Fields\n\n- **`object`** `\"monitor\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `mon_`.\n\n- **`slug`** `string`\n\n URL-safe identifier for the monitor.\n\n- **`team_id`** `string`\n\n- **`project_id`** `string`\n\n- **`metric_id`** `string`\n\n The metric this monitor watches. Fixed for the life of the monitor.\n\n- **`created_at`** [`ISODateString`](/api/monitors#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/monitors#iso-date-string)\n\n- **`name`** `string`\n\n- **`description`** `string | null`\n\n What this monitor watches for.\n\n- **`status`** [`MonitorStatus`](/api/monitors#monitor-status)\n\n Whether the monitor evaluates on its schedule or is paused.\n\n- **`chart_value`** [`MetricChartValue`](/api/monitors#metric-chart-value)\n\n Which value of the metric is compared against the threshold.\n\n- **`scope`** [`MonitorScope`](/api/monitors#monitor-scope)\n\n Whether the monitor fires once for the metric, or tracks each series separately.\n\n- **`time_window_minutes`** `number | null`\n\n How far back each evaluation looks when it reads the metric.\n\n- **`condition_type`** [`MonitorConditionType`](/api/monitors#monitor-condition-type)\n\n Threshold compares a value; sustained adds a duration; existence means value > 0; absence means\n no observations in a complete window.\n\n- **`operator`** [`MonitorOperator`](/api/monitors#monitor-operator)\n\n How the metric value is compared against the threshold.\n\n- **`threshold_value`** `number | null`\n\n The value the metric is compared against.\n\n- **`sustained_minutes`** `number | null`\n\n How long the condition must hold before the monitor fires.\n\n- **`schedule_cron`** `string`\n\n Cron expression, in UTC, setting how often the monitor evaluates.\n\n- **`alert_mode`** [`MonitorAlertMode`](/api/monitors#monitor-alert-mode)\n\n Whether an alert stays open until the condition clears, or opens and ends at once.\n\n- **`expires_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n\n When the monitor stops evaluating; null when it does not expire.\n\n- **`series_filters`** `{ include?: Record<string, string>[] | undefined; exclude?: Record<string, string>[] | undefined; } | null`\n\n Narrows which of the metric's series this monitor watches.\n\n- **`min_occurrences`** `number | null`\n\n How many times the condition must be met before the monitor fires.\n\n- **`occurrence_window_minutes`** `number | null`\n\n The window those occurrences have to fall within.\n\n- **`last_evaluated_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n\n When the monitor last evaluated.\n\n- **`next_evaluation_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n\n When the monitor evaluates next.\n\n- **`recent_alerts`** [`Alert[]`](/api/alerts#model)\n\n The most recent alerts this monitor raised.\n\n- **`series_states`** [`MonitorSeriesState[]`](/api/monitors#series-state-model)\n\n Current state of each series the monitor watches.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### MonitorStatus\n\n`\"active\" | \"paused\" | \"disabled\"`\n\n#### MetricChartValue\n\n`\"count\" | \"average\" | \"sum\" | \"min\" | \"max\" | \"last\" | \"cumulative_sum\" | \"cumulative_count\" | \"p50\" | \"p95\" | \"p99\" | \"count_unique\"`\n\nAggregation applied to a metric's values. Use `last` when records are snapshots of a persistent\nthing such as a deal, user, or inventory item: each time bucket contains the latest reading for\nthat series in the bucket. Within the requested range, the last observed value carries forward\nacross complete empty intervals instead of reading them as zero.\n\n#### MonitorScope\n\n`\"total\" | \"any_series\" | \"each_series\"`\n\n#### MonitorConditionType\n\n`\"threshold\" | \"sustained\" | \"existence\" | \"absence\"`\n\nThreshold compares values; sustained adds duration; existence fires for value > 0; absence\ndetects a complete window with no observations, including chart gaps.\n\n#### MonitorOperator\n\n`\"gt\" | \"gte\" | \"lt\" | \"lte\" | \"eq\" | \"neq\"`\n\n#### MonitorAlertMode\n\n`\"spanning\" | \"instant\"`\n\n## Monitor Preview Model\n\n### Fields\n\n- **`object`** `\"monitor_preview\"`\n\n- **`start_at`** [`ISODateString`](/api/monitors#iso-date-string)\n\n- **`end_at`** [`ISODateString`](/api/monitors#iso-date-string)\n\n- **`complete_through_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n\n- **`ready`** `boolean`\n\n False means the window is not complete yet, so no condition verdict is available.\n\n- **`verdicts`** [`MonitorVerdict[]`](/api/monitors#verdict-model)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Monitor Verdict Model\n\n### Fields\n\n- **`series_hash`** `string`\n\n- **`series_label`** `string | null`\n\n- **`value`** `number | null`\n\n- **`is_met`** `boolean | null`\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Monitor Series State Model\n\n### Fields\n\n- **`monitor_id`** `string`\n\n- **`series_hash`** `string`\n\n Identifies which series of the metric this state tracks.\n\n- **`is_firing`** `boolean`\n\n Whether this series currently meets the monitor's condition.\n\n- **`last_triggered_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n\n When this series last met the condition.\n\n- **`last_value`** `number | null`\n\n The value this series had at the last evaluation.\n\n- **`started_firing_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n\n When the current firing period began; null when the series is not firing.\n\n- **`occurrence_timestamps`** `string[] | null`\n\n When the condition was met, for a monitor that counts occurrences.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Monitors\n\n### Endpoint\n\nList the monitors watching a project's metrics.\n\n```http\nGET /v1/projects/:project_id/monitors\nGET /v1/projects/:project_id/metrics/:metric_id/monitors\n```\n\n**Scope:** `monitors:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string`\n Unique identifier of the metric. Only used by `/v1/projects/:project_id/metrics/:metric_id/monitors`.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Optional. Defaults to `\"name\"`. Allowed values: `\"name\"`, `\"created_at\"`.\n\n- **`metric_id`** `string`\n Return only monitors watching this metric. Optional.\n\n- **`status`** `string`\n Return only monitors in this state. Optional. Allowed values: `\"active\"`, `\"paused\"`, `\"disabled\"`.\n\n- **`limit`** `number`\n Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Optional. Defaults to `\"asc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Monitor[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Call the nested path to scope the results to one metric, or the flat path with an optional `metric_id` filter.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Monitor\n\n### Endpoint\n\nRetrieve a single monitor.\n\n```http\nGET /v1/projects/:project_id/metrics/:metric_id/monitors/:monitor_id\n```\n\n**Scope:** `monitors:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n- **`monitor_id`** `string` -- **Required**\n Unique identifier of the monitor.\n\n### Response\n\nMonitor retrieved\n\n```ts\n{\n message: string;\n data: Monitor;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Includes the monitor's recent alerts and the current state of every series it watches.\n\n## Create Monitor\n\n### Endpoint\n\nCreate a monitor that raises alerts when a metric meets a condition.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/monitors\n```\n\n**Scope:** `monitors:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this monitor. Optional. Minimum length: `2`. Maximum length: `64`.\n\n- **`description`** `string | null`\n What this monitor watches for. Optional.\n\n- **`status`** `string`\n Whether the monitor starts evaluating immediately or switched off. `disabled` is not accepted: the system uses it to record that it stopped the monitor itself. Optional. Defaults to `\"active\"`. Allowed values: `\"active\"`, `\"paused\"`.\n\n- **`chart_value`** [`MetricChartValue`](/api/monitors#metric-chart-value)\n Which value of the metric is compared against the threshold. Optional. Defaults to `\"sum\"`.\n\n- **`scope`** [`MonitorScope`](/api/monitors#monitor-scope)\n Whether the monitor fires once for the metric as a whole, or tracks each series separately. Optional. Defaults to `\"total\"`.\n\n- **`time_window_minutes`** `integer | null`\n Evaluation lookback in whole minutes (1 to 525600). Null means since the metric was created. No-data conditions require a finite window. Optional.\n\n- **`condition_type`** [`MonitorConditionType`](/api/monitors#monitor-condition-type)\n threshold compares a value, sustained requires that comparison to hold, existence fires when the selected value is greater than zero, and absence fires when no observations exist in the complete evaluation window, regardless of chart gap handling. Optional. Defaults to `\"threshold\"`.\n\n- **`operator`** [`MonitorOperator`](/api/monitors#monitor-operator)\n How the metric value is compared against the threshold. Optional. Defaults to `\"gt\"`.\n\n- **`threshold_value`** `number | null`\n The value the metric is compared against. Optional.\n\n- **`sustained_minutes`** `number | null`\n How long the condition must hold before the monitor fires. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression, in UTC, setting how often the monitor evaluates. Optional. Defaults to `\"* * * * *\"`. Minimum length: `1`. Maximum length: `128`.\n\n- **`alert_mode`** [`MonitorAlertMode`](/api/monitors#monitor-alert-mode)\n Whether an alert stays open until the condition clears, or opens and ends in one moment. Optional. Defaults to `\"spanning\"`.\n\n- **`expires_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n When the monitor stops evaluating. Leave null for no expiry. Optional.\n\n- **`series_filters`** `object | null`\n Narrows which of the metric's series this monitor watches. Optional.\n\n- **`min_occurrences`** `integer | null`\n How many times the condition must be met before the monitor fires. Optional.\n\n- **`occurrence_window_minutes`** `integer | null`\n The window those occurrences have to fall within. Optional.\n\n- **`metric_id`** `string`\n Metric to watch. Optional when using the /metrics/:metric/monitors route. Optional.\n\n### Response\n\nYour monitor has been created.\n\n```ts\n{\n message: string;\n data: Monitor;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The monitor starts evaluating on its schedule as soon as it is created, unless it is created with `status` set to `paused`.\n- `scope` decides how many alerts a monitor can raise at once: `any_series` fires once for the metric as a whole, `each_series` tracks every series separately.\n- No-data (`absence`) conditions require `chart_value=count` and a finite `time_window_minutes`.\n- `sustained_minutes` is required when `condition_type` is `sustained`, and `threshold_value` is required when it is `threshold`.\n- `min_occurrences` and `occurrence_window_minutes` must both be set or both be null, and occurrence-based triggering cannot be combined with a sustained condition.\n- `schedule_cron` must be a valid cron expression. It is evaluated in UTC, and the finest granularity is one minute.\n- A monitor cannot be created active with an `expires_at` already in the past.\n\n## Preview Monitor\n\n### Endpoint\n\nPreview a monitor condition over its current evaluation window without saving or raising alerts.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/monitors/preview\n```\n\n**Scope:** `monitors:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this monitor. Optional. Minimum length: `2`. Maximum length: `64`.\n\n- **`description`** `string | null`\n What this monitor watches for. Optional.\n\n- **`status`** `string`\n Whether the monitor starts evaluating immediately or switched off. `disabled` is not accepted: the system uses it to record that it stopped the monitor itself. Optional. Defaults to `\"active\"`. Allowed values: `\"active\"`, `\"paused\"`.\n\n- **`chart_value`** [`MetricChartValue`](/api/monitors#metric-chart-value)\n Which value of the metric is compared against the threshold. Optional. Defaults to `\"sum\"`.\n\n- **`scope`** [`MonitorScope`](/api/monitors#monitor-scope)\n Whether the monitor fires once for the metric as a whole, or tracks each series separately. Optional. Defaults to `\"total\"`.\n\n- **`time_window_minutes`** `integer | null`\n Evaluation lookback in whole minutes (1 to 525600). Null means since the metric was created. No-data conditions require a finite window. Optional.\n\n- **`condition_type`** [`MonitorConditionType`](/api/monitors#monitor-condition-type)\n threshold compares a value, sustained requires that comparison to hold, existence fires when the selected value is greater than zero, and absence fires when no observations exist in the complete evaluation window, regardless of chart gap handling. Optional. Defaults to `\"threshold\"`.\n\n- **`operator`** [`MonitorOperator`](/api/monitors#monitor-operator)\n How the metric value is compared against the threshold. Optional. Defaults to `\"gt\"`.\n\n- **`threshold_value`** `number | null`\n The value the metric is compared against. Optional.\n\n- **`sustained_minutes`** `number | null`\n How long the condition must hold before the monitor fires. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression, in UTC, setting how often the monitor evaluates. Optional. Defaults to `\"* * * * *\"`. Minimum length: `1`. Maximum length: `128`.\n\n- **`alert_mode`** [`MonitorAlertMode`](/api/monitors#monitor-alert-mode)\n Whether an alert stays open until the condition clears, or opens and ends in one moment. Optional. Defaults to `\"spanning\"`.\n\n- **`expires_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n When the monitor stops evaluating. Leave null for no expiry. Optional.\n\n- **`series_filters`** `object | null`\n Narrows which of the metric's series this monitor watches. Optional.\n\n- **`min_occurrences`** `integer | null`\n How many times the condition must be met before the monitor fires. Optional.\n\n- **`occurrence_window_minutes`** `integer | null`\n The window those occurrences have to fall within. Optional.\n\n- **`metric_id`** `string`\n Metric to watch. Optional when using the /metrics/:metric/monitors route. Optional.\n\n### Response\n\nMonitor preview retrieved\n\n```ts\n{\n message: string;\n data: MonitorPreview;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Uses the same series filtering and condition evaluation as scheduled monitors. This is a condition preview, not a simulation of cron timing, sustained duration, or occurrence counting.\n- An incomplete window returns ready=false with no verdicts. No-data conditions count observations across the whole window, including when the chart displays missing buckets as gaps.\n- No-data (`absence`) conditions require `chart_value=count` and a finite `time_window_minutes`.\n- `sustained_minutes` is required when `condition_type` is `sustained`, and `threshold_value` is required when it is `threshold`.\n- `min_occurrences` and `occurrence_window_minutes` must both be set or both be null, and occurrence-based triggering cannot be combined with a sustained condition.\n- `schedule_cron` must be a valid cron expression. It is evaluated in UTC, and the finest granularity is one minute.\n- A monitor cannot be created active with an `expires_at` already in the past.\n\n## Update Monitor\n\n### Endpoint\n\nChange a monitor's condition, schedule, or status.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/monitors/:monitor_id\n```\n\n**Scope:** `monitors:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n- **`monitor_id`** `string` -- **Required**\n Unique identifier of the monitor.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this monitor. Optional. Minimum length: `2`. Maximum length: `64`.\n\n- **`description`** `string | null`\n What this monitor watches for. Optional.\n\n- **`chart_value`** [`MetricChartValue`](/api/monitors#metric-chart-value)\n Which value of the metric is compared against the threshold. Optional.\n\n- **`scope`** [`MonitorScope`](/api/monitors#monitor-scope)\n Whether the monitor fires once for the metric as a whole, or tracks each series separately. Optional.\n\n- **`time_window_minutes`** `integer | null`\n Evaluation lookback in whole minutes (1 to 525600). Null means since the metric was created. No-data conditions require a finite window. Optional.\n\n- **`condition_type`** [`MonitorConditionType`](/api/monitors#monitor-condition-type)\n threshold compares a value, sustained requires that comparison to hold, existence fires when the selected value is greater than zero, and absence fires when no observations exist in the complete evaluation window, regardless of chart gap handling. Optional.\n\n- **`operator`** [`MonitorOperator`](/api/monitors#monitor-operator)\n How the metric value is compared against the threshold. Optional.\n\n- **`threshold_value`** `number | null`\n The value the metric is compared against. Optional.\n\n- **`sustained_minutes`** `number | null`\n How long the condition must hold before the monitor fires. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression, in UTC, setting how often the monitor evaluates. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`alert_mode`** [`MonitorAlertMode`](/api/monitors#monitor-alert-mode)\n Whether an alert stays open until the condition clears, or opens and ends in one moment. Optional.\n\n- **`expires_at`** [`ISODateString | null`](/api/monitors#iso-date-string)\n When the monitor stops evaluating. Leave null for no expiry. Optional.\n\n- **`series_filters`** `object | null`\n Narrows which of the metric's series this monitor watches. Optional.\n\n- **`min_occurrences`** `integer | null`\n How many times the condition must be met before the monitor fires. Optional.\n\n- **`occurrence_window_minutes`** `integer | null`\n The window those occurrences have to fall within. Optional.\n\n### Response\n\nYour monitor has been updated.\n\n```ts\n{\n message: string;\n data: Monitor;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Send only the fields you are changing. Anything omitted keeps its current value.\n- Changing what the monitor MEANS (its condition, scope, series filters or alert mode) ends any period currently firing, because the old alert no longer describes the new rule.\n- `metric_id` cannot be changed. Create a new monitor against the other metric instead.\n- The metric a monitor watches cannot be changed. Create a second monitor instead.\n- No-data (`absence`) conditions require `chart_value=count` and a finite `time_window_minutes`.\n- `sustained_minutes` is required when `condition_type` is `sustained`, and `threshold_value` is required when it is `threshold`.\n- `min_occurrences` and `occurrence_window_minutes` must both be set or both be null, and occurrence-based triggering cannot be combined with a sustained condition.\n- `schedule_cron` must be a valid cron expression. It is evaluated in UTC, and the finest granularity is one minute.\n- `status` is not editable here. Use the pause and resume operations, which is also what keeps a customer from writing the `disabled` the system uses for expiry.\n- Moving `expires_at` is rejected when that would leave an active monitor with an expiry already in the past. Editing a monitor that has already expired is allowed, including the update that extends its expiry.\n- Conditions are checked against the monitor as it will be stored, not against the fields you send, so a change that would leave the monitor in an invalid combination is rejected even when the conflicting value is one you did not send.\n\n## Pause Monitor\n\n### Endpoint\n\nPause a monitor so it stops evaluating.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/monitors/:monitor_id/pause\n```\n\n**Scope:** `monitors:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n- **`monitor_id`** `string` -- **Required**\n Unique identifier of the monitor.\n\n### Response\n\nMonitor paused\n\n```ts\n{\n message: string;\n data: Monitor;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A paused monitor raises no alerts and closes nothing that is already firing. Resuming evaluates it again on its next scheduled tick.\n\n## Resume Monitor\n\n### Endpoint\n\nResume a paused monitor so it evaluates on its schedule again.\n\n```http\nPOST /v1/projects/:project_id/metrics/:metric_id/monitors/:monitor_id/resume\n```\n\n**Scope:** `monitors:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n- **`monitor_id`** `string` -- **Required**\n Unique identifier of the monitor.\n\n### Response\n\nMonitor resumed\n\n```ts\n{\n message: string;\n data: Monitor;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A monitor the system stopped because its expiry passed can be resumed once the expiry is moved into the future. Resuming one whose expiry is still in the past is rejected.\n\n## Delete Monitor\n\n### Endpoint\n\nDelete a monitor.\n\n```http\nDELETE /v1/projects/:project_id/metrics/:metric_id/monitors/:monitor_id\n```\n\n**Scope:** `monitors:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n- **`monitor_id`** `string` -- **Required**\n Unique identifier of the monitor.\n\n### Response\n\nYour monitor has been deleted.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Alerts the monitor already raised are deleted with it.\n\n",
|
|
26
26
|
"api/alerts": "# Alerts\n\n## Alert Model\n\n### Fields\n\n- **`object`** `\"alert\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `alr_`.\n\n- **`slug`** `string`\n\n URL-safe identifier for the alert.\n\n- **`team_id`** `string`\n\n- **`project_id`** `string`\n\n- **`metric_id`** `string`\n\n The metric the monitor was watching.\n\n- **`metric_name`** `string | null`\n\n- **`monitor_id`** `string`\n\n The monitor that raised the alert.\n\n- **`monitor_name`** `string | null`\n\n- **`created_at`** [`ISODateString`](/api/alerts#iso-date-string)\n\n When the condition first held. The start of the firing period.\n\n- **`ended_at`** [`ISODateString | null`](/api/alerts#iso-date-string)\n\n When the condition stopped holding. Null while the alert is still firing.\n\n- **`series_hash`** `string`\n\n Identifies which series of the metric fired, when the monitor watches series separately.\n\n- **`series_label`** `string | null`\n\n- **`trigger_value`** `number`\n\n The metric value at the moment the alert opened.\n\n- **`threshold_value`** `number`\n\n The value the metric had to cross for the monitor to fire.\n\n- **`condition_description`** `string`\n\n The condition in words, for example `above 500 for 10 minutes`.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Alerts\n\n### Endpoint\n\nList the alerts a metric's monitors have raised.\n\n```http\nGET /v1/projects/:project_id/metrics/:metric_id/alerts\n```\n\n**Scope:** `alerts:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`metric_id`** `string` -- **Required**\n Unique identifier of the metric.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are sorted by. Alerts sort by when their period opened. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`.\n\n- **`metric_id`** `string`\n Return only alerts raised on this metric. Optional.\n\n- **`monitor_id`** `string`\n Return only alerts raised by this monitor. Optional.\n\n- **`is_open`** `string`\n Set true for periods still firing, false for periods that have ended. Optional. Allowed values: `\"true\"`, `\"false\"`, `\"1\"`, `\"0\"`.\n\n- **`time_range`** [`AnalyticsTimeRange`](/api/metrics#analytics-time-range)\n Window to search, relative to now. Ignored when start_at and end_at are sent. Optional.\n\n- **`timezone`** `string`\n IANA timezone the time range is anchored to. Defaults to UTC. Optional.\n\n- **`page_id`** `string`\n Resolve the time range from this page's setting instead of the metric's. Optional.\n\n- **`start_at`** [`ISODateString`](/api/alerts#iso-date-string)\n Start of the window to search, as an ISO 8601 value. Optional.\n\n- **`end_at`** [`ISODateString`](/api/alerts#iso-date-string)\n End of the window to search, as an ISO 8601 value. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Alert[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- An alert is a firing PERIOD, not a single notification. `created_at` is when the condition started holding and `ended_at` is when it stopped, so one incident is one row for its whole duration.\n- Results cover a time window. Send `start_at` and `end_at`, or a `time_range`; with neither, the metric's own configured chart range is used.\n- An alert is returned when its period OVERLAPS the window, so a period that opened before the window and is still firing is included.\n- Use `is_open=true` to see only what is currently firing.\n- `before` and `after` are mutually exclusive. Send one or neither.\n- `timezone` must be a valid IANA timezone name, or `UTC`.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Alert\n\n### Endpoint\n\nRetrieve a single alert.\n\n```http\nGET /v1/projects/:project_id/alerts/:alert_id\n```\n\n**Scope:** `alerts:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`alert_id`** `string` -- **Required**\n Unique identifier of the alert.\n\n### Response\n\nAlert retrieved\n\n```ts\n{\n message: string;\n data: Alert;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A null `ended_at` means the condition still holds and the period is still open.\n\n",
|
|
@@ -32,13 +32,13 @@ export const DOCS_PAGES = {
|
|
|
32
32
|
"api/keys": "# API Keys\n\n## API Key Model\n\n### Fields\n\n- **`object`** `\"key\"`\n\n- **`id`** `string`\n\n- **`last4`** `string`\n\n The last four characters of the key, for telling keys apart. The full secret is only returned\n at creation.\n\n- **`created_at`** [`ISODateString`](/api/keys#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/keys#iso-date-string)\n\n- **`scopes`** [`Scopes`](/api/keys#scopes)\n\n What the key may do: each resource mapped to its allowed actions (`read`, `write`, `delete`).\n\n- **`team_id`** `string`\n\n- **`name`** `string`\n\n- **`updated_by`** `string | null`\n\n Who last changed the key, including at creation.\n\n- **`last_used_at`** [`ISODateString | null`](/api/keys#iso-date-string)\n\n When the key last authenticated a request, accurate to five minutes. Null until it is used. A\n key created before Tailglow began recording key use shows null until its next request.\n\n### Referenced Types\n\n#### Scopes\n\n`Record<string, (\"read\" | \"write\" | \"delete\")[]>`\n\nScope names mapped to their permitted actions. Allowed scope keys: `\"users\"`, `\"roles\"`, `\"keys\"`, `\"records\"`, `\"projects\"`, `\"metrics\"`, `\"views\"`, `\"billing\"`, `\"pages\"`, `\"monitors\"`, `\"alerts\"`, `\"chats\"`, `\"secrets\"`, `\"servers\"`, `\"ingest_keys\"`, `\"sources\"`, `\"domains\"`, `\"facets\"`, `\"drains\"`, `\"pulls\"`, `\"checks\"`, `\"logs\"`. Allowed action values: `\"read\"`, `\"write\"`, `\"delete\"`.\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### Scope\n\n`\"users\" | \"roles\" | \"keys\" | \"records\" | \"projects\" | \"metrics\" | \"views\" | \"billing\" | \"pages\" | \"monitors\" | \"alerts\" | \"chats\" | \"secrets\" | \"servers\" | \"ingest_keys\" | \"sources\" | \"domains\" | \"facets\" | \"drains\" | \"pulls\" | \"checks\" | \"logs\"`\n\n#### ScopeValue\n\n`\"read\" | \"write\" | \"delete\"`\n\n## List API Keys\n\n### Endpoint\n\nRetrieve a list of API keys for the current team.\n\n```http\nGET /v1/keys\n```\n\n**Scope:** `keys:read`\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the API keys. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"name\"`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Key[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve API Key\n\n### Endpoint\n\nRetrieve a single API key.\n\n```http\nGET /v1/keys/:key_id\n```\n\n**Scope:** `keys:read`\n\n### Path Parameters\n\n- **`key_id`** `string` -- **Required**\n Unique identifier of the key.\n\n### Response\n\nKey retrieved\n\n```ts\n{\n message: string;\n data: Key;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create API Key\n\n### Endpoint\n\nCreate a new API key for the current team.\n\n```http\nPOST /v1/keys\n```\n\n**Scope:** `keys:write`\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Display name for the API key. Minimum length: `2`. Maximum length: `60`.\n\n- **`scopes`** [`Scopes`](/api/keys#scopes)\n Scope names mapped to their permitted actions. Optional. Allowed scope keys: `\"users\"`, `\"roles\"`, `\"keys\"`, `\"records\"`, `\"projects\"`, `\"metrics\"`, `\"views\"`, `\"billing\"`, `\"pages\"`, `\"monitors\"`, `\"alerts\"`, `\"chats\"`, `\"secrets\"`, `\"servers\"`, `\"ingest_keys\"`, `\"sources\"`, `\"domains\"`, `\"facets\"`, `\"drains\"`, `\"pulls\"`, `\"checks\"`, `\"logs\"`. Allowed action values: `\"read\"`, `\"write\"`, `\"delete\"`.\n\n### Response\n\nYou'll only be able to see your API key one time.\n\n```ts\n{\n message: string;\n data: Key & { api_key: string };\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`api_key`** `string` -- **Required**\n The full API key. Returned once, when the key is created, and never again. Store it at that point; afterwards only `last4` is available.\n\n### Comments\n\n- You can assign only scopes that your own authorization has.\n\n## Update API Key\n\n### Endpoint\n\nUpdate an existing API key.\n\n```http\nPOST /v1/keys/:key_id\n```\n\n**Scope:** `keys:write`\n\n### Path Parameters\n\n- **`key_id`** `string` -- **Required**\n Unique identifier of the key.\n\n### Request Body\n\n- **`name`** `string`\n Display name for the API key. Optional.\n\n- **`scopes`** [`Scopes`](/api/keys#scopes)\n Scope names mapped to their permitted actions. Optional. Allowed scope keys: `\"users\"`, `\"roles\"`, `\"keys\"`, `\"records\"`, `\"projects\"`, `\"metrics\"`, `\"views\"`, `\"billing\"`, `\"pages\"`, `\"monitors\"`, `\"alerts\"`, `\"chats\"`, `\"secrets\"`, `\"servers\"`, `\"ingest_keys\"`, `\"sources\"`, `\"domains\"`, `\"facets\"`, `\"drains\"`, `\"pulls\"`, `\"checks\"`, `\"logs\"`. Allowed action values: `\"read\"`, `\"write\"`, `\"delete\"`.\n\n### Response\n\nYour API key has been updated.\n\n```ts\n{\n message: string;\n data: Key;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- You can assign only scopes that your own authorization has.\n\n## Delete API Key\n\n### Endpoint\n\nDelete an API key from the current team.\n\n```http\nDELETE /v1/keys/:key_id\n```\n\n### Path Parameters\n\n- **`key_id`** `string` -- **Required**\n Unique identifier of the key.\n\n### Response\n\nYour API key has been deleted.\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Deleting a key needs `keys:delete`, except that a key can always delete itself. `tglow logout` uses this to remove the key it signed in with.\n\n",
|
|
33
33
|
"api/domains": "# Domains\n\n## Domain Model\n\n### Fields\n\n- **`object`** `\"domain\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `dom_`.\n\n- **`team_id`** `string`\n\n- **`domain`** `string`\n\n The hostname the customer added, stored lowercase.\n\n- **`ownership_type`** [`DnsRecordType`](/api/domains#dns-record-type)\n\n The record proving the customer controls this hostname. Always a TXT at\n `_tailglow-verify.{domain}`, required even for a domain with no purpose, which needs nothing\n else. Verification is per-hostname: it proves nothing about the parent domain or a sibling.\n\n- **`ownership_name`** `string`\n\n- **`ownership_value`** `string`\n\n- **`ownership_status`** [`DnsRecordStatus`](/api/domains#dns-record-status)\n\n What the last lookup found. `unchecked` means no lookup has run yet, which is different from\n `missing` (looked up, definitively not there).\n\n- **`ownership_actual`** `string | null`\n\n What DNS actually returned. Null when the record is absent or has not been looked up.\n\n- **`ownership_checked_at`** [`ISODateString | null`](/api/domains#iso-date-string)\n\n- **`ownership_verified`** `boolean`\n\n Whether Tailglow currently treats the ownership record as good. True while it resolves, and it\n stays true for 24 hours after it stops so a brief DNS problem does not unverify a working\n domain.\n\n- **`ownership_failed_at`** [`ISODateString | null`](/api/domains#iso-date-string)\n\n When the ownership record had been missing long enough that Tailglow stops serving the domain.\n Null while ownership holds, including during the grace period that follows a failed check.\n\n- **`routing_type`** [`DnsRecordType | null`](/api/domains#dns-record-type)\n\n The record routing traffic to Tailglow, or null while the domain has no purpose and so asks for\n no record. Always a CNAME at the hostname itself.\n\n- **`routing_name`** `string | null`\n\n- **`routing_value`** `string | null`\n\n- **`routing_status`** [`DnsRecordStatus`](/api/domains#dns-record-status)\n\n What the last lookup found. A routing record whose target has since moved reads as `unchecked`:\n the stored answer described the old target, so it says nothing about the current one.\n\n- **`routing_actual`** `string | null`\n\n- **`routing_checked_at`** [`ISODateString | null`](/api/domains#iso-date-string)\n\n- **`routing_verified`** `boolean`\n\n Whether Tailglow currently treats the routing record as good. True while it resolves, and it\n stays true for seven days after it stops so a DNS change in progress does not take a live\n domain out of service.\n\n- **`certificate_status`** [`CertificateStatus`](/api/domains#certificate-status)\n\n Progress of the TLS certificate Tailglow provisions once routing is verified.\n\n- **`certificate_error`** `string | null`\n\n Why the last certificate attempt failed; null unless `certificate_status` is `failed`.\n\n- **`certificate_provisioned_at`** [`ISODateString | null`](/api/domains#iso-date-string)\n\n When the current certificate was issued.\n\n- **`purpose`** [`DomainTrafficPurpose | null`](/api/domains#domain-traffic-purpose)\n\n What this domain serves, or null when it serves nothing yet. A domain serves ingest traffic or\n status pages, never both.\n\n- **`is_sso_enabled`** `boolean`\n\n Whether members with an email at this domain sign in through SSO. Adds no DNS record.\n\n- **`lifecycle_action`** [`DomainLifecycleAction | null`](/api/domains#domain-lifecycle-action)\n\n Infrastructure work Tailglow is currently doing for this domain; null when idle.\n\n- **`lifecycle_status`** [`DomainLifecycleStatus | null`](/api/domains#domain-lifecycle-status)\n\n How that work is progressing.\n\n- **`lifecycle_error`** `string | null`\n\n Why the last attempt failed. Tailglow retries automatically.\n\n- **`last_checked_at`** [`ISODateString | null`](/api/domains#iso-date-string)\n\n When this domain was last checked. A domain can be checked once every 30 seconds; per-record\n timing lives on `ownership_checked_at` and `routing_checked_at`.\n\n- **`created_at`** [`ISODateString`](/api/domains#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/domains#iso-date-string)\n\n- **`updated_by`** `string | null`\n\n The user who last changed this domain.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### DnsRecordType\n\n`\"txt\" | \"cname\"`\n\n#### DnsRecordStatus\n\n`\"unchecked\" | \"valid\" | \"invalid\" | \"missing\" | \"unavailable\"`\n\n#### CertificateStatus\n\n`\"none\" | \"provisioning\" | \"active\" | \"failed\"`\n\n#### DomainTrafficPurpose\n\n`\"ingest\" | \"pages\"`\n\nWhat a domain serves. Stored on the domain.\n\n#### DomainLifecycleAction\n\n`\"attach\" | \"reconcile\" | \"disable_ingest\" | \"disable_pages\" | \"delete_domain\" | \"delete_team\"`\n\n#### DomainLifecycleStatus\n\n`\"pending\" | \"processing\" | \"failed\"`\n\n## List Domains\n\n### Endpoint\n\nRetrieve a list of custom domains for the current team.\n\n```http\nGET /v1/domains\n```\n\n**Scope:** `domains:read`\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the domains. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"domain\"`, `\"certificate_status\"`.\n\n- **`ownership_verified`** `string`\n Filter domains by ownership verification state. Optional. Allowed values: `\"true\"`, `\"false\"`.\n\n- **`certificate_status`** [`CertificateStatus`](/api/domains#certificate-status)\n Filter domains by the status of their TLS certificate. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Domain[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `ownership_verified` accepts the strings `true` and `false`. Any other value is rejected.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Domain\n\n### Endpoint\n\nRetrieve a single custom domain, including the DNS records needed to verify and route it.\n\n```http\nGET /v1/domains/:domain_id\n```\n\n**Scope:** `domains:read`\n\n### Path Parameters\n\n- **`domain_id`** `string` -- **Required**\n Unique identifier of the domain.\n\n### Response\n\nDomain retrieved\n\n```ts\n{\n message: string;\n data: Domain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Domain\n\n### Endpoint\n\nAdd a custom domain to the current team.\n\n```http\nPOST /v1/domains\n```\n\n**Scope:** `domains:write`\n\n### Request Body\n\n- **`domain`** `string` -- **Required**\n The custom domain to add, for example `analytics.example.com`. Stored in lowercase. Minimum length: `1`. Maximum length: `255`.\n\n- **`purpose`** [`DomainTrafficPurpose`](/api/domains#domain-traffic-purpose)\n Traffic purpose to configure for this domain. Use `ingest` to receive analytics data, or `pages` to serve status pages. Leave it out to add the domain without a purpose and choose one later. Optional.\n\n### Response\n\n```ts\n{\n message: string;\n data: Domain;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The number of custom domains a team can add is limited by its billing plan.\n- A hostname can only be registered once. Adding one that already exists returns a conflict.\n- Some hostnames are reserved and cannot be added.\n- `domain` cannot contain a wildcard (`*`). Add each hostname you want to serve as its own domain.\n- `domain` must be a valid hostname such as `analytics.example.com`. Labels may contain letters, digits and hyphens, cannot start or end with a hyphen, and the top level domain must be at least two letters.\n\n## Check Domain DNS\n\n### Endpoint\n\nLook up every DNS record this domain needs and report what is currently there.\n\n```http\nPOST /v1/domains/:domain_id/check\n```\n\n**Scope:** `domains:write`\n\n### Path Parameters\n\n- **`domain_id`** `string` -- **Required**\n Unique identifier of the domain.\n\n### Response\n\n```ts\n{\n message: string;\n data: Domain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A check that completes always returns 200. A record that does not resolve is a result, not an error, so read the outcome from `ownership_status` and `routing_status` rather than the status code.\n- Ownership is checked first. Routing is only checked once ownership has been verified and the domain has a traffic purpose enabled.\n- A record that has never been looked up reports `unchecked`, which is not the same as `missing`.\n- A domain can be checked at most once every 30 seconds.\n\n## Update Domain\n\n### Endpoint\n\nUpdate a domain's traffic purpose or SSO assignment.\n\n```http\nPOST /v1/domains/:domain_id\n```\n\n**Scope:** `domains:write`\n\n### Path Parameters\n\n- **`domain_id`** `string` -- **Required**\n Unique identifier of the domain.\n\n### Request Body\n\n- **`purpose`** [`DomainTrafficPurpose`](/api/domains#domain-traffic-purpose)\n What this domain serves. Ownership must be verified before a purpose can be set, and `null` takes the domain out of service. Omit to leave it unchanged. Optional.\n\n- **`is_sso_enabled`** `boolean`\n Whether SSO is assigned to this domain. Optional.\n\n### Response\n\n```ts\n{\n message: string;\n data: Domain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Domain ownership must be verified before a purpose can be set.\n- A domain serves either ingest traffic or pages. Send `purpose: null` to take it out of service before setting the other, rather than switching between them directly.\n- Setting `purpose` to null queues infrastructure teardown. The domain keeps reporting its old purpose until that work completes.\n- Only one lifecycle operation runs at a time. If another is in progress, retry shortly.\n\n## Delete Domain\n\n### Endpoint\n\nDelete a custom domain from the current team.\n\n```http\nDELETE /v1/domains/:domain_id\n```\n\n**Scope:** `domains:delete`\n\n### Path Parameters\n\n- **`domain_id`** `string` -- **Required**\n Unique identifier of the domain.\n\n### Response\n\n```ts\n{\n message: string;\n data: null;\n status: 202;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Deletion runs in the background. The domain remains listed with a pending lifecycle state until its edge configuration has been removed.\n\n",
|
|
34
34
|
"api/servers": "# Servers\n\n## Server Model\n\n### Fields\n\n- **`object`** `\"server\"`\n\n- **`id`** `string`\n\n- **`project_id`** `string`\n\n- **`project_name`** `string`\n\n Display name of that project.\n\n- **`team_id`** `string`\n\n- **`name`** `string | null`\n\n Generated friendly name, for example `swift-hawk`.\n\n- **`sku`** `string`\n\n The server's size, as a named bundle of CPU and memory, for example `v1-1cpu-2gb`.\n\n- **`status`** [`ServerStatus`](/api/servers#server-status)\n\n Lifecycle state: `provisioning` until the server first comes up, then `active`. A paused\n free-trial server is `draining` while it finishes the data it accepted, then `idle`. A server\n removed by scaling down is `terminating` until it has finished that data and been deleted.\n\n- **`provisioning_phase`** [`ServerProvisioningPhase | null`](/api/servers#server-provisioning-phase)\n\n Why provisioning is still in flight (e.g. \"awaiting_capacity\"); null once active.\n\n- **`ordinal`** `number`\n\n The server's stable position in the project's fleet, starting at 0.\n\n- **`created_at`** [`ISODateString`](/api/servers#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/servers#iso-date-string)\n\n- **`heartbeat_at`** [`ISODateString | null`](/api/servers#iso-date-string)\n\n When the server last reported its vitals. Servers report every 30 seconds; null means it has\n never reported.\n\n- **`cpu_percent`** `number`\n\n CPU usage the server last reported, from 0 to 100. Zero until the first report.\n\n- **`memory_percent`** `number`\n\n Memory usage the server last reported, from 0 to 100. Zero until the first report.\n\n- **`spool_percent`** `number | null`\n\n Actual PVC fullness 0-100, derived as max(bytes, inodes), or null before the first heartbeat.\n\n- **`spool_bytes_percent`** `number | null`\n\n Actual PVC byte fullness 0-100, or null before the first heartbeat.\n\n- **`spool_inodes_percent`** `number | null`\n\n Actual PVC inode (file-count) fullness 0-100, or null before the first heartbeat.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### ServerStatus\n\n`\"provisioning\" | \"active\" | \"upgrading\" | \"draining\" | \"idle\" | \"terminating\"`\n\n#### ServerProvisioningPhase\n\n`\"allocating\" | \"awaiting_capacity\" | \"attaching_storage\" | \"pulling_image\" | \"starting\"`\n\n## Server Event Model\n\n### Fields\n\n- **`object`** `\"server_rollout_event\"`\n\n- **`id`** `string`\n\n- **`project_id`** `string`\n\n- **`started_at`** [`ISODateString`](/api/servers#iso-date-string)\n\n When the deploy of the project's servers began.\n\n- **`completed_at`** [`ISODateString | null`](/api/servers#iso-date-string)\n\n When the deploy finished; null while it is still in progress.\n\n- **`image_tag`** `string | null`\n\n Version tag the deploy moves the project's servers to.\n\n- **`phase`** `string | null`\n\n The most recent step the deploy reached; null before the first step is recorded.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## List Servers\n\n### Endpoint\n\nRetrieve a list of servers in a project.\n\n```http\nGET /v1/projects/:project_id/servers\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the servers. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`, `\"status\"`.\n\n- **`status`** [`ServerStatus`](/api/servers#server-status)\n Filter by server status. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Server[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A server is a dedicated machine that receives and processes the project's data. Every project runs its own fleet.\n- A server that is still coming up is returned with a status of `provisioning`, and a server on its way out with a status of `terminating`.\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Server\n\n### Endpoint\n\nRetrieve a single server.\n\n```http\nGET /v1/projects/:project_id/servers/:server_id\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`server_id`** `string` -- **Required**\n Unique identifier of the server.\n\n### Response\n\nServer retrieved\n\n```ts\n{\n message: string;\n data: Server;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Scale Servers\n\n### Endpoint\n\nScale a project's fleet to a desired total number of servers.\n\n```http\nPOST /v1/projects/:project_id/servers\n```\n\n**Scope:** `servers:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`count`** `integer` -- **Required**\n Total number of servers the project should run after this request. This is the desired total, not the number of servers to add or remove. The maximum is the team's per-project server limit, which is set by its plan. Minimum: `0`.\n\n### Response 200\n\n```ts\n{\n message: string;\n data: Server[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Response 202\n\n```ts\n{\n message: string;\n data: Server[];\n status: 202;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `count` is the total number of servers the project should end up running, not the number to add. A project already running two servers reaches three by sending `count: 3`.\n- Scaling down is the same call with a lower `count`, and `count: 0` takes the whole fleet down. Tailglow chooses which servers to retire and drains them first, so they stay in the response with a status of `terminating` until they finish.\n- There is no endpoint that deletes an individual server. Removing capacity is always a scale request with a lower `count`.\n- Scaling up requires the team to have a payment method, and `count` must stay within the team's per-project server limit.\n- The response is the project's whole fleet after the request, not only the servers that changed.\n- Requesting the count the project already runs, with nothing in flight, returns a `409`.\n- During a platform update, a scale request is queued and answered with a `202`; it is applied once the update finishes, and the project's `queued_server_count` shows it meanwhile. A project that has no servers yet still gets its first server immediately.\n- Adding servers to a project that has not finished moving to the current platform version is queued the same way, even with no update in progress. Removing servers is not held back by it.\n- Requesting the count the project currently runs while a change is queued cancels the queued change, or returns a `409` if that change has already started. A newer request replaces an older queued one.\n\n## Update Server\n\n### Endpoint\n\nRename a server.\n\n```http\nPOST /v1/projects/:project_id/servers/:server_id\n```\n\n**Scope:** `servers:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`server_id`** `string` -- **Required**\n Unique identifier of the server.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Display name for the server. Minimum length: `1`. Maximum length: `64`.\n\n### Response\n\nServer updated\n\n```ts\n{\n message: string;\n data: Server;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `name` is the only editable field on a server. It is a display label and does not change how data is routed or how much capacity the server has.\n- Renaming never adds or removes servers. Use `POST /v1/projects/:project_id/servers` to change how many servers the project runs.\n\n## Retrieve Server Vitals\n\n### Endpoint\n\nRetrieve CPU, memory, and buffer usage for a server over a time range.\n\n```http\nGET /v1/projects/:project_id/servers/:server_id/vitals\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`server_id`** `string` -- **Required**\n Unique identifier of the server.\n\n### Query Parameters\n\n- **`start_at`** [`ISODateString`](/api/servers#iso-date-string)\n Start time (ISO format, default: 12 hours ago). Optional.\n\n- **`end_at`** [`ISODateString`](/api/servers#iso-date-string)\n End time (ISO format, default: now). Optional.\n\n- **`interval`** `string`\n Aggregation interval. Optional. Defaults to `\"minute\"`. Allowed values: `\"minute\"`, `\"hour\"`.\n\n### Response\n\nVitals retrieved\n\n```ts\n{\n message: string;\n data: TimeseriesAggregation;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The response carries Worker CPU, Worker Memory, Buffer, Bytes, and Inodes series, plus ingress CPU and memory when available, bucketed by `interval`.\n- Older points come from archived data and recent points come from the running server, stitched into one continuous timeline. A server replaced by a platform deploy does not break the series.\n- Without `start_at` and `end_at` the range is the last 12 hours.\n\n## Retrieve Server Pipeline\n\n### Endpoint\n\nRetrieve queue depth, throughput, and error counts for a server over a time range.\n\n```http\nGET /v1/projects/:project_id/servers/:server_id/pipeline\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`server_id`** `string` -- **Required**\n Unique identifier of the server.\n\n### Query Parameters\n\n- **`start_at`** [`ISODateString`](/api/servers#iso-date-string)\n Start time (ISO format, default: 12 hours ago). Optional.\n\n- **`end_at`** [`ISODateString`](/api/servers#iso-date-string)\n End time (ISO format, default: now). Optional.\n\n- **`interval`** `string`\n Aggregation interval. Optional. Defaults to `\"minute\"`. Allowed values: `\"minute\"`, `\"hour\"`.\n\n### Response\n\nPipeline retrieved\n\n```ts\n{\n message: string;\n data: TimeseriesAggregation;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Pending averages valid queued records, Throughput counts drained records, and Errors counts rejected requests and dropped frames. Older pending history without record counts is unknown.\n- Takes the same range and interval parameters as the vitals endpoint, so the two can be read over the same window.\n- Without `start_at` and `end_at` the range is the last 12 hours.\n\n## List Server Events\n\n### Endpoint\n\nRetrieve the platform events recorded for a server's project over a time range.\n\n```http\nGET /v1/projects/:project_id/servers/:server_id/events\n```\n\n**Scope:** `servers:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`server_id`** `string` -- **Required**\n Unique identifier of the server.\n\n### Query Parameters\n\n- **`start_at`** [`ISODateString`](/api/servers#iso-date-string) -- **Required**\n Earliest event date and time to return (ISO format).\n\n- **`end_at`** [`ISODateString`](/api/servers#iso-date-string) -- **Required**\n Latest event date and time to return (ISO format).\n\n- **`type`** `string`\n Filter by event type. Optional. Allowed values: `\"rollout\"`.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: ServerRolloutEvent[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- An event is a Tailglow deploy of the project's servers. Requesting events over the same window as the vitals and pipeline endpoints shows whether a change in those charts lines up with a deploy.\n- `start_at` and `end_at` are both required.\n- An event whose `completed_at` is `null` is still in progress.\n- Events belong to the project, so every server in the project returns the same list.\n- `after` and `before` are mutually exclusive.\n\n",
|
|
35
|
-
"api/drains": "# Drains\n\n## Drain Model\n\n### Fields\n\n- **`object`** `\"drain\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `drn_`.\n\n- **`project_id`** `string`\n\n- **`team_id`** `string`\n\n- **`name`** `string`\n\n- **`collection_id`** `string | null`\n\n The collection whose records are forwarded. Null when the drain reads from a view.\n\n- **`view_id`** `string | null`\n\n The view whose records are forwarded. Null when the drain reads from a collection.\n\n- **`destination_url`** `string`\n\n The HTTPS URL records are delivered to.\n\n- **`headers_configured`** `string[]`\n\n Names of the headers sent with every delivery. The values are stored encrypted and are never\n returned, so this shows what was configured without revealing the secrets.\n\n- **`body_format`** [`DrainBodyFormat`](/api/drains#drain-body-format)\n\n The shape of each delivered request body.\n\n- **`compression`** [`DrainCompression`](/api/drains#drain-compression)\n\n The compression applied to each delivered request body.\n\n- **`backfill_enabled`** `boolean`\n\n Whether records that already existed when the drain first activated were delivered too. Decided\n once at first activation and cannot be changed afterwards.\n\n- **`exclude_fields`** `string[]`\n\n Top-level field names removed from every record before delivery, for destinations that reject\n fields they do not expect. Empty means nothing is removed.\n\n- **`status`** [`DrainStatus`](/api/drains#drain-status)\n\n Where the drain is in its lifecycle, from unverified through delivering or paused.\n\n- **`schedule_cron`** `string`\n\n Cron expression setting how often undelivered records are looked for, evaluated in UTC. A drain\n that is behind keeps delivering without waiting for the next run, so this controls how quickly\n new records are picked up rather than how fast a backlog clears.\n\n- **`next_send_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When the drain is next due to look for records. Null means no time is set rather than nothing\n scheduled: on an active drain it is due immediately, and on a stopped one it is ignored.\n Whether a drain runs at all is `status`.\n\n- **`verified_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When the destination was proven reachable. Null until the drain is verified.\n\n- **`last_success_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When the destination last accepted a delivery. Null if it never has.\n\n- **`last_failure_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When a delivery last failed. Null if none ever has.\n\n- **`consecutive_failure_count`** `number`\n\n How many deliveries have failed in a row. Resets to zero on the next success.\n\n- **`last_error`** `string | null`\n\n What went wrong on the most recent failed delivery. Null if none ever has.\n\n- **`last_batch_size`** `number`\n\n How many records were in the most recent successful delivery.\n\n- **`records_sent_hourly`** `DrainHourlyRecords[]`\n\n The last 24 hours of delivery volume, oldest first. Always 24 entries, so an hour with no\n deliveries reads as a zero rather than being absent, and a drain that has never sent still\n returns a full window of zeros. Sum the counts for \"records sent in the last 24 hours\".\n\n- **`created_at`** [`ISODateString`](/api/drains#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/drains#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### DrainBodyFormat\n\n`\"ndjson\" | \"json\"`\n\n#### DrainCompression\n\n`\"none\" | \"gzip\"`\n\n#### DrainStatus\n\n`\"draft\" | \"pending_verification\" | \"active\" | \"paused\" | \"disabled\"`\n\n## List Drains\n\n### Endpoint\n\nRetrieve a list of drains for a project.\n\n```http\nGET /v1/projects/:project_id/drains\n```\n\n**Scope:** `drains:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are ordered by. Optional. Defaults to `\"created_at\"`. Allowed values: `\"name\"`, `\"created_at\"`.\n\n- **`status`** [`DrainStatus`](/api/drains#drain-status)\n Return only drains in this state. Optional.\n\n- **`collection_id`** `string`\n Return only drains that read from this collection. Optional.\n\n- **`view_id`** `string`\n Return only drains that read from this view. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Drain[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Drain\n\n### Endpoint\n\nRetrieve a single drain.\n\n```http\nGET /v1/projects/:project_id/drains/:drain_id\n```\n\n**Scope:** `drains:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain retrieved\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Drain\n\n### Endpoint\n\nCreate a drain that forwards records from one collection or view to an external destination.\n\n```http\nPOST /v1/projects/:project_id/drains\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name shown for this drain. Minimum length: `2`. Maximum length: `128`.\n\n- **`collection_id`** `string | null`\n Collection whose records are forwarded. Set this or `view_id`, not both. Optional.\n\n- **`view_id`** `string | null`\n View whose records are forwarded. Set this or `collection_id`, not both. Optional.\n\n- **`destination_url`** `string` -- **Required**\n HTTPS URL records are delivered to. Must resolve to a public address. Minimum length: `1`. Maximum length: `2048`.\n\n- **`headers`** `object | null`\n Headers sent with every delivery, for authenticating to the destination. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.\n\n- **`body_format`** [`DrainBodyFormat`](/api/drains#drain-body-format)\n Shape of each delivered request body. Optional.\n\n- **`compression`** [`DrainCompression`](/api/drains#drain-compression)\n Compression applied to each delivered request body. Optional.\n\n- **`backfill_enabled`** `boolean`\n Whether records that already existed when the drain first activates are delivered too. Decided once at first activation and cannot be changed afterwards. Optional.\n\n- **`exclude_fields`** `string[]`\n Top-level field names stripped from every record before delivery, for destinations that reject fields they do not expect. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting how often undelivered records are looked for, evaluated in UTC. Defaults to hourly. A drain that is behind keeps delivering without waiting for the next run, so this sets how quickly new records are picked up, not how fast a backlog clears. Optional. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Drain;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The destination must be reachable over HTTPS at a public address. Private, loopback and link-local addresses are rejected.\n- A new drain starts unverified and sends nothing. Verify it before it delivers records.\n- A destination that is Tailglow ingest or a domain this team has verified is trusted, so it skips verification and is activated on create.\n- A trusted destination that would write back into the same collection the drain reads from is rejected, because it would loop.\n- Exactly one of `collection_id` or `view_id` must be set. A drain reads from one container.\n- `headers` accepts at most 20 entries.\n- `exclude_fields` accepts at most 50 entries, each a top-level key of at most 256 characters. Dots, whitespace, wildcards and newlines are rejected. Entries are trimmed and duplicates removed.\n- `schedule_cron` accepts a five-field expression evaluated in UTC. Second-level precision and hashed fields are rejected; the shortest supported gap is one minute.\n\n## Update Drain\n\n### Endpoint\n\nUpdate a drain's name, payload format, compression, or excluded fields.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this drain. Optional. Minimum length: `2`. Maximum length: `128`.\n\n- **`body_format`** [`DrainBodyFormat`](/api/drains#drain-body-format)\n Shape of each delivered request body. Optional.\n\n- **`compression`** [`DrainCompression`](/api/drains#drain-compression)\n Compression applied to each delivered request body. Optional.\n\n- **`exclude_fields`** `string[]`\n Top-level field names stripped from every record before delivery, for destinations that reject fields they do not expect. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting how often undelivered records are looked for, evaluated in UTC. Defaults to hourly. A drain that is behind keeps delivering without waiting for the next run, so this sets how quickly new records are picked up, not how fast a backlog clears. Optional. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\nDrain updated\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `destination_url`, `headers` and `backfill_enabled` cannot be changed. Changing the URL or headers would invalidate the proof of access established at verification, and backfill is decided once when the drain first activates. Delete the drain and create a new one instead.\n- At least one field must be provided.\n- Any field not listed here is rejected, including `destination_url`, `headers` and `backfill_enabled`, which cannot be changed after create.\n- `exclude_fields` accepts at most 50 entries, each a top-level key of at most 256 characters. Dots, whitespace, wildcards and newlines are rejected. Entries are trimmed and duplicates removed.\n- `schedule_cron` accepts a five-field expression evaluated in UTC. Second-level precision and hashed fields are rejected; the shortest supported gap is one minute.\n\n## Delete Drain\n\n### Endpoint\n\nDelete a drain and stop all delivery to its destination.\n\n```http\nDELETE /v1/projects/:project_id/drains/:drain_id\n```\n\n**Scope:** `drains:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Verify Drain\n\n### Endpoint\n\nSend a verification marker to the drain's destination and return the destination's response.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/verify\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response 200 (1)\n\nDrain auto-verified — destination is infrastructure you control.\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Response 200 (2)\n\nVerification marker sent\n\n```ts\n{\n message: string;\n data: { drain: Drain; response: { status: number; status_text: string; ok: boolean; response_time_ms: number; body_preview: string; } };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`drain`** [`Drain`](/api/drains#model) -- **Required**\n The drain as it stands after the verification attempt.\n\n- **`response`** `{ status: number; status_text: string; ok: boolean; response_time_ms: number; body_preview: string; }` -- **Required**\n What the destination replied with, including a non-2xx status.\n\n### Comments\n\n- Proving control of the destination takes two steps. This one delivers a marker token to the destination; read that token from what the destination received and send it back to `verify_confirm`.\n- The marker token is never included in this response. Reading it from the destination is what proves access, so echoing it here would defeat the check.\n- A destination that has since become trusted infrastructure is activated immediately and returns the drain with no marker sent.\n- The `response` field reports what the destination replied with, including non-2xx statuses, so a misconfigured endpoint can be diagnosed without reading logs.\n\n## Confirm Drain Verification\n\n### Endpoint\n\nConfirm a drain by sending back the marker token the destination received.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/verify_confirm\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Request Body\n\n- **`token`** `string` -- **Required**\n The marker token the destination received from the verify request. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\nDrain verified\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Send the token that arrived at the destination from `verify`. A correct token activates the drain and it begins delivering records.\n\n## Pause Drain\n\n### Endpoint\n\nPause a drain so it stops delivering records without losing its verification.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/pause\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain paused\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A paused drain keeps its position, so resuming continues from where it stopped rather than re-sending or skipping records.\n\n## Resume Drain\n\n### Endpoint\n\nResume a paused drain and continue delivering records from where it stopped.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/resume\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain resumed\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Send Drain Sample\n\n### Endpoint\n\nSend a sample payload to the drain's destination and return the destination's response.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/sample\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\n```ts\n{\n message: string;\n data: { status: number; status_text: string; ok: boolean; response_time_ms: number; body_preview: string };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`status`** `number` -- **Required**\n The HTTP status the destination replied with.\n\n- **`status_text`** `string` -- **Required**\n The status text that came with it.\n\n- **`ok`** `boolean` -- **Required**\n Whether the destination accepted the request.\n\n- **`response_time_ms`** `number` -- **Required**\n How long the destination took to reply, in milliseconds.\n\n- **`body_preview`** `string` -- **Required**\n The start of the destination's response body, for diagnosing a rejection.\n\n### Comments\n\n- Use this to check the destination accepts the payload shape before or after activating the drain. The sample is not part of the record stream and does not move the drain's position.\n- A non-2xx reply is returned rather than raised, so the status and body preview can be read directly from the response.\n\n",
|
|
35
|
+
"api/drains": "# Drains\n\n## Drain Model\n\n### Fields\n\n- **`object`** `\"drain\"`\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `drn_`.\n\n- **`project_id`** `string`\n\n- **`team_id`** `string`\n\n- **`name`** `string`\n\n- **`collection_id`** `string | null`\n\n The collection whose records are forwarded. Null when the drain reads from a view.\n\n- **`view_id`** `string | null`\n\n The view whose records are forwarded. Null when the drain reads from a collection.\n\n- **`destination_url`** `string`\n\n The HTTPS URL records are delivered to.\n\n- **`headers_configured`** `string[]`\n\n Names of the headers sent with every delivery. The values are stored encrypted and are never\n returned, so this shows what was configured without revealing the secrets.\n\n- **`body_format`** [`DrainBodyFormat`](/api/drains#drain-body-format)\n\n The shape of each delivered request body.\n\n- **`compression`** [`DrainCompression`](/api/drains#drain-compression)\n\n The compression applied to each delivered request body.\n\n- **`backfill_enabled`** `boolean`\n\n Whether records that already existed when the drain first activated were delivered too. Decided\n once at first activation and cannot be changed afterwards.\n\n- **`exclude_fields`** `string[]`\n\n Top-level field names removed from every record before delivery, for destinations that reject\n fields they do not expect. Empty means nothing is removed.\n\n- **`status`** [`DrainStatus`](/api/drains#drain-status)\n\n Where the drain is in its lifecycle, from unverified through delivering or paused.\n\n- **`schedule_cron`** `string`\n\n Cron expression setting how often undelivered records are looked for, evaluated in UTC. A drain\n that is behind keeps delivering without waiting for the next run, so this controls how quickly\n new records are picked up rather than how fast a backlog clears.\n\n- **`next_send_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When the drain is next due to look for records. Null means no time is set rather than nothing\n scheduled: on an active drain it is due immediately, and on a stopped one it is ignored.\n Whether a drain runs at all is `status`.\n\n- **`verified_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When the destination was proven reachable. Null until the drain is verified.\n\n- **`last_success_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When the destination last accepted a delivery. Null if it never has.\n\n- **`last_failure_at`** [`ISODateString | null`](/api/drains#iso-date-string)\n\n When a delivery last failed. Null if none ever has.\n\n- **`consecutive_failure_count`** `number`\n\n How many deliveries have failed in a row. Resets to zero on the next success.\n\n- **`last_error`** `string | null`\n\n What went wrong on the most recent failed delivery. Null if none ever has.\n\n- **`last_batch_size`** `number`\n\n How many records were in the most recent successful delivery.\n\n- **`records_sent_hourly`** `DrainHourlyRecords[]`\n\n The last 24 hours of delivery volume, oldest first. Always 24 entries, so an hour with no\n deliveries reads as a zero rather than being absent, and a drain that has never sent still\n returns a full window of zeros. Sum the counts for \"records sent in the last 24 hours\".\n\n- **`created_at`** [`ISODateString`](/api/drains#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/drains#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### DrainBodyFormat\n\n`\"ndjson\" | \"json\"`\n\n#### DrainCompression\n\n`\"none\" | \"gzip\"`\n\n#### DrainStatus\n\n`\"draft\" | \"pending_verification\" | \"active\" | \"paused\" | \"disabled\"`\n\n## List Drains\n\n### Endpoint\n\nRetrieve a list of drains for a project.\n\n```http\nGET /v1/projects/:project_id/drains\n```\n\n**Scope:** `drains:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are ordered by. Optional. Defaults to `\"created_at\"`. Allowed values: `\"name\"`, `\"created_at\"`.\n\n- **`status`** [`DrainStatus`](/api/drains#drain-status)\n Return only drains in this state. Optional.\n\n- **`collection_id`** `string`\n Return only drains that read from this collection. Optional.\n\n- **`view_id`** `string`\n Return only drains that read from this view. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Drain[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Drain\n\n### Endpoint\n\nRetrieve a single drain.\n\n```http\nGET /v1/projects/:project_id/drains/:drain_id\n```\n\n**Scope:** `drains:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain retrieved\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Drain\n\n### Endpoint\n\nCreate a drain that forwards records from one collection or view to an external destination.\n\n```http\nPOST /v1/projects/:project_id/drains\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name shown for this drain. Minimum length: `2`. Maximum length: `128`.\n\n- **`collection_id`** `string | null`\n Collection whose records are forwarded. Set this or `view_id`, not both. Optional.\n\n- **`view_id`** `string | null`\n View whose records are forwarded. Set this or `collection_id`, not both. Optional.\n\n- **`destination_url`** `string` -- **Required**\n HTTPS URL records are delivered to. Must resolve to a public address. Minimum length: `1`. Maximum length: `2048`.\n\n- **`headers`** `object | null`\n Headers sent with every delivery, for authenticating to the destination. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.\n\n- **`body_format`** [`DrainBodyFormat`](/api/drains#drain-body-format)\n Shape of each delivered request body. Optional.\n\n- **`compression`** [`DrainCompression`](/api/drains#drain-compression)\n Compression applied to each delivered request body. Optional.\n\n- **`backfill_enabled`** `boolean`\n Whether records that already existed when the drain first activates are delivered too. Decided once at first activation and cannot be changed afterwards. Optional.\n\n- **`exclude_fields`** `string[]`\n Top-level field names stripped from every record before delivery, for destinations that reject fields they do not expect. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting how often undelivered records are looked for, evaluated in UTC. Defaults to hourly. A drain that is behind keeps delivering without waiting for the next run, so this sets how quickly new records are picked up, not how fast a backlog clears. Optional. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Drain;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The destination must be reachable over HTTPS at a public address. Private, loopback and link-local addresses are rejected.\n- A new drain starts unverified and sends nothing. Verify it before it delivers records.\n- A destination that is Tailglow ingest or a domain this team has verified is trusted, so it skips verification and is activated on create.\n- A trusted destination that would write back into the same collection the drain reads from is rejected, because it would loop.\n- Exactly one of `collection_id` or `view_id` must be set. A drain reads from one container.\n- `headers` accepts at most 20 entries.\n- `exclude_fields` accepts at most 50 entries, each a top-level key of at most 256 characters. Dots, whitespace, wildcards and newlines are rejected. Entries are trimmed and duplicates removed.\n- `schedule_cron` accepts a five-field expression evaluated in UTC. Second-level precision and hashed fields are rejected; the shortest supported gap is one minute.\n\n## Update Drain\n\n### Endpoint\n\nUpdate a drain's name, payload format, compression, or excluded fields.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this drain. Optional. Minimum length: `2`. Maximum length: `128`.\n\n- **`body_format`** [`DrainBodyFormat`](/api/drains#drain-body-format)\n Shape of each delivered request body. Optional.\n\n- **`compression`** [`DrainCompression`](/api/drains#drain-compression)\n Compression applied to each delivered request body. Optional.\n\n- **`exclude_fields`** `string[]`\n Top-level field names stripped from every record before delivery, for destinations that reject fields they do not expect. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting how often undelivered records are looked for, evaluated in UTC. Defaults to hourly. A drain that is behind keeps delivering without waiting for the next run, so this sets how quickly new records are picked up, not how fast a backlog clears. Optional. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\nDrain updated\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `destination_url`, `headers` and `backfill_enabled` cannot be changed. Changing the URL or headers would invalidate the proof of access established at verification, and backfill is decided once when the drain first activates. Delete the drain and create a new one instead.\n- At least one field must be provided.\n- Any field not listed here is rejected, including `destination_url`, `headers` and `backfill_enabled`, which cannot be changed after create.\n- `exclude_fields` accepts at most 50 entries, each a top-level key of at most 256 characters. Dots, whitespace, wildcards and newlines are rejected. Entries are trimmed and duplicates removed.\n- `schedule_cron` accepts a five-field expression evaluated in UTC. Second-level precision and hashed fields are rejected; the shortest supported gap is one minute.\n\n## Delete Drain\n\n### Endpoint\n\nDelete a drain and stop all delivery to its destination.\n\n```http\nDELETE /v1/projects/:project_id/drains/:drain_id\n```\n\n**Scope:** `drains:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Verify Drain\n\n### Endpoint\n\nSend a verification marker to the drain's destination and return the destination's response.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/verify\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response 200 (1)\n\nDrain auto-verified — destination is infrastructure you control.\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Response 200 (2)\n\nVerification marker sent\n\n```ts\n{\n message: string;\n data: { drain: Drain; response: { status: number; status_text: string; ok: boolean; response_time_ms: number; body_preview: string; } };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`drain`** [`Drain`](/api/drains#model) -- **Required**\n The drain as it stands after the verification attempt.\n\n- **`response`** `{ status: number; status_text: string; ok: boolean; response_time_ms: number; body_preview: string; }` -- **Required**\n What the destination replied with, including a non-2xx status.\n\n### Comments\n\n- Proving control of the destination takes two steps. This one delivers a marker token to the destination; read that token from what the destination received and send it back to `verify_confirm`.\n- The marker token is never included in this response. Reading it from the destination is what proves access, so echoing it here would defeat the check.\n- A destination that has since become trusted infrastructure is activated immediately and returns the drain with no marker sent.\n- The `response` field reports what the destination replied with, including non-2xx statuses, so a misconfigured endpoint can be diagnosed without reading logs.\n\n## Confirm Drain Verification\n\n### Endpoint\n\nConfirm a drain by sending back the marker token the destination received.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/verify_confirm\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Request Body\n\n- **`token`** `string` -- **Required**\n The marker token the destination received from the verify request. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\nDrain verified\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Send the token that arrived at the destination from `verify`. A correct token activates the drain and it begins delivering records.\n\n## Pause Drain\n\n### Endpoint\n\nPause a drain so it stops delivering records without losing its verification.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/pause\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain paused\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- A paused drain keeps its position, so resuming continues from where it stopped rather than re-sending or skipping records.\n\n## Resume Drain\n\n### Endpoint\n\nResume a paused drain and continue delivering records from where it stopped.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/resume\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\nDrain resumed\n\n```ts\n{\n message: string;\n data: Drain;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Send Drain Sample\n\n### Endpoint\n\nSend a sample payload to the drain's destination and return the destination's response.\n\n```http\nPOST /v1/projects/:project_id/drains/:drain_id/sample\n```\n\n**Scope:** `drains:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`drain_id`** `string` -- **Required**\n Unique identifier of the drain.\n\n### Response\n\n```ts\n{\n message: string;\n data: { status: number; status_text: string; ok: boolean; response_time_ms: number; body_preview: string };\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n#### Additional Response Fields\n\n- **`status`** `number` -- **Required**\n The HTTP status the destination replied with.\n\n- **`status_text`** `string` -- **Required**\n The status text that came with it.\n\n- **`ok`** `boolean` -- **Required**\n Whether the destination accepted the request.\n\n- **`response_time_ms`** `number` -- **Required**\n How long the destination took to reply, in milliseconds.\n\n- **`body_preview`** `string` -- **Required**\n The start of the destination's response body, for diagnosing a rejection.\n\n### Comments\n\n- Use this to check the destination accepts the payload shape before or after activating the drain. The sample is not part of the record stream and does not move the drain's position.\n- A non-2xx reply is returned rather than raised, so the status and body preview can be read directly from the response.\n- The sample is built from the drain's newest records, so while the project's server is stopped this answers 409 `server_stopped`. Start the server and try again.\n\n",
|
|
36
36
|
"api/pulls": "# Pulls\n\n## Pull Model\n\n### Fields\n\n- **`object`** `\"pull\"`\n\n Always `\"pull\"`.\n\n- **`id`** `string`\n\n Unique identifier, prefixed with `pul_`.\n\n- **`project_id`** `string`\n\n The project this pull belongs to.\n\n- **`team_id`** `string`\n\n The team that owns the pull.\n\n- **`name`** `string`\n\n Display name for the pull.\n\n- **`source_id`** `string`\n\n The source fetched records are written to.\n\n- **`source_name`** `string | null`\n\n Display name of that source.\n\n- **`collection_slug`** `string | null`\n\n The collection within the source that records land in. Defaults to a slug of the pull name.\n\n- **`endpoint_url`** `string`\n\n The HTTPS URL that is fetched on each run.\n\n- **`method`** [`PullMethod`](/api/pulls#pull-method)\n\n The HTTP method used to fetch the endpoint.\n\n- **`headers_configured`** `string[]`\n\n Names of the headers sent with every fetch. The values are stored encrypted and are never\n returned, so this shows what was configured without revealing the secrets.\n\n- **`request_body`** `string | null`\n\n The request body sent when the method is `post`. Null for `get`.\n\n- **`json_records_path`** `string | null`\n\n Dot path to the array of records inside a JSON response, such as `data` or `data.result`. Null\n when the response is stored exactly as it arrives.\n\n- **`schedule_cron`** `string`\n\n Cron expression setting when the endpoint is fetched, evaluated in UTC.\n\n- **`status`** [`PullStatus`](/api/pulls#pull-status)\n\n The current state of the pull.\n\n- **`next_fetch_at`** [`ISODateString | null`](/api/pulls#iso-date-string)\n\n When the next fetch is due. Null when the pull is paused or disabled, because nothing is\n scheduled: a time here would promise a fetch that will not happen.\n\n- **`missed_slot_count`** `number`\n\n How many scheduled slots have elapsed without a fetch, since the pull was created or last\n resumed. Almost always because the project had no healthy server at the time.\n \n Missed slots are counted rather than fetched late. A pull records whatever its endpoint serves\n at the moment of the request, so a delayed fetch would return current data stamped with a time\n it does not describe. A rising count next to a healthy `last_success_at` means the schedule is\n finer than the project's capacity has been able to keep up with.\n\n- **`last_attempt_at`** [`ISODateString | null`](/api/pulls#iso-date-string)\n\n When the endpoint was last fetched, whether or not it succeeded.\n\n- **`last_success_at`** [`ISODateString | null`](/api/pulls#iso-date-string)\n\n When records were last fetched and accepted.\n\n- **`last_failure_at`** [`ISODateString | null`](/api/pulls#iso-date-string)\n\n When a fetch last failed.\n\n- **`consecutive_failure_count`** `number`\n\n How many fetches have failed in a row. Resets to zero on the next success.\n\n- **`last_error`** `string | null`\n\n The error from the most recent failed fetch.\n\n- **`last_error_stage`** [`PullErrorStage | null`](/api/pulls#pull-error-stage)\n\n Which step of the most recent failed fetch went wrong.\n\n- **`last_http_status`** `number | null`\n\n The HTTP status the endpoint returned on the most recent fetch.\n\n- **`last_response_time_ms`** `number | null`\n\n How long the most recent fetch took, in milliseconds.\n\n- **`last_record_count`** `number`\n\n How many records the most recent successful fetch produced.\n\n- **`records_pulled_hourly`** `PullHourlyRecords[]`\n\n The last 24 hours of fetch volume, oldest first. Always 24 entries, so an hour with no fetches\n reads as a zero rather than being absent, and a pull that has never run still returns a full\n window of zeros. Sum the counts for \"records pulled in the last 24 hours\".\n\n- **`created_at`** [`ISODateString`](/api/pulls#iso-date-string)\n\n When the pull was created.\n\n- **`updated_at`** [`ISODateString`](/api/pulls#iso-date-string)\n\n When the pull was last changed.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### PullMethod\n\n`\"get\" | \"post\"`\n\n#### PullStatus\n\n`\"active\" | \"paused\" | \"disabled\"`\n\n#### PullErrorStage\n\n`\"request\" | \"response\" | \"parse\" | \"ingest\"`\n\n## Pull Test Result\n\n### Fields\n\n- **`ok`** `boolean`\n\n Whether the endpoint responded successfully and the response could be read as records.\n\n- **`status`** `number`\n\n The HTTP status the endpoint returned. Zero when no response was received at all.\n\n- **`status_text`** `string`\n\n The HTTP status text the endpoint returned.\n\n- **`response_time_ms`** `number`\n\n How long the fetch took, in milliseconds.\n\n- **`byte_length`** `number`\n\n How many bytes the endpoint returned.\n\n- **`record_count`** `number`\n\n How many records the response would produce. Zero when the test failed.\n\n- **`body_preview`** `string`\n\n The first 2 KB of the response body.\n\n- **`error_stage`** [`PullErrorStage | null`](/api/pulls#pull-error-stage)\n\n Which step went wrong. Null when the test succeeded.\n\n- **`error`** `string | null`\n\n What went wrong. Null when the test succeeded.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### PullErrorStage\n\n`\"request\" | \"response\" | \"parse\" | \"ingest\"`\n\n## List Pulls\n\n### Endpoint\n\nRetrieve a list of pulls.\n\n```http\nGET /v1/projects/:project_id/pulls\n```\n\n**Scope:** `pulls:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are ordered by. Optional. Defaults to `\"created_at\"`. Allowed values: `\"name\"`, `\"created_at\"`, `\"last_success_at\"`.\n\n- **`status`** `string`\n Return only pulls in this state. Optional. Allowed values: `\"active\"`, `\"paused\"`, `\"disabled\"`.\n\n- **`source_id`** `string`\n Return only pulls that write to this source. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Pull[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Pull\n\n### Endpoint\n\nRetrieve a single pull.\n\n```http\nGET /v1/projects/:project_id/pulls/:pull_id\n```\n\n**Scope:** `pulls:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`pull_id`** `string` -- **Required**\n Unique identifier of the pull.\n\n### Response\n\nPull retrieved\n\n```ts\n{\n message: string;\n data: Pull;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Pull\n\n### Endpoint\n\nCreate a scheduled pull in a project.\n\n```http\nPOST /v1/projects/:project_id/pulls\n```\n\n**Scope:** `pulls:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name shown for this pull. Minimum length: `2`. Maximum length: `128`.\n\n- **`source_id`** `string` -- **Required**\n Source that fetched records are written to. Minimum length: `1`.\n\n- **`collection_slug`** `string | null`\n Collection within the source that records land in. Leave unset and it is derived from the pull name, so each pull gets its own stream. Optional.\n\n- **`endpoint_url`** `string` -- **Required**\n HTTPS URL fetched on every run. Must resolve to a public address. Minimum length: `1`. Maximum length: `2048`.\n\n- **`method`** `string`\n HTTP method used to fetch the endpoint. Optional. Defaults to `\"get\"`. Allowed values: `\"get\"`, `\"post\"`.\n\n- **`headers`** `object | null`\n Headers sent with every fetch, for authenticating to the endpoint. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.\n\n- **`request_body`** `string | null`\n Body sent with every fetch. Only valid when `method` is `post`, for endpoints that answer queries over POST. Optional.\n\n- **`json_records_path`** `string | null`\n Dot path to the array of records inside a JSON response, for example `data` or `data.result`. Leave unset to store the response exactly as it arrives. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting when the endpoint is fetched, evaluated in UTC. Defaults to every minute, which is also the shortest supported gap. Optional. Defaults to `\"* * * * *\"`. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\nPull created\n\n```ts\n{\n message: string;\n data: Pull;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Pull names are unique within a project.\n- The endpoint must be reachable over HTTPS at a public address. Addresses inside private or reserved ranges are rejected, and the check is repeated on every fetch, not just at create.\n- A pull is created `active` even if the project has no servers yet. It starts fetching as soon as a server exists; an empty fleet delays the first fetch rather than changing the status.\n- An endpoint answering with a Prometheus HTTP Service Discovery document is followed rather than stored. Each discovered target is fetched, its metrics parsed into records, and the target's labels merged onto them so targets stay distinguishable. This is what makes an endpoint that hands out short-lived signed URLs work on a schedule: the discovery response is re-read on every run, so the credentials are always current.\n- Discovered targets are fetched over HTTPS at public addresses only, checked with the same guard as the endpoint itself, so a discovery response cannot direct a pull at a private address.\n- A pull follows at most 50 discovered targets and fails if the document lists more, rather than fetching some and silently omitting the rest. Individual targets are retried; if some still fail, the run succeeds with the records it did collect and reports how many targets did not.\n- `request_body` is only accepted when `method` is `post`. Sending one with a `get` pull is rejected rather than ignored, so a half-configured pull fails at create time instead of quietly fetching the wrong thing.\n- `headers` accepts at most 20 entries. A header whose value is `null` is ignored here, since on create there is nothing yet for it to remove. Names are matched case-insensitively, so two spellings of one name are stored as a single header.\n- `schedule_cron` must be a valid cron expression. It is evaluated in UTC, and the finest granularity is one minute.\n- `collection_slug` must be lowercase, start with a letter or digit, and may otherwise contain digits, letters, hyphens and underscores.\n- `json_records_path` is for endpoints that wrap their rows in an envelope. An endpoint returning `{\"data\": [{...}, {...}]}` stores one record containing the whole response unless you set the path to `data`, which stores the two records instead. Nested keys are joined with dots (`data.result`). Object keys only: array indexes and wildcards are not supported.\n- `json_records_path` applies only to JSON responses. JSONL, CSV, TSV and Prometheus responses already produce one record per row, so leave it unset for those.\n- A fetch fails with a `parse` error when the path is missing from the response or does not resolve to a list or object, rather than falling back to storing the whole document. Use the test endpoint to check a path against a live response before saving.\n\n## Update Pull\n\n### Endpoint\n\nUpdate a pull.\n\n```http\nPOST /v1/projects/:project_id/pulls/:pull_id\n```\n\n**Scope:** `pulls:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`pull_id`** `string` -- **Required**\n Unique identifier of the pull.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this pull. Optional. Minimum length: `2`. Maximum length: `128`.\n\n- **`collection_slug`** `string | null`\n Collection within the source that records land in. Leave unset and it is derived from the pull name, so each pull gets its own stream. Optional.\n\n- **`endpoint_url`** `string`\n HTTPS URL fetched on every run. Must resolve to a public address. Optional. Minimum length: `1`. Maximum length: `2048`.\n\n- **`method`** `string`\n HTTP method used to fetch the endpoint. Optional. Allowed values: `\"get\"`, `\"post\"`.\n\n- **`headers`** `object | null`\n Headers sent with every fetch, for authenticating to the endpoint. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.\n\n- **`request_body`** `string | null`\n Body sent with every fetch. Only valid when `method` is `post`, for endpoints that answer queries over POST. Optional.\n\n- **`json_records_path`** `string | null`\n Dot path to the array of records inside a JSON response, for example `data` or `data.result`. Leave unset to store the response exactly as it arrives. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting when the endpoint is fetched, evaluated in UTC. Defaults to every minute, which is also the shortest supported gap. Optional. Minimum length: `1`. Maximum length: `128`.\n\n### Response\n\nPull updated\n\n```ts\n{\n message: string;\n data: Pull;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Changes take effect on the next fetch; a fetch already in flight finishes under the old configuration.\n- A changed `schedule_cron` takes effect from the next fetch onward.\n- At least one field must be provided.\n- Any field not listed here is rejected, including `source_id`, which cannot be changed after create. Create a new pull to write into a different source.\n- Clearing `request_body` is required before changing `method` from `post` to `get`; a pull cannot keep a body it would never send.\n- `headers` is a patch, not a replacement, because the values are write-only and never returned. A name mapped to a string adds or replaces that header, a name mapped to `null` removes it, and a name you do not mention keeps its stored value. Omit the field to leave every header alone, or send `null` in place of the object to remove all of them.\n- `headers` names are matched case-insensitively, so patching `authorization` replaces a stored `Authorization` rather than adding a second header. The spelling you send is the one stored and sent.\n- `headers` accepts at most 20 entries, counted after the patch is applied.\n- `schedule_cron` must be a valid cron expression, evaluated in UTC.\n- Send `json_records_path` as `null` to stop unwrapping and store responses whole again.\n\n## Delete Pull\n\n### Endpoint\n\nDelete a pull.\n\n```http\nDELETE /v1/projects/:project_id/pulls/:pull_id\n```\n\n**Scope:** `pulls:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`pull_id`** `string` -- **Required**\n Unique identifier of the pull.\n\n### Response\n\nPull deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Records the pull already wrote are not deleted. They belong to the source and stay queryable.\n\n## Pause Pull\n\n### Endpoint\n\nPause a pull so it stops fetching.\n\n```http\nPOST /v1/projects/:project_id/pulls/:pull_id/pause\n```\n\n**Scope:** `pulls:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`pull_id`** `string` -- **Required**\n Unique identifier of the pull.\n\n### Response\n\nPull paused\n\n```ts\n{\n message: string;\n data: Pull;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Only an active pull can be paused. Pausing an already-paused pull succeeds and changes nothing.\n\n## Resume Pull\n\n### Endpoint\n\nResume a paused pull.\n\n```http\nPOST /v1/projects/:project_id/pulls/:pull_id/resume\n```\n\n**Scope:** `pulls:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`pull_id`** `string` -- **Required**\n Unique identifier of the pull.\n\n### Response\n\nPull resumed\n\n```ts\n{\n message: string;\n data: Pull;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Resumes a pull you paused, and also re-enables one the system disabled after a long failure streak. Resuming clears the failure count, so it starts from a clean slate.\n\n## Test Pull\n\n### Endpoint\n\nFetch the endpoint once without storing anything.\n\n```http\nPOST /v1/projects/:project_id/pulls/:pull_id/test\n```\n\n**Scope:** `pulls:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`pull_id`** `string` -- **Required**\n Unique identifier of the pull.\n\n### Response\n\n```ts\n{\n message: string;\n data: PullTestResult;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Nothing is ingested and no health field or schedule is changed, so this is safe to run against a live pull.\n- The response reports the HTTP status, how long the fetch took, how many records the configured format would produce, and the first 2 KB of the body.\n\n",
|
|
37
37
|
"api/checks": "# Checks\n\n## Check Model\n\n### Fields\n\n- **`object`** `\"check\"`\n\n- **`id`** `string`\n\n- **`project_id`** `string`\n\n- **`team_id`** `string`\n\n- **`name`** `string`\n\n- **`source_id`** `string`\n\n The source that observations are written to. Nothing else is stored.\n\n- **`source_name`** `string | null`\n\n- **`collection_slug`** `string`\n\n The collection within the source that observations land in. Give several checks the same slug\n to chart them together, one strip per check under a worst-of overall row.\n\n- **`endpoint_url`** `string`\n\n The HTTPS URL requested on every run.\n\n- **`method`** [`CheckMethod`](/api/checks#check-method)\n\n- **`headers_configured`** `string[]`\n\n Names of the headers sent with every request. The values are stored encrypted and are never\n returned, so this shows what was configured without revealing the secrets.\n\n- **`request_body`** `string | null`\n\n The request body sent when the method is `post`. Null for `get`.\n\n- **`schedule_cron`** `string`\n\n Cron expression setting when the endpoint is requested, evaluated in UTC.\n\n- **`check_retry_seconds`** `number | null`\n\n How long to wait before one confirming request settles a failed run. Null records the first\n result as it stands, which turns a single flake into a minute of recorded downtime.\n\n- **`status`** [`CheckStatus`](/api/checks#check-status)\n\n The current state. A check is only ever stopped by you pausing it or by the team falling out of\n good standing. A failing endpoint never stops it, because the outage is the thing it is there\n to record.\n\n- **`next_fetch_at`** [`ISODateString | null`](/api/checks#iso-date-string)\n\n When the next run is due. Null while the check is paused or disabled.\n\n- **`missed_slot_count`** `number`\n\n How many scheduled slots have elapsed without a run, since the check was created or last\n resumed. Almost always because the project had no healthy server at the time.\n \n Missed slots are counted rather than run late: a slot describes the endpoint at one moment, so\n a late request would report the wrong minute. These are gaps in the uptime record, not\n downtime.\n\n- **`last_up`** `boolean | null`\n\n Whether the most recent run found the endpoint up, meaning it answered with a 2xx. Null until\n the first run settles, and null again after a resume, which is what separates a check nothing\n has measured yet from one measured to be down.\n\n- **`results_hourly`** `CheckHourlyResults[]`\n\n The last 24 hours of results, oldest first. Always 24 entries, so an hour with no runs reads as\n zeros rather than being absent, and a check that has never run still returns a full window. `up\n / (up + down)` over the window is the uptime for that period.\n\n- **`last_checked_at`** [`ISODateString | null`](/api/checks#iso-date-string)\n\n When the endpoint was last requested, whether or not it answered. Cleared by a resume.\n\n- **`last_success_at`** [`ISODateString | null`](/api/checks#iso-date-string)\n\n When the endpoint last returned a 2xx. Survives a pause, so it dates an ongoing outage.\n\n- **`last_failure_at`** [`ISODateString | null`](/api/checks#iso-date-string)\n\n When the endpoint last failed to return a 2xx. Survives a pause.\n\n- **`consecutive_failure_count`** `number`\n\n How many runs have failed in a row. Resets on the next success. Reporting only: the count never\n stops the check, however high it climbs.\n\n- **`last_error`** `string | null`\n\n The error from the most recent failed run.\n\n- **`last_error_stage`** [`CheckErrorStage | null`](/api/checks#check-error-stage)\n\n Which step of the most recent failed run went wrong.\n\n- **`last_http_status`** `number | null`\n\n The HTTP status returned by the most recent run. Null when nothing answered.\n\n- **`last_response_time_ms`** `number | null`\n\n How long the most recent run took, in milliseconds.\n\n- **`created_at`** [`ISODateString`](/api/checks#iso-date-string)\n\n- **`updated_at`** [`ISODateString`](/api/checks#iso-date-string)\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### CheckMethod\n\n`\"get\" | \"post\"`\n\nStored as plain strings rather than Postgres enums: nothing queries by either, so a database enum\nwould only buy DDL on every added value. `CheckStatus` stays an enum because it IS queried, and\nbacks the dispatch index.\n\n#### CheckStatus\n\n`\"active\" | \"paused\" | \"disabled\"`\n\n#### CheckErrorStage\n\n`\"request\" | \"response\"`\n\n## Check Test Result\n\n### Fields\n\n- **`ok`** `boolean`\n\n Whether the endpoint answered with a 2xx.\n\n- **`status`** `number`\n\n The HTTP status the endpoint returned. Zero when no response was received at all.\n\n- **`status_text`** `string`\n\n- **`response_time_ms`** `number`\n\n How long the request took, in milliseconds.\n\n- **`byte_length`** `number`\n\n How many bytes the endpoint returned.\n\n- **`body_preview`** `string`\n\n The first 2 KB of the response body, for confirming you reached the endpoint you meant to.\n\n- **`error_stage`** [`CheckErrorStage | null`](/api/checks#check-error-stage)\n\n Which step went wrong. Null when the test succeeded.\n\n- **`error`** `string | null`\n\n What went wrong. Null when the test succeeded.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### CheckErrorStage\n\n`\"request\" | \"response\"`\n\n## List Checks\n\n### Endpoint\n\nRetrieve a list of checks.\n\n```http\nGET /v1/projects/:project_id/checks\n```\n\n**Scope:** `checks:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field the results are ordered by. Optional. Defaults to `\"created_at\"`. Allowed values: `\"name\"`, `\"created_at\"`.\n\n- **`status`** `string`\n Return only checks in this state. Optional. Allowed values: `\"active\"`, `\"paused\"`, `\"disabled\"`.\n\n- **`source_id`** `string`\n Return only checks that write their observations to this source. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Check[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- `after` and `before` are mutually exclusive.\n\n## Retrieve Check\n\n### Endpoint\n\nRetrieve a single check.\n\n```http\nGET /v1/projects/:project_id/checks/:check_id\n```\n\n**Scope:** `checks:read`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`check_id`** `string` -- **Required**\n Unique identifier of the check.\n\n### Response\n\nCheck retrieved\n\n```ts\n{\n message: string;\n data: Check;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n## Create Check\n\n### Endpoint\n\nCreate a scheduled check in a project.\n\n```http\nPOST /v1/projects/:project_id/checks\n```\n\n**Scope:** `checks:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n### Request Body\n\n- **`name`** `string` -- **Required**\n Name shown for this check. Minimum length: `2`. Maximum length: `128`.\n\n- **`source_id`** `string` -- **Required**\n Source that observations are written to. Nothing else about the response is stored. Minimum length: `1`.\n\n- **`collection_slug`** `string` -- **Required**\n Collection within the source that observations land in. Give several checks the same slug to chart them together, one strip per check under a worst-of overall row. Minimum length: `1`. Maximum length: `64`.\n\n- **`endpoint_url`** `string` -- **Required**\n HTTPS URL requested on every run. Must resolve to a public address. Minimum length: `1`. Maximum length: `2048`.\n\n- **`method`** `string`\n HTTP method used to request the endpoint. Optional. Defaults to `\"get\"`. Allowed values: `\"get\"`, `\"post\"`.\n\n- **`headers`** `object | null`\n Headers sent with every request, for authenticating to the endpoint. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.\n\n- **`request_body`** `string | null`\n Body sent with every request. Only valid when `method` is `post`, for health endpoints that answer queries over POST. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting when the endpoint is requested, evaluated in UTC. Defaults to every minute, which is also the shortest supported gap. Optional. Defaults to `\"* * * * *\"`. Minimum length: `1`. Maximum length: `128`.\n\n- **`check_retry_seconds`** `integer | null`\n Seconds to wait before one confirming request settles a failed run. Send `null` to record the first result as it stands. Optional. Defaults to `10`.\n\n### Response\n\nCheck created\n\n```ts\n{\n message: string;\n data: Check;\n status: 201;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- The endpoint must be reachable over HTTPS at a public address. Addresses inside private or reserved ranges are rejected, and the check is repeated on every run, not just at create.\n- A check is created `active` even if the project has no servers yet. It starts running as soon as a server exists; an empty fleet delays the first run rather than changing the status.\n- The response body is never stored or parsed. Each run writes one observation recording whether the endpoint answered, its status code and how long it took.\n- A failing endpoint never stops a check, however long the failure lasts. Only pausing it, or the team falling out of good standing, stops one.\n- `request_body` is only accepted when `method` is `post`. Sending one with a `get` check is rejected rather than ignored, so a half-configured check fails at create time instead of quietly measuring the wrong request.\n- `headers` accepts at most 20 entries. A header whose value is `null` is ignored here, since on create there is nothing yet for it to remove. Names are matched case-insensitively, so two spellings of one name are stored as a single header.\n- `schedule_cron` must be a valid cron expression. It is evaluated in UTC, and the finest granularity is one minute.\n- `collection_slug` is required, and must be lowercase, start with a letter or digit, and may otherwise contain digits, letters, hyphens and underscores. Naming it here is what lets several checks deliberately share one collection and chart together; renaming the check later never moves the series, because the slug is stored rather than derived.\n- `check_retry_seconds` is between 1 and 20, and defaults to 10. A failed run waits this long and requests once more before it is recorded, so a single flake does not become a recorded minute of downtime. Send `null` to record the first result as it stands.\n- Each run writes one observation to `collection_slug` carrying `check_id`, `check_name`, `checked_at`, `up`, `http_status`, `response_time_ms`, `error_stage`, `error_message` and `attempts`. `up` is 1 when the endpoint returned a 2xx and 0 when it did not answer or answered with an error status, so the average of `up` over a period is the uptime for that period.\n- `check_name` is the name the check carried when the run was recorded, so a chart grouped on `check_id` can label its series with something readable. Renaming the check does not rewrite observations already stored.\n- `checked_at` is the scheduled minute rather than the moment the observation was written, so a run delayed by a confirming request still lands in the minute it describes.\n- `name` must be unique within the project, so a check is identifiable in a list and in the strip it charts as.\n\n## Update Check\n\n### Endpoint\n\nUpdate a check.\n\n```http\nPOST /v1/projects/:project_id/checks/:check_id\n```\n\n**Scope:** `checks:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`check_id`** `string` -- **Required**\n Unique identifier of the check.\n\n### Request Body\n\n- **`name`** `string`\n Name shown for this check. Optional. Minimum length: `2`. Maximum length: `128`.\n\n- **`collection_slug`** `string | null`\n Collection within the source that observations land in. Give several checks the same slug to chart them together, one strip per check under a worst-of overall row. Optional.\n\n- **`endpoint_url`** `string`\n HTTPS URL requested on every run. Must resolve to a public address. Optional. Minimum length: `1`. Maximum length: `2048`.\n\n- **`method`** `string`\n HTTP method used to request the endpoint. Optional. Allowed values: `\"get\"`, `\"post\"`.\n\n- **`headers`** `object | null`\n Headers sent with every request, for authenticating to the endpoint. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.\n\n- **`request_body`** `string | null`\n Body sent with every request. Only valid when `method` is `post`, for health endpoints that answer queries over POST. Optional.\n\n- **`schedule_cron`** `string`\n Cron expression setting when the endpoint is requested, evaluated in UTC. Defaults to every minute, which is also the shortest supported gap. Optional. Minimum length: `1`. Maximum length: `128`.\n\n- **`check_retry_seconds`** `integer | null`\n Seconds to wait before one confirming request settles a failed run. Send `null` to record the first result as it stands. Optional.\n\n### Response\n\nCheck updated\n\n```ts\n{\n message: string;\n data: Check;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Changes take effect on the next run; a run already in flight finishes under the old configuration.\n- A changed `schedule_cron` takes effect from the next run onward.\n- At least one field must be provided.\n- Any field not listed here is rejected, including `source_id`, which cannot be changed after create. Create a new check to record into a different source.\n- Clearing `request_body` is required before changing `method` from `post` to `get`; a check cannot keep a body it would never send.\n- `headers` is a patch, not a replacement, because the values are write-only and never returned. A name mapped to a string adds or replaces that header, a name mapped to `null` removes it, and a name you do not mention keeps its stored value. Omit the field to leave every header alone, or send `null` in place of the object to remove all of them.\n- `headers` names are matched case-insensitively, so patching `authorization` replaces a stored `Authorization` rather than adding a second header. The spelling you send is the one stored and sent.\n- `headers` accepts at most 20 entries, counted after the patch is applied.\n- `schedule_cron` must be a valid cron expression, evaluated in UTC. Changing it restarts the missed-slot count from the edit, since slots before it belonged to a different schedule.\n- Send `check_retry_seconds` as `null` to stop confirming failures and record the first result as it stands.\n- Changing `collection_slug` starts a new series. Observations already written stay where they are, so a chart over the old collection stops at the edit.\n\n## Delete Check\n\n### Endpoint\n\nDelete a check.\n\n```http\nDELETE /v1/projects/:project_id/checks/:check_id\n```\n\n**Scope:** `checks:delete`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`check_id`** `string` -- **Required**\n Unique identifier of the check.\n\n### Response\n\nCheck deleted\n\n```ts\n{\n message: string;\n data: null;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Observations the check already wrote are not deleted. They belong to the source and stay queryable, so a chart of past uptime keeps working.\n\n## Pause Check\n\n### Endpoint\n\nPause a check so it stops running.\n\n```http\nPOST /v1/projects/:project_id/checks/:check_id/pause\n```\n\n**Scope:** `checks:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`check_id`** `string` -- **Required**\n Unique identifier of the check.\n\n### Response\n\nCheck paused\n\n```ts\n{\n message: string;\n data: Check;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Only an active check can be paused. Pausing an already-paused check succeeds and changes nothing.\n- A pause is a gap in the uptime record rather than downtime. Nothing is recorded for the minutes it covers.\n\n## Resume Check\n\n### Endpoint\n\nResume a paused check.\n\n```http\nPOST /v1/projects/:project_id/checks/:check_id/resume\n```\n\n**Scope:** `checks:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`check_id`** `string` -- **Required**\n Unique identifier of the check.\n\n### Response\n\nCheck resumed\n\n```ts\n{\n message: string;\n data: Check;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Resumes a check you paused. The failure count starts again from zero.\n- A check the system stopped while the team was not in good standing is not resumable here and returns 400. It starts again on its own once the team is back in good standing.\n\n## Test Check\n\n### Endpoint\n\nRequest the endpoint once without recording anything.\n\n```http\nPOST /v1/projects/:project_id/checks/:check_id/test\n```\n\n**Scope:** `checks:write`\n\n### Path Parameters\n\n- **`project_id`** `string` -- **Required**\n Unique identifier of the project.\n\n- **`check_id`** `string` -- **Required**\n Unique identifier of the check.\n\n### Response\n\n```ts\n{\n message: string;\n data: CheckTestResult;\n status: 200;\n error: null;\n pagination: null;\n endpoint: string;\n}\n```\n\n### Comments\n\n- Nothing is recorded and no health field or schedule is changed, so this is safe to run against a live check. The result does not appear in the uptime history.\n- The response reports the HTTP status, how long the request took, and the first 2 KB of the body so you can confirm you reached the endpoint you meant to.\n- There is no confirming retry here, unlike a scheduled run: a test reports what happened rather than settling a verdict.\n\n",
|
|
38
38
|
"api/logs": "# Logs\n\n## Log Model\n\n### Fields\n\n- **`object`** `\"log\"`\n\n- **`action`** `string`\n\n What happened, as a short code such as `sign_in` or `user.mfa_reset`.\n\n- **`success`** `boolean`\n\n Whether the attempted action succeeded. Failed attempts, like a bad sign-in, are recorded too.\n\n- **`message`** `string | null`\n\n- **`team_id`** `string | null`\n\n Null on sign-in entries that are not tied to a team.\n\n- **`actor_id`** `string | null`\n\n Who acted: the user id, or the API key id when a key acted. See `actor_type`. Null when\n Tailglow acted automatically.\n\n- **`actor_type`** `string | null`\n\n One of `AUTH_SUBJECTS`. Whether a user or an API key acted.\n\n- **`actor_name`** `string | null`\n\n Display name of the actor as it was WHEN THE ACTION HAPPENED, so a renamed or deleted actor\n still reads correctly. Reads `Tailglow Admin` on entries where a Tailglow operator acted, and\n `Tailglow` on entries Tailglow wrote automatically.\n\n- **`actor_context`** `string | null`\n\n `app` for a member or API key, `admin` when a Tailglow operator acted on this team, and\n `system` when Tailglow acted automatically, for example when a payment arrived. Decide who\n acted from this field, not from `actor_name`, which members choose themselves.\n\n- **`actor_role_id`** `string | null`\n\n Role the actor held at the time. Null on entries a customer reads about an operator.\n\n- **`actor_session_id`** `string | null`\n\n Session the action belongs to, so one actor's run of changes can be grouped.\n\n- **`resource_type`** `string | null`\n\n What the entry is about: the user who signed in, or the row that changed.\n\n- **`resource_id`** `string | null`\n\n Identifier of the resource named by `resource_type`.\n\n- **`ip_address`** `string | null`\n\n- **`device`** `string | null`\n\n Readable device summary parsed from the user agent, for example `Chrome 126 on Mac OS`.\n\n- **`user_agent`** `string | null`\n\n- **`created_at`** [`ISODateString`](/api/logs#iso-date-string)\n\n- **`type`** [`LogType`](/api/logs#log-type)\n\n Which kind of entry this is: `auth` for sign-in activity, `audit` for changes to resources,\n `billing` for changes to the team's billing.\n\n- **`details`** [`AuthLogDetails`](/api/logs#auth-details-model) | [`AuditLogDetails`](/api/logs#audit-details-model) | [`BillingLogDetails`](/api/logs#billing-details-model)\n\n Extra context whose shape follows `type`: `AuthLogDetails` for sign-ins, `AuditLogDetails` for\n changes, `BillingLogDetails` for billing.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### LogType\n\n`\"auth\" | \"admin_auth\" | \"audit\" | \"billing\"`\n\n#### BillingPlan\n\n`\"pro_v1\" | \"enterprise_v1\"`\n\n## Auth Log Details Model\n\n### Fields\n\n- **`email`** `string`\n\n The email address the sign-in was attempted with.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Audit Log Details Model\n\n### Fields\n\n- **`changed_fields`** `string[]`\n\n The fields the request changed, sorted and de-duplicated.\n\n- **`endpoint`** `string | null`\n\n The API endpoint the change came through.\n\n- **`request_client`** `string | null`\n\n Which surface made the request: the API directly, the dashboard, the admin portal, or the AI\n assistant.\n\n- **`client_version`** `string | null`\n\n Version of the client that made the request, when it reported one.\n\n- **`ai_chat_id`** `string | null`\n\n Set when the AI assistant made the change; identifies the chat it happened in.\n\n- **`[key: string]`** `unknown`\n\n Any additional key recorded with the entry.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n## Billing Log Details Model\n\n### Fields\n\n- **`amount_cents`** `number`\n\n Amount in cents, such as an invoice total or a failed payment.\n\n- **`stripe_invoice_id`** `string`\n\n Stripe's id for the invoice, as shown on the invoice list.\n\n- **`period_start_at`** [`ISODateString`](/api/logs#iso-date-string)\n\n First moment of the billing month the entry is about.\n\n- **`plan_from`** [`BillingPlan`](/api/logs#billing-plan)\n\n- **`plan_to`** [`BillingPlan`](/api/logs#billing-plan)\n\n- **`payment_method_brand`** `string`\n\n- **`payment_method_last4`** `string`\n\n- **`discount_id`** `string`\n\n- **`discount_percent`** `number`\n\n- **`discount_amount_cents`** `number`\n\n- **`line_item_type`** `string`\n\n The invoice line a discount applies to, such as `compute` or `storage`.\n\n- **`available_ai_tokens_before`** `number`\n\n Tailglow AI tokens available to the team before the entry's change.\n\n- **`available_ai_tokens_after`** `number`\n\n Tailglow AI tokens available to the team after the entry's change.\n\n- **`trial_ends_at`** [`ISODateString`](/api/logs#iso-date-string)\n\n When the free trial ends after the entry's change.\n\n- **`days`** `number`\n\n Days the free trial was extended by.\n\n- **`server_hours`** `number`\n\n Server-hours of credit the entry added.\n\n- **`has_payment_method`** `boolean`\n\n For a trial that ended: whether a payment method was on file, so its servers kept running.\n\n- **`invoice_id`** `string`\n\n For a delinquency change: the invoice that caused it, or whose payment cleared it.\n\n### Referenced Types\n\n#### ISODateString\n\n`ISODateString`\n\nAn ISO 8601 date-time string returned at the JSON API boundary.\n\n#### BillingPlan\n\n`\"pro_v1\" | \"enterprise_v1\"`\n\n## List Logs\n\n### Endpoint\n\nRetrieve activity logs available to the current authorization.\n\n```http\nGET /v1/logs\n```\n\n### Query Parameters\n\n- **`order_by`** `string`\n Field used to order the logs. Optional. Defaults to `\"created_at\"`. Allowed values: `\"created_at\"`.\n\n- **`start_at`** [`ISODateString`](/api/logs#iso-date-string)\n Earliest log date and time to return. Optional.\n\n- **`end_at`** [`ISODateString`](/api/logs#iso-date-string)\n Latest log date and time to return. Optional.\n\n- **`team_id`** `string`\n Filter by team ID. Optional.\n\n- **`user_id`** `string`\n Filter by user ID. Optional.\n\n- **`type`** [`LogType`](/api/logs#log-type)\n Filter by log type. Optional.\n\n- **`limit`** `number`\n Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.\n\n- **`after`** `string`\n Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.\n\n- **`before`** `string`\n Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.\n\n- **`sort`** `string`\n Sort direction for the result set. Optional. Defaults to `\"desc\"`. Allowed values: `\"asc\"`, `\"desc\"`.\n\n### Response\n\n```ts\n{\n message: string;\n data: Log[];\n status: 200;\n error: null;\n pagination: Pagination;\n endpoint: string;\n}\n```\n\n### Comments\n\n- You can retrieve your own authentication logs without `logs:read`. Billing logs require `billing:read`; all other log reads require `logs:read`.\n- Without `type`, the list includes every type you can read. `admin_auth` logs exist only on Tailglow's own admin team.\n- `after` and `before` are mutually exclusive.\n\n",
|
|
39
39
|
"guides/agents": "# Set up Tailglow with an agent\n\nThis page is written for AI agents such as Claude Code, Codex, Cursor and Cowork that are helping someone set up Tailglow. People are welcome to read it too.\n\nTailglow turns the data a product sends into live charts and pages. Data arrives at a **source** and lands in **collections**. **Views** reshape it, and **metrics** count it; charts, **pages** and **monitors** are built on metrics. Every project runs on its own **server**, which Tailglow provisions and operates.\n\n## Start by asking\n\nUnless the person has already said, ask what they want to see first. Each answer maps to one path in step 4:\n\n| They want | Path |\n| ------------------------------------------------------- | ------------------------------- |\n| Visitors, page views and clicks in a web app | [Web app](#web-app) |\n| Events, jobs or errors from a backend | [Backend](#backend) |\n| Screens and errors in a React Native or Expo app | [React Native](#react-native) |\n| To know that a site or API is up | [Uptime check](#uptime-check) |\n| Data they already have: files, exports, another service | [Anything else](#anything-else) |\n\n## 1. Sign in\n\n```bash\nnpx @tailglow/cli login\n```\n\nWithout a terminal attached, which is how an agent runs it, the command prints a link and a code, remembers the sign-in, and exits. Show both to the person. They sign in, or create an account if they have none (the first 3 days are free and need no card), check that the page shows the same code, and approve. Then finish signing in:\n\n```bash\nnpx @tailglow/cli login --wait\n```\n\n`--wait` waits up to a minute. Exit code `75` means the person has not approved yet: run it again. Exit code `1` with status `unreachable` means the API could not be reached: the sign-in is kept, so run `login --wait` again once the connection is back. Status `consumed` means the credential was already handed out: start over with `login --new`. Any other exit code `1` means the sign-in was denied, expired or withdrawn: start over with `login`.\n\nThis signs the CLI in as the person, with what their role allows in each of their teams, starting in the team they choose. To use an API key for one team instead, with only the permissions the person picks, add `--method api-key`; that team needs a project first.\n\nWhen Tailglow is at capacity, a brand-new account joins a waitlist instead of signing in, so the sign-in cannot be approved and `--wait` keeps exiting `75` until it expires. If the person sees the waitlist, say so and stop until they are let in.\n\nThe credential is saved to `~/.tailglow/config.json` and the CLI uses it from then on. You never need to see it: do not ask for it, print it, or put it in code. `tglow logout` removes it.\n\nTo keep the CLI around, install it once. It needs Node 18.3 or newer:\n\n```bash\nnpm install -g @tailglow/cli\ntglow whoami\n```\n\nIn CI, set `TAILGLOW_API_KEY` to an API key instead of logging in.\n\n## 2. Learn the CLI as you go\n\nEvery command has the shape `tglow <resource> <command> [flags]`, and the CLI documents itself:\n\n```bash\ntglow help # every resource and the global flags\ntglow sources # the commands on one resource\ntglow sources create --help --json # one command's flags, types, required fields and rules\n```\n\nRead `--help --json` before you call a command for the first time. It is generated from the same validation the API enforces, so it is always current, and its `notes` carry rules a field list cannot.\n\n- Flags are field names in kebab-case: `time_window_minutes` is `--time-window-minutes`.\n- IDs in the path are flags too: `--source-id src_x`, never a positional argument.\n- Output is JSON whenever it is piped. Read `.data`, and `.pagination.next_cursor` to continue a list.\n- Set the project once so you can leave it off every command: `tglow config set project prj_...`\n\nThe CLI also carries these docs, so you do not need to fetch them. `tglow docs` prints the index and `tglow docs guides/sdk` prints one page, as markdown even when piped.\n\n## 3. Pick a project\n\n```bash\ntglow projects list\ntglow projects create --name \"Acme web\"\ntglow config set project prj_...\n```\n\nReuse a project when the person already has one for this app. A new team's first project comes with a free trial server, which starts in the background: `tglow servers list` shows `provisioning` until it is `active`.\n\n## 4. Connect data\n\nMost paths need a source and an ingest key:\n\n```bash\ntglow sources create --name \"Web app\"\ntglow ingest_keys create --source-id src_...\n```\n\nThe ingest key's `url` is the address to send records to, with the key already in it. It is `null` until the project's server is active; retrieve the key again once it is. An ingest key can only write records into its own source, so it is safe in client-side code. Keep it in the app's environment configuration anyway, so it can be replaced without a code change.\n\n### Web app\n\n```bash\nnpm install @tailglow/browser\n```\n\n```javascript\nimport { Tailglow } from \"@tailglow/browser\";\n\n// The ingest key's url, read from the app's public environment configuration.\nconst tg = new Tailglow({ url: TAILGLOW_INGEST_URL });\n\ntg.track(\"signup\", { plan: \"pro\" });\ntg.identify(\"user_123\");\n```\n\nPage views, clicks in elements marked `data-telemetry`, outbound clicks, uncaught errors and performance signals are collected automatically. A site without a build step can use the script tag instead: see [Drop-in script](/guides/quickstart#drop-in-script). In Next.js, SvelteKit or another framework that renders on the server, create the SDK in the browser only, once: see [Frameworks](/guides/sdk#frameworks).\n\nThe SDK ignores `localhost` by default. To test locally, pass `excludeLocalhost: false` and tag the traffic, for example `context: { environment: \"development\" }`, so it can be filtered out of real charts.\n\n### Backend\n\n```bash\nnpm install @tailglow/core\n```\n\n```javascript\nimport { TailglowCore } from \"@tailglow/core\";\n\nconst tg = new TailglowCore({ url: process.env.TAILGLOW_INGEST_URL });\n\ntg.track(\"job_completed\", { job_id: \"abc\" });\nawait tg.flush();\n```\n\nNothing is collected automatically on a backend. Flush before the process or request ends; in a serverless function, see [Serverless functions](/guides/sdk#serverless-functions). Node 22 or newer, or Bun. Any other language can [send records over HTTP](#anything-else).\n\n### React Native\n\nInstall `@tailglow/react-native` and follow [React Native](/guides/sdk#react-native-tailglowreact-native) in the SDK guide. The app passes the SDK its `AppState`, `Platform`, storage and navigation, which the guide shows in one file.\n\n### Uptime check\n\nA check needs no code. It fetches a public HTTPS address on a schedule and records whether it answered:\n\n```bash\ntglow checks create --name \"Homepage\" --source-id src_... --collection-slug uptime --endpoint-url https://acme.com\ntglow checks test --check-id chk_...\n```\n\n`test` fetches once and stores nothing. Checks that share a collection chart together, one row each, which is what a status page is. See [Checks](/guides/checks).\n\n### Anything else\n\nPOST JSON, JSONL, CSV, TSV or plain text to the ingest URL. Add `&collection=<name>` to choose where the records land; the collection is created the first time data arrives for it.\n\n```bash\ncurl -X POST \"$TAILGLOW_INGEST_URL&collection=orders\" \\\n -H \"Content-Type: application/json\" \\\n -d '[{\"order_id\":\"o_1\",\"total\":42.5,\"created_at\":\"2026-09-27T12:00:00Z\"}]'\n```\n\n`202 Accepted` means the records are stored and will be processed. Retry on `5xx` and on network errors; a `4xx` means the request itself has to change. One request can carry up to 20 MiB. See [Sending data](/guides/sending-data).\n\n## 5. Confirm data arrived\n\n```bash\ntglow collections list --source-id src_...\ntglow collections list-docs --collection-id col_...\n```\n\nThe browser and backend SDKs write to collections named `events`, `errors` and `logs`. A collection appears with its first record.\n\n## 6. Build a chart\n\nA view is written for a settled record shape. By default Tailglow settles a shape when it arrives again at least 60 minutes after it was first seen, or when 100 or more records of it arrive in one batch. Check with:\n\n```bash\ntglow collections list-schemas --collection-id col_...\n```\n\nIf the person confirms a candidate shape is real data rather than a one-off test, settle it by hand:\n\n```bash\ntglow collections update-schema --collection-id col_... --schema-version-id csv_... --state settled\n```\n\nThen create a view on the collection and declare the fields it should produce, based on the records you sampled in step 5. A metric can only use fields the view declares, and its time field has to be one of them, typed `date`. This example reads the SDK's `events` collection:\n\n```bash\ntglow views create --collection-id col_... --name \"Product events\" \\\n --output-schema '[{\"name\":\"event_time\",\"type\":\"date\"},{\"name\":\"type\",\"type\":\"string\"},{\"name\":\"plan\",\"type\":\"string\"}]'\n```\n\nField types are `string`, `number`, `boolean`, `date`, `array` and `null`. Tailglow writes the transform that fills those fields from the records. Add `--hint` only for a rule it cannot read from field names, such as \"classify orders above 500 as large\". Choose the fields with care: once the transform is written, the view's fields cannot be changed in place.\n\nRetrieve the view until its `status` is `active` before telling the person the chart is ready. If it is `error`, read `error_message`. If `waiting_shapes_count` is above 0, `tglow views list-deferrals --view-id view_...` shows what it is waiting on.\n\n```bash\ntglow views retrieve --view-id view_...\n```\n\nThen build up from the view. The `events` collection holds page views and clicks as well as your own events, so filter a metric to the event it counts:\n\n```bash\ntglow metrics create --view-id view_... --name \"Signups per day\" --timestamp-field event_time \\\n --filters '?type=equals:signup' --ui-chart-value count --ui-chart-interval day\ntglow pages create --name \"Product\"\ntglow components create --page-id page_... --metric-id mtrc_...\n```\n\nRead `--help --json` on each command for the rest of its fields.\n\nPeople who would rather describe a chart in words can ask the assistant in the Tailglow dashboard.\n\n## Rules\n\n- Ask before any delete. Without a terminal attached, `delete` runs immediately and never asks.\n- Ask before changing servers or billing. Servers cost money.\n- Never print, log or commit the CLI credential. Ingest keys are fine in client-side code.\n- Send only fields that `--help --json` lists. The API rejects unknown fields.\n\n## More\n\n- The same pages without a network call: `tglow docs`, then `tglow docs <path>` for any page it lists\n- Index of the docs for agents: [docs.tailglow.io/llms.txt](https://docs.tailglow.io/llms.txt)\n- Every page in one file: [docs.tailglow.io/llms-full.txt](https://docs.tailglow.io/llms-full.txt)\n- Any docs page as markdown: add `.md`, for example [docs.tailglow.io/guides/sdk.md](https://docs.tailglow.io/guides/sdk.md)\n- Guides: [JavaScript SDK](/guides/sdk), [Sending data](/guides/sending-data), [Checks](/guides/checks), [TGL](/guides/tgl), [Metrics](/guides/metrics), [Pages](/guides/pages), [CLI](/guides/cli)\n",
|
|
40
|
-
"guides/quickstart": "# Quickstart\n\nGet a web app sending data to Tailglow. This is the typical setup: a browser app that auto-collects page views, marked-section engagement and clicks, outbound clicks, uncaught errors, and performance measurements, with your own custom events on top.\n\nYou need one value: the **ingest URL** from the source's **Ingest** tab, which already carries its key (for example `https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_your_key`). The [Ingest Key](/guides/ingest-key) guide shows how to reach it.\n\nCopying the key on its own and passing it separately still works, and a key passed that way overrides one carried in the URL.\n\n## Add the SDK\n\nChoose one. Both do exactly the same thing; use the drop-in tag if you do not have a build step.\n\n### Drop-in script\n\nPaste one tag into your `<head>`. It self-initializes and starts collecting immediately, no build step:\n\n```html\n<script\n defer\n src=\"https://cdn.tailglow.io/tglow.js\"\n data-url=\"https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_your_key\"\n></script>\n```\n\n### npm\n\n```bash\nbun add @tailglow/browser\n# or: npm install @tailglow/browser\n```\n\n```javascript\nimport { Tailglow } from \"@tailglow/browser\";\n\nconst tg = new Tailglow({\n url: \"https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_your_key\"\n});\n```\n\nEither way, auto-collection starts the moment the SDK loads: page views, engagement and clicks inside elements marked with `data-telemetry`, outbound-link clicks, uncaught errors, performance measurements, and a one-time device snapshot. The default path is cookieless and does not read or write persistent browser storage. Assess consent and disclosure requirements for the data you collect and the jurisdictions where you operate.\n\n## Track your own events\n\nSend anything meaningful in your product. The first argument is the event name, the second is any properties you want to attach:\n\n```javascript\ntg.track(\"signup\", { plan: \"pro\" });\ntg.track(\"purchase\", { amount: 99, currency: \"USD\" });\n```\n\nWith the drop-in tag, call the global `tglow` the same way:\n\n```javascript\ntglow(\"track\", \"signup\", { plan: \"pro\" });\n```\n\nOr track clicks straight from your markup, no JavaScript:\n\n```html\n<button data-tglow-event=\"signup\" data-tglow-method=\"github\">Sign up</button>\n```\n\n## Identify the signed-in user\n\nWhen your app knows who the user is (from your own auth), attach their ID. Events already queued earlier in the session are backfilled with it:\n\n```javascript\ntg.identify(\"user_123\");\n```\n\n## See it on localhost\n\nBy default the SDK skips `localhost` so dev traffic does not pollute your analytics. To watch events flow while developing, opt in and tag them as dev so you can filter them out later:\n\n```javascript\nconst tg = new Tailglow({\n url: \"https://us11.ingest.tailglow.io/prj_xxx\",\n key: \"tg_ingest_your_key\",\n excludeLocalhost: false,\n context: { environment: \"development\" }\n});\n```\n\nTrigger a page view or a `track()` call, then confirm the records land in your project.\n\n## Next steps\n\n- **[JavaScript SDK](/guides/sdk)**: the full reference, covering error capture, identity, context, configuration, and record shapes.\n- **Backend, Electron, and React Native**: the same core runs server-side and in native apps. See [Runtimes](/guides/sdk#runtimes) in the SDK guide.\n",
|
|
41
|
-
"guides/concepts": "# Concepts\n\n## Introduction\n\nTailglow separates the ingest boundary from the data boundary. The core concepts are:\n\n- **Sources**: Authentication and ingest configuration for a project.\n- **Collections**: Logical streams of records within a source. Collections own schema versions, files, and raw records.\n- **Views**: Transformed data derived from a collection or produced by joining views.\n- **Metrics**: Aggregated measurements and charts built from views.\n\n## Sources and collections\n\nA source is the place you configure ingest keys. Its **Ingest** tab displays the project's default endpoint after one has been provisioned and any available team ingest domains. If neither exists, the tab says **No ingest endpoints configured**. The project and team domains own those routing endpoints; the ingest key selects the source. A new source starts with no collections.\n\nTailglow chooses one collection for each ingest request. A valid `?collection=charges` query parameter picks it, and a request without one goes to the source's `catchall` collection. Fields inside the records, including `type`, never choose the collection. Every record in a JSON array or JSONL request goes to that collection.\n\nA source supports up to 100 collections. New collection names route to `catchall` after the source reaches that guardrail. A burst of simultaneous requests can briefly create more than 100 collections.\n\n## Records\n\nRecords are the events you send to an ingest endpoint. Tailglow accepts JSON, JSONL, CSV, TSV, PSV, SSV, and plain text. Raw collection records can be sampled as soon as they arrive, whatever the state of their schema version.\n\nJSON records need no fixed wrapper. Tailglow normalizes tabular and text input into JSON records, renames unsafe property keys, adds delivery metadata, and replaces embedded image data with artifact references.\n\n## Schema versions\n\nEvery record that reaches a collection is read for its shape: the paths it carries and the type at each path, down to the collection's depth rule. The first time a shape is seen, the collection registers a schema version for it and stamps every file holding that shape with the version. A version's number is permanent. Its canonical shape can still refine in place: a path first seen blank learns its type, and the paths a wider record carries are added once that shape has come back or arrived in bulk, so a single stray key never changes a version. A rule change can merge two versions into one, and the merged version's files move to the survivor. A record that is not a JSON object, or an empty one, has no shape to register: it is stored and kept, but it never joins a schema version and no view reads it.\n\nA version is in one of three states:\n\n- **Candidate**: the shape has been seen once. Its records are stored and served immediately by any view mapping that already fits them, but no mapping is written for it yet.\n- **Settled**: the shape was seen again after the collection's recurrence gap, arrived in one batch with at least the bulk row count, or a person settled it. Only settled versions are offered to views for authoring.\n- **Stale**: a candidate that was not seen again within the stale window. Its rows stay stored but unclassified. A stale shape that returns settles.\n\nEach view decides separately whether an existing TGL script can read a version. Changes under fields a script never reads reuse the script at once. A change from `name: string` to `name: {first, last}` needs a new decision for a view that reads the name, while a view that only reads `run_id` continues.\n\nNulls and empty values never make a new version: a version first seen with `user_id: null` refines in place once strings arrive. Absent keys follow the collection's optional keys switch. On automatic, a record whose keys are a subset of a version's keys joins that version. On manual, absence is structural, and a record missing a key is a distinct version. A mapping written while the path had no values is flagged on the view with the refined paths, and re-authoring it clears the notice. See [Schema settings](/guides/sources#schema-settings) for the rules and settings a collection carries.\n\n## Views\n\nViews reshape data with [TGL](/guides/tgl). In the **Views** page, click **New View** and choose one of the available creation modes:\n\n- **Collection**: Choose a **Source Collection**, optionally define an output schema and a generation hint, then click **Create View**. Tailglow generates transforms for the collection's settled schema versions.\n- **View**: This is a join builder, not a simple parent-derived view. Choose a base view, add one or more lookup views, and select the fields that join them.\n\nThe view's **Overview** shows how source versions flow through scripts into the output schema and linked metrics, drains, or views. Narrow panels use a connected vertical layout. More than two scripts become a counted group; open it for the script list, or open a source group to choose an individual version. Failed and processing scripts remain visible in the group's status. Counts describe the loaded data; **Version coverage** lets you load more versions.\n\nThe saved **Generation hint** is shown directly below the diagram on desktop and mobile. Change it in the view's Settings. If none is saved, the diagram says **No generation hint added.** Users who can edit the view can click the **+** beside it to open Settings and add a hint.\n\nYou can inspect transformed data on a view's **Records** tab.\n\n## Metrics\n\nMetrics aggregate data from a view. Choose a timestamp field, optional grouping, filters, and value or unique-count fields. Use metrics to chart counts, numeric values, and grouped trends, then attach monitors when you need alerts. Events with overridden timestamps that land far in the past are held for review rather than charted silently; [Late data](/guides/metrics#late-data) explains the window.\n\n## Summary\n\nSources authenticate and configure ingest. Collections own the raw data and its schema versions. Views transform or join collection data one schema version at a time. Metrics aggregate view output for analysis and monitoring.\n",
|
|
40
|
+
"guides/quickstart": "# Quickstart\n\nUsing a coding agent? Paste this prompt into Claude Code, Codex, Cursor, or Cowork:\n\n```text\nSet up Tailglow using https://tailglow.io/agents.md\n```\n\nGet a web app sending data to Tailglow. This is the typical setup: a browser app that auto-collects page views, marked-section engagement and clicks, outbound clicks, uncaught errors, and performance measurements, with your own custom events on top.\n\nYou need one value: the **ingest URL** from the source's **Ingest** tab, which already carries its key (for example `https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_your_key`). The [Ingest Key](/guides/ingest-key) guide shows how to reach it.\n\nCopying the key on its own and passing it separately still works, and a key passed that way overrides one carried in the URL.\n\n## Add the SDK\n\nChoose one. Both do exactly the same thing; use the drop-in tag if you do not have a build step.\n\n### Drop-in script\n\nPaste one tag into your `<head>`. It self-initializes and starts collecting immediately, no build step:\n\n```html\n<script\n defer\n src=\"https://cdn.tailglow.io/tglow.js\"\n data-url=\"https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_your_key\"\n></script>\n```\n\n### npm\n\n```bash\nbun add @tailglow/browser\n# or: npm install @tailglow/browser\n```\n\n```javascript\nimport { Tailglow } from \"@tailglow/browser\";\n\nconst tg = new Tailglow({\n url: \"https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_your_key\"\n});\n```\n\nEither way, auto-collection starts the moment the SDK loads: page views, engagement and clicks inside elements marked with `data-telemetry`, outbound-link clicks, uncaught errors, performance measurements, and a one-time device snapshot. The default path is cookieless and does not read or write persistent browser storage. Assess consent and disclosure requirements for the data you collect and the jurisdictions where you operate.\n\n## Track your own events\n\nSend anything meaningful in your product. The first argument is the event name, the second is any properties you want to attach:\n\n```javascript\ntg.track(\"signup\", { plan: \"pro\" });\ntg.track(\"purchase\", { amount: 99, currency: \"USD\" });\n```\n\nWith the drop-in tag, call the global `tglow` the same way:\n\n```javascript\ntglow(\"track\", \"signup\", { plan: \"pro\" });\n```\n\nOr track clicks straight from your markup, no JavaScript:\n\n```html\n<button data-tglow-event=\"signup\" data-tglow-method=\"github\">Sign up</button>\n```\n\n## Identify the signed-in user\n\nWhen your app knows who the user is (from your own auth), attach their ID. Events already queued earlier in the session are backfilled with it:\n\n```javascript\ntg.identify(\"user_123\");\n```\n\n## See it on localhost\n\nBy default the SDK skips `localhost` so dev traffic does not pollute your analytics. To watch events flow while developing, opt in and tag them as dev so you can filter them out later:\n\n```javascript\nconst tg = new Tailglow({\n url: \"https://us11.ingest.tailglow.io/prj_xxx\",\n key: \"tg_ingest_your_key\",\n excludeLocalhost: false,\n context: { environment: \"development\" }\n});\n```\n\nTrigger a page view or a `track()` call, then confirm the records land in your project.\n\n## Next steps\n\n- **[JavaScript SDK](/guides/sdk)**: the full reference, covering error capture, identity, context, configuration, and record shapes.\n- **Backend, Electron, and React Native**: the same core runs server-side and in native apps. See [Runtimes](/guides/sdk#runtimes) in the SDK guide.\n",
|
|
41
|
+
"guides/concepts": "# Concepts\n\nUsing a coding agent? Paste this prompt into Claude Code, Codex, Cursor, or Cowork:\n\n```text\nSet up Tailglow using https://tailglow.io/agents.md\n```\n\n## Introduction\n\nTailglow separates the ingest boundary from the data boundary. The core concepts are:\n\n- **Sources**: Authentication and ingest configuration for a project.\n- **Collections**: Logical streams of records within a source. Collections own schema versions, files, and raw records.\n- **Views**: Transformed data derived from a collection or produced by joining views.\n- **Metrics**: Aggregated measurements and charts built from views.\n\n## Sources and collections\n\nA source is the place you configure ingest keys. Its **Ingest** tab displays the project's default endpoint after one has been provisioned and any available team ingest domains. If neither exists, the tab says **No ingest endpoints configured**. The project and team domains own those routing endpoints; the ingest key selects the source. A new source starts with no collections.\n\nTailglow chooses one collection for each ingest request. A valid `?collection=charges` query parameter picks it, and a request without one goes to the source's `catchall` collection. Fields inside the records, including `type`, never choose the collection. Every record in a JSON array or JSONL request goes to that collection.\n\nA source supports up to 100 collections. New collection names route to `catchall` after the source reaches that guardrail. A burst of simultaneous requests can briefly create more than 100 collections.\n\n## Records\n\nRecords are the events you send to an ingest endpoint. Tailglow accepts JSON, JSONL, CSV, TSV, PSV, SSV, and plain text. Raw collection records can be sampled as soon as they arrive, whatever the state of their schema version.\n\nJSON records need no fixed wrapper. Tailglow normalizes tabular and text input into JSON records, renames unsafe property keys, adds delivery metadata, and replaces embedded image data with artifact references.\n\n## Schema versions\n\nEvery record that reaches a collection is read for its shape: the paths it carries and the type at each path, down to the collection's depth rule. The first time a shape is seen, the collection registers a schema version for it and stamps every file holding that shape with the version. A version's number is permanent. Its canonical shape can still refine in place: a path first seen blank learns its type, and the paths a wider record carries are added once that shape has come back or arrived in bulk, so a single stray key never changes a version. A rule change can merge two versions into one, and the merged version's files move to the survivor. A record that is not a JSON object, or an empty one, has no shape to register: it is stored and kept, but it never joins a schema version and no view reads it.\n\nA version is in one of three states:\n\n- **Candidate**: the shape has been seen once. Its records are stored and served immediately by any view mapping that already fits them, but no mapping is written for it yet.\n- **Settled**: the shape was seen again after the collection's recurrence gap, arrived in one batch with at least the bulk row count, or a person settled it. Only settled versions are offered to views for authoring.\n- **Stale**: a candidate that was not seen again within the stale window. Its rows stay stored but unclassified. A stale shape that returns settles.\n\nEach view decides separately whether an existing TGL script can read a version. Changes under fields a script never reads reuse the script at once. A change from `name: string` to `name: {first, last}` needs a new decision for a view that reads the name, while a view that only reads `run_id` continues.\n\nNulls and empty values never make a new version: a version first seen with `user_id: null` refines in place once strings arrive. Absent keys follow the collection's optional keys switch. On automatic, a record whose keys are a subset of a version's keys joins that version. On manual, absence is structural, and a record missing a key is a distinct version. A mapping written while the path had no values is flagged on the view with the refined paths, and re-authoring it clears the notice. See [Schema settings](/guides/sources#schema-settings) for the rules and settings a collection carries.\n\n## Views\n\nViews reshape data with [TGL](/guides/tgl). In the **Views** page, click **New View** and choose one of the available creation modes:\n\n- **Collection**: Choose a **Source Collection**, optionally define an output schema and a generation hint, then click **Create View**. Tailglow generates transforms for the collection's settled schema versions.\n- **View**: This is a join builder, not a simple parent-derived view. Choose a base view, add one or more lookup views, and select the fields that join them.\n\nThe view's **Overview** shows how source versions flow through scripts into the output schema and linked metrics, drains, or views. Narrow panels use a connected vertical layout. More than two scripts become a counted group; open it for the script list, or open a source group to choose an individual version. Failed and processing scripts remain visible in the group's status. Counts describe the loaded data; **Version coverage** lets you load more versions.\n\nThe saved **Generation hint** is shown directly below the diagram on desktop and mobile. Change it in the view's Settings. If none is saved, the diagram says **No generation hint added.** Users who can edit the view can click the **+** beside it to open Settings and add a hint.\n\nYou can inspect transformed data on a view's **Records** tab.\n\n## Metrics\n\nMetrics aggregate data from a view. Choose a timestamp field, optional grouping, filters, and value or unique-count fields. Use metrics to chart counts, numeric values, and grouped trends, then attach monitors when you need alerts. Events with overridden timestamps that land far in the past are held for review rather than charted silently; [Late data](/guides/metrics#late-data) explains the window.\n\n## Summary\n\nSources authenticate and configure ingest. Collections own the raw data and its schema versions. Views transform or join collection data one schema version at a time. Metrics aggregate view output for analysis and monitoring.\n",
|
|
42
42
|
"guides/auth": "# Authentication\n\nTailglow supports email sign-in and passwords. Each browser keeps its own session, which you can\nreview or revoke from your profile.\n\n## Email sign-in\n\nEnter your email address and Tailglow emails you a sign-in link and an 8-character code. This\npasswordless option is the default for new accounts.\n\n- The link and the code only work in the browser where you asked for them. If you open the email on\n another device, such as your phone, type the code into the browser where you started.\n- Both expire after 10 minutes and work once. Requesting a new email replaces the previous link and\n code.\n- If you open the link in another tab of the same browser, the tab where you started shows that you\n are signed in, and you can close it.\n- You can request up to 5 sign-in emails in 15 minutes, and up to 20 in a day.\n\n## Password\n\nPasswords are optional and must be at least 16 characters long. Use a password manager to generate\nand store yours.\n\nYou must enable multi-factor authentication (MFA) before adding a password. To change your password,\nopen your profile's **Security** tab and provide a valid MFA code.\n\n## Browser sessions\n\nSigning in creates a separate session for that browser. Signing in somewhere new does not sign out\nyour other browsers, and closing a browser does not end its session.\n\nOpen **Profile** > **Security**, then find **Devices** to review active sessions and sign out a\nspecific browser. You can have up to 25 active browser sessions. If you exceed that limit, Tailglow\nsigns out the least recently active session. A session you have not used for six months, in a\nbrowser or a terminal, ends on its own, and you sign in again there.\n\nClearing Tailglow site data signs out that browser locally. The session may remain listed in the\n**Devices** section until you revoke it.\n\n## CLI sessions\n\nApproving a `tglow login` in the browser gives that terminal a session of its own. It appears under\n**Devices** with a **CLI** label and the machine's name. Like a browser session, it can act in any of\nyour teams; it starts in the one you chose when you approved it. A CLI session carries the MFA\nverification of the browser session that approved it.\n\nCLI sessions are counted separately from browser sessions, up to 10 active at once; signing in from\nanother terminal beyond that ends the least recently used one. Revoking it under **Devices**,\nsigning out of every device, changing your password, resetting MFA, or running `tglow logout` in\nthat terminal ends it immediately.\n\nA CLI session cannot change how your account signs in: MFA, passwords and sessions are managed\nhere, in the browser. See [CLI](/guides/cli#authenticating).\n\n## Multi-factor authentication\n\nTailglow uses time-based one-time passwords (TOTP) for MFA. You will need an authenticator app that\ncan scan a QR code and generate six-digit codes.\n\n### Set up MFA\n\n1. Open **Profile** and select **Security**.\n2. In **Multi-factor Authentication**, open the actions menu and select **Enable MFA**.\n3. Scan the QR code with your authenticator app.\n4. Click **Confirm MFA**.\n5. Enter the six-digit code from your authenticator app and click **Submit**.\n6. Save the backup codes, then click **I've copied my backup codes**.\n\nBackup codes are shown only once. Store them somewhere secure, such as a password manager. Each code\ncan be used once, and Tailglow emails you when a backup code is used to sign in.\n\nYou can regenerate backup codes from **Profile** > **Security**. Regenerating them requires a valid\nMFA code and invalidates the previous set.\n\n### Disable MFA\n\nYou can disable MFA from **Profile** > **Security** with an authenticator code or backup code. If\npassword sign-in is enabled, disabling MFA also disables password sign-in. You can continue using\nemail sign-in. Disabling MFA also signs you out of every browser, including the one you are using.\n\n### Team-required MFA\n\nTeam owners can require MFA for all members. If a team requires MFA, Tailglow prompts members to set\nit up before they can access that team.\n\nMFA verification applies to one browser session at a time.\n\n## Sign out and security activity\n\nSigning out normally ends only the current browser session. From **Profile** > **Security** you can:\n\n- Use the **Devices** section to sign out another browser or every browser.\n- Review authentication history, including the action, time, device, and IP address.\n\n## Roles and permissions\n\nYour role in each team determines which features and actions you can access. Team owners can manage\nroles from **Team Settings** > **Roles**. See [Permissions](/guides/permissions) for how access levels\nand role presets work.\n\nYour selected team is specific to each browser, so another signed-in browser can remain on a\ndifferent team.\n",
|
|
43
43
|
"guides/servers": "# Servers\n\nServers receive and process data for a project. A source's **Ingest** tab shows the project's endpoint when one is available.\n\n## Trial and paid servers\n\nThe free trial starts one server in your team's first project and grants the team 72 server-hours of compute credit. A payment method is required to add more servers. Credits are shared across projects: two running servers use the same 72-hour balance in 36 hours. Manage Servers shows the monthly cost before you confirm a change.\n\nThe three days start when the trial server first comes up. Without a payment method, the initial server pauses after those three days. Billable usage ends at the trial deadline, including when cleanup runs late. With a card, servers keep running and use any remaining credits before paid usage begins. Unused credits do not expire. Tailglow staff can extend a trial, which adds the matching server-hours and brings back a paused trial server. See [Billing](/guides/billing#free-server-trial) for month boundaries, resume rules, and the AI and storage allowances every team has.\n\n## Manage server count\n\n1. Open **Servers** in the project sidebar.\n2. Click **Manage Servers**.\n3. Set the desired server count.\n4. Review the consequence and confirm it.\n\nScaling up uses **Add server** or **Add servers** and starts provisioning after you confirm. Scaling down, including scaling to zero, requires a hold-to-confirm action. Scaling to zero stops ingestion, so events sent while no server is running are not stored or recoverable.\n\n### Scaling during a platform update\n\nTailglow updates every server to new platform versions one project at a time, reserving the capacity each update needs before it starts. While an update is in progress, a change to the server count is queued instead of applied: Manage Servers confirms the request, the Servers page shows the queued count, and the change starts automatically when the update finishes. Adding servers to a project that has not finished moving to the current platform version is queued the same way; removing servers is not.\n\nTo cancel a queued change, select **Cancel queued change** on the Servers page, or set the count back to the number of servers the project runs. A newer request replaces an older one. A project with no servers yet is not held back; its first server starts right away. A paused trial server that resumes after you add a payment method also waits for the update to finish.\n\n## Statuses\n\nA new server starts as **provisioning** and becomes **active** after setup. Scaling down changes it to **terminating** while Tailglow drains and removes it. Scaling up later creates a new server.\n\n## Server analytics\n\nServer detail pages show resource usage for the selected server. **Project Ingest Pipeline** shows pending records, throughput, and errors across every server in the project, including temporary deployment servers. You can select a time range or brush across a chart to focus on a smaller window.\n\nThe current queue counts valid spooled records awaiting write, including records being processed. It refreshes independently of the selected chart window. When queues change during the observation, Tailglow counts the durable records it can confirm and marks the pending count with **≈**. If a current server's queue cannot be read or a concurrent write's outcome is unknown, the queue is unavailable instead of showing an uncertain total. A stale reading means the last complete or estimated snapshot is too old to confirm the current queue. The single-line summary shows processed records from the last completed minute alongside the current pending count. The error button appears when the selected chart range contains recorded errors; errors count rejected requests and dropped frames.\n\nThe summary shows a skeleton while its first request loads and keeps existing readings visible during refreshes. Delayed or unavailable measurements are labeled separately, so an available queue or processed count remains visible. A retry control appears after a failed refresh or when neither current reading is available.\n\nHistorical pending values are average queue occupancy, so they can contain fractions. Older history recorded in request frames has gaps in the pending-record series.\n\nThroughput is counted in the minute each spool batch completes. Delayed sampling or shutdown uploads preserve that minute, even when resource measurements for it are unavailable. This does not reconstruct missing historical measurements.\n\nMissing measurements appear as gaps, including when a server's contribution cannot be read. Pending, throughput, and errors can be available independently. Live polling pauses in background tabs.\n\nThe analytics API supports windows up to 48 hours with minute buckets or 30 days with hour buckets. Future end times are limited to the current time.\n\nMeasured values in the current chart bucket show **(so far)** until the bucket ends. For example, at 9:35 an hourly bucket starting at 9:00 is still in progress.\n",
|
|
44
44
|
"guides/ingest-key": "# Ingest Key\n\n## Getting Your Key\n\nYour ingest key is located in your source settings. Select a project, open **Settings**, choose the **Sources** tab, and click the edit button for a source. In the source drawer, select the **Ingest** tab. The **Ingest Keys** section lets you view and manage the source's keys. Each source can have multiple ingest keys.\n\n## Prefix\n\nYour ingest key always begins with the prefix `tg_ingest_`.\n\n## Usage\n\n### Query Parameter (Recommended)\n\nThe simplest way to authenticate is by including your ingest key as a `key` query parameter in the URL. This works across all platforms -- browsers, backends, and mobile apps.\n\n```bash\ncurl -X POST \"https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_123\" \\\n -H \"Content-Type: text/plain\" \\\n -d '{\"records\": []}' # Your records here\n```\n\nReplace the URL with your project's ingest endpoint, found in your source settings.\n\nEven though the payload is JSON, setting `Content-Type: text/plain` is safe because the ingest server auto-detects the format. For browser-based clients, this is especially important: `text/plain` is a [simple content type](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests), so browsers skip the CORS preflight OPTIONS request entirely, sending one request instead of two.\n\nThis method also works with `navigator.sendBeacon`, which does not support custom headers:\n\n```javascript\nnavigator.sendBeacon(\n \"https://us11.ingest.tailglow.io/prj_xxx?key=tg_ingest_123\",\n JSON.stringify({ event: \"page_view\", path: \"/home\" })\n);\n```\n\n### Authorization Header\n\nYou can also include your ingest key in the `Authorization` header using the `Bearer` or `Key` prefix:\n\n```bash\ncurl -X POST https://us11.ingest.tailglow.io/prj_xxx \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Key tg_ingest_123\" \\\n -d '{\"records\": []}' # Your records here\n```\n\nNote that from browsers, using a custom `Authorization` header with `Content-Type: application/json` will trigger a CORS preflight request, doubling the number of requests.\n\n## Security\n\nYour ingest key has write-only access to your ingest server. While it's important to keep this key private, it's acceptable to include it in frontend code if you're using the ingest server for frontend metrics and analytics.\n\n## Managing Your Keys\n\nEach source can have multiple ingest keys. You can create new keys and delete old ones as needed from your source settings. When you need to rotate a key, simply create a new one, update your services to use it, and then delete the old key.\n\n## Key and Team Status\n\nAn ingest key works only when its team status also permits ingestion:\n\n- **Active team**: An active key on an active team works normally.\n- **Delinquent team**: For a non-Enterprise team, an uncollectible or later-unpaid invoice can make the team delinquent. Active ingest keys are then disabled until the payment issue is resolved. A single failed payment attempt does not by itself apply this state.\n- **Restricted team**: The team is read-only and ingestion is disabled until the restriction is resolved.\n- **Blocked team**: The team cannot be accessed and ingestion is disabled. Contact support to resolve this.\n\nAn ingest request is accepted only when both the key and its team are active. If the team becomes `delinquent`, `restricted`, or `blocked`, the key stops accepting data. Update your payment information or contact support as appropriate to restore access.\n",
|
|
@@ -50,10 +50,10 @@ export const DOCS_PAGES = {
|
|
|
50
50
|
"guides/sources": "# Sources\n\nSources organize the data sent to a project. Each source has its own ingest keys and settings, and contains collections of related records.\n\n## Create and configure a source\n\n1. Open project **Settings** and select **Sources**.\n2. Click the plus button.\n3. Enter a name and click **Create Source**.\n4. In the source drawer, open **Ingest** to copy an ingest endpoint or manage keys.\n\nCreating a source does not guarantee that a key was created. Use **Add Key** in the Ingest tab if you need one.\n\n## Collections and schema detection\n\nSources begin with no collections. Tailglow chooses one collection for each ingest request: the `?collection=` value when it is valid, otherwise the source's `catchall` collection. Fields inside the records, including `type`, never choose the collection. All records in a JSON array or JSONL request use that collection.\n\nCollection names are normalized before they are matched, so `?collection=Charges`, `?collection=CHARGES`, and `?collection=charges` all resolve to the same `charges` collection. Normalizing trims the value, lowercases it, replaces every character outside `a-z`, `0-9`, `_`, and `-` with a hyphen, collapses repeated hyphens, and truncates to 64 characters. A value with no letters or digits left after that is ignored rather than rejected, and the request goes to `catchall`. The collection's `name` field carries its display casing.\n\nA source supports up to 100 collections. New collection names route to `catchall` after the source reaches that guardrail. A burst of simultaneous requests can briefly create more than 100 collections.\n\nEach collection detail page lists schema versions with their state (candidate, settled, or stale), rows, files, and last-seen time. Use the eye button to inspect a version's fields and types. **Version details** shows why it settled and its dates; the drawer also shows whether a field has appeared as null, empty, or absent. Choose **Settle version** in the drawer when you trust a candidate or stale shape and do not want to wait for it to recur. Settling is one way: a settled version never goes back to candidate. [Concepts](/guides/concepts#schema-versions) explains the states. The aggregate **Schema Tree** remains on the collection page to compare fields across versions.\n\nWith **Automatic** detection, Tailglow groups changing keys when existing mappings can keep working. It leaves a proposal unchanged when a mapping reads inside that path, or when a mapping's reads cannot be determined. There is no approval task in Automatic mode. With **Manual** detection, the **Schema proposals** card shows suggested groups. Its eye button opens the proposed changes, including the remaining shapes and tracked child paths. Choose **Group these keys** to apply it or **Leave unchanged** to close it. The drawer warns when an existing mapping may need updating. These changes require `sources:write` access.\n\n## Schema settings\n\nThe collection's **Overview** includes a **Schema recognition** summary. Its edit button, or the gear in the page header, opens the settings drawer. **Recognition**, **Settlement**, and **Limits** group the options; choose **Save changes** to apply edits. Closing the drawer discards unsaved edits. Rules and settings are separate on purpose. Changing a rule re-keys the collection: every version and unclassified shape is re-signed under the new rules, and shapes that now match are merged into one version, which keeps its files and mappings. Before a rule change lands, a confirmation shows how many shapes the collection holds now and how many it would hold under the new rules. Changing a setting re-keys nothing.\n\nRules:\n\n- **Grouped paths** keep arbitrary children out of schema identity while every raw value is still stored. Enter one path per line, such as `/metadata`. Escape a literal slash in a key as `~1` and a literal tilde as `~0`.\n- **Tracked child paths** restore tracking for selected children of an opaque path, such as `/metadata/customer_id`.\n- **Maximum depth** sets how many levels of nesting take part in identity. The default is 5. A mapping can read five levels deep, so raising this past 5 records deeper shapes without making them readable.\n- **Nulls and absent values**, **Optional keys**, and **Detection** are each **Automatic** or **Manual**. Automatic accepts null and absent values at any path a mapping reads, lets a record whose keys are a subset of a version's keys join that version, and applies a safe opaque-path proposal from the detector on its own. Manual waits for a person at each of those points.\n\nSettings:\n\n- **Recurrence gap**: how long after first sight a shape must be seen again before its version settles. The default is 60 minutes.\n- **Bulk rows**: a single batch carrying at least this many rows of a shape settles its version at once. The default is 100, and 0 turns the bulk path off.\n- **Stale after**: how long a candidate waits to be seen again before it goes stale. The default is 24 hours.\n- **Settled versions limit**, **Candidate pool**, and **Alias pool** bound how many settled versions, candidate and stale versions, and cached signatures a collection may hold. Past a bound, new shapes stay unclassified with their signatures kept.\n- **Files per batch**: how many collection files one ingest batch may open at once for this collection. A batch with more versions than that is written in more than one pass, so every shape with a version still lands in a file of its own version. Shapes past the candidate pool get the same number of files per batch, the shapes with the most rows first, so they can be linked to a version once the pool has room; the rows of shapes past that budget are kept in the batch's unclassified file and cannot be linked to a version later.\n- **Unclassified retention**: how many days the files of shapes that never settled are kept before they are deleted. That covers unclassified files and the files of stale versions, and a stale version with nothing left under it is deleted with them, together with the pairings views held on it. Leave it empty to keep them forever.\n\nFor a version a view cannot map automatically, you can supply a hint, provide a script, or exclude that version from a particular view with a recorded reason.\n\nAn ingest key's recent-use time indicates that requests reached the source. It does not mean every view and chart has finished processing them.\n\n## Deleting a source\n\nUse the hold-to-delete control in the source drawer to schedule deletion. The source remains visible\nwith a line-through name and a **Deleting** chip. Hover the chip or open the source drawer to see how\nlong remains before permanent removal. Tailglow schedules removal for seven days later.\n\nBefore that date, open the source drawer and click **Restore Source** to cancel deletion. The source\nkeeps accepting data for the whole window, so a restored source carries on with nothing missing.\n\nThe same drawer offers **Delete Permanently** if you would rather not wait. It asks you to type the\nsource name, destroys every collection and record under it immediately, and cannot be undone.\n\nPermanent removal deletes the source, its collections and data, and any views built from those\ncollections. Existing results from joins that used one of those views remain. If the removed view\nwas the join's main input, the join stops receiving new records; if it was a lookup, new records are\nno longer enriched by it. Reconfigure or remove affected joins before permanent removal.\n",
|
|
51
51
|
"guides/records": "# Views\n\nViews reshape data from a collection or combine existing views. Use them to prepare data for analysis, charts, and monitors. See [Sources](/guides/sources) for how incoming data is organized into collections.\n\n## Create a view\n\n1. Open **Views** and click **New View**.\n2. In the **Collection** tab, select a **Source Collection**.\n3. Enter a **View Name**. You can also add an **Output Schema** or **Generation Hint**.\n4. Click **Create View**.\n\nTo combine existing views instead, use the **View** tab and choose a base view with one or more lookup views.\n\n## Inspect a view\n\nOpen a view and select **Records** to inspect its transformed output.\n\n## View status\n\nThe status chip can show **Empty**, **Preparing**, **Indexing**, **Needs TGL**, **Active**, or\n**Error**. **Preparing** and **Indexing** mean Tailglow is building the view. **Needs TGL** means the\nview needs a transform script for a schema version. **Error** means generation or indexing needs\nattention.\n",
|
|
52
52
|
"guides/metrics": "# Metrics & Monitors\n\nMetrics aggregate a view's data into charts. A metric needs a name, view, and timestamp field. Grouping and filters are optional.\n\n## Create a metric\n\n1. Open **Metrics** and click **New Metric**.\n2. Choose a view and timestamp field. The UI selects the first eligible date or string output field by default.\n3. Optionally choose **Group By**, a numeric value field, a unique-count field, and filters.\n4. Click **Create Metric**.\n\nTailglow backfills existing view data, then updates the metric from new data. The status indicates whether it is initializing, waiting for transforms, backfilling, active, in error, or cancelled.\n\n## Partial results and waiting shapes\n\nCharts show available results with a compact coverage control in the header. Open it to see whether records need mapping or recovery work, or completeness is simply unverified. Unverified historical or time coverage can remain even when the source view has no pending shapes and its scripts are live. An amber region on Cartesian charts marks incomplete results with known mapping, recovery, or exclusion reasons; empty ranges show a no-data state. Trends and forecasts use verified complete buckets; they are unavailable when coverage is unknown or too little complete data remains. Monitors and storage sealing continue to use the verified boundary.\n\nUse **Inspect view**, then **Inspect** on a waiting shape, to see missing fields, changed types, possible substitutions, and the fields each candidate TGL reads. The drawer lists the shape's fields and types, using full paths for nested fields. **Activity details** shows its state, dates, source, stored rows, and schema observations. Pending shapes also show the next automatic check or requested hint. A small deferral-evidence count is not the total number of ingested records.\n\nExcluding a version records a decision for that view and version, including future matching records. It does not erase existing materialized output. Charts retain an exclusion notice; other views make independent decisions.\n\nWaiting for a schema mapping does not create a new late-data decision. Catch-up preserves original admission. Sealed history is rebuilt by re-backfilling the metric: a change to its data feed, or the re-backfill action, wipes its rollups and rebuilds them from the retained rows. Adding rows to the hot tier is not enough.\n\n## Metric settings\n\nMetric settings are organized into four tabs. **Display** covers display value, time range, chart type, value unit, empty-interval handling, the maximum series shown, Compact Values, which rounds tooltip readings to compact notation such as 321.3M and is on by default, and Y Axis Min and Max, which pin either end of the y axis on line, area, and bar charts. Leave a bound empty and that side keeps fitting the data. **Analysis** covers trend lines, forecasting, and alert markers. **Colors** covers how series are pinned and painted. The metric's name is edited in the settings header. Tailglow chooses the interval from the selected time range.\n\nThe **Query** tab lets you change the view, timestamp field, grouping, values, and filters. Changing it rebuilds the metric.\n\nTailglow supports line, area, bar, pie, scatter, radar, stat, gauge, calendar, and uptime charts. Trend lines and forecasts are available for line, area, and bar charts. Alert markers are available for line, area, bar, pie, radar, and calendar charts. With trend lines on, the tooltip reports each bucket's distance from the trend, green above and red below, and a single-series chart adds the overall growth per period. A line whose direction the data does not support carries a small warning triangle instead.\n\nCalendar and uptime charts render time itself, so they read best over day-sized buckets. A calendar shades one cell per day (per hour when the range is a week or less) by value, and a metric with several series sums them into one calendar. An uptime strip colors one cell per bucket using the value rules from the **Colors** tab; a metric with several series draws one strip per series plus an Overall row carrying the worst value of any series in each bucket. An uptime chart in By Value mode with no rules yet offers a one-click preset: 99.9 and above green, 99 and above amber, anything lower red. Click any cell on either chart to zoom into that period.\n\n## Forecasting\n\nA forecast extends a metric's best fit line past your latest data, so the chart shows where the metric is heading.\n\nTurn it on from the **Analysis** tab of the metric's settings with **Show Forecast**. **Default Horizon** sets how far ahead the chart looks: Next 7 days, Next 30 days, Next 90 days, End of quarter, End of year, or Next year. Relative horizons resolve when the chart is viewed, so a metric set to Next 30 days always looks 30 days past the day you open it. **Model** sets the shape of the projection. Auto follows whichever model fits the current data best, or you can pin Linear, Exponential, Logarithmic, Logistic, or Sinusoidal. Either way, the list marks the model that currently fits your data best.\n\nEvery line, area, and bar chart in the app carries a forecast control in its header, whether or not the metric has forecasting saved. Use it to change the horizon or the model for your own view. The metric's saved settings do not change, and neither does what anyone else sees. **Use metric default** returns you to the saved settings, and your choice lasts only for the current visit.\n\nThe projected part of the chart is drawn dashed, with a shaded band around it covering the range of likely values. The band is always shown with a forecast. In the tooltip, projected buckets add labeled columns: the estimate appears as a small chip on a track, with the bucket's bounds beside it. A bucket that is still filling in shows the value recorded so far under Now, with Est. and Max for the projected landing and ceiling; a fully projected bucket shows Min and Max around the estimate. Buckets that are fully measured keep the plain tooltip.\n\nWhen a fitted model explains little of a series' history, or its first projection leaps far from recent values, that series carries a small warning triangle in the tooltip: the forecast still draws, but read it as low confidence.\n\nForecasts need a line, area, or bar chart. On a stacked bar chart every visible series gets its own fit, and the projected buckets draw as hatched ghost bars stacked in the same order as the bars. The tooltip reports each series' projection plus a Total row. On a stacked line or area chart the forecast projects the stack's total.\n\n## Late data\n\nMost data arrives in order and charts simply fill forward. Data sent live is never late: an event without a timestamp of its own is stamped on arrival and always charts automatically.\n\nOnly events with an overridden timestamp, such as the SDK's `event_time` field, can land in the past. This is normal when importing history, replaying a queue, or batching in an integration. How far back such an event may reach and still enter charts automatically is your project's late data window.\n\n### The late data window\n\nOpen project **Settings**, then find the **Late Data** card under General. The window is a number of minutes, counted back from the newest data already in your charts:\n\n- **Within the window**, a late event folds into charts automatically. Nothing to do.\n- **Beyond the window**, the event is stored and counted, but stays out of charts until you fold it in with a re-backfill.\n\nThe default is 120 minutes, which covers most integrations. Set it to 0 to hold everything that arrives behind your charts: each such event is surfaced instead of charted. The maximum is 129,600 minutes (90 days).\n\nThe window is a promise about charts, not about storage. Late events are kept in your views either way; only their aggregation waits. Records, exports, and drains are unaffected.\n\nChanging the window applies to events that arrive after the change. Events already held under the old window stay in **Late Data Waiting** until you re-backfill them.\n\n### Re-backfilling late data\n\nWhen events are held out of charts, a **Late Data Waiting** card appears in the project's general settings. Each row is one batch: the view it belongs to, how many events were held, and the event-time range they span.\n\nClick **Re-backfill** to fold a batch in. The row shows **Queued**, and the events appear in charts as the fold completes, back to the earliest event in the batch.\n\nVery large batches can appear in this list even when their events are inside the window. Folding a batch that touches many hours of history is expensive on a busy metric, so a batch beyond roughly a hundred thousand events, or one spread across more than 48 distinct hours, waits for the same one-click confirmation instead of folding silently. The window itself never changes on its own: only you decide what enters charts automatically.\n\n### Choosing a window\n\nThe default suits the common pattern of backfilling history first and then running live. If your integrations regularly submit data that is hours or days old, widen the window so those events chart without manual steps. If you report on recent data and want it locked quickly, narrow the window or set it to 0 and treat **Late Data Waiting** as your review queue.\n\n## Monitors\n\nOpen a metric and use its monitor control to manage monitors. Click the add icon to create one. A metric can have up to 15 monitors.\n\nThe drawer lets you choose threshold, sustained, existence, or no-data conditions, along with the evaluation window, frequency, alert length, occurrence threshold, expiry, series filters, and preview. A spanning alert remains open until it clears; an instant alert opens and ends at once.\n\n**Existence** fires when the selected metric value is greater than zero. To detect that a feed has stopped, choose **No data**, then set an evaluation window such as **10 minutes**. This condition counts observations across the whole window, even if the chart displays missing values as gaps. A measured value of zero still counts as data. Evaluation waits until the window is complete; a processing delay or unknown completeness does not itself mean data is missing.\n\nUse **Total** when the whole feed must go quiet, **Any series** when any known series going quiet should open one alert, or **Each series** for a separate alert per quiet series. Series filters narrow the observations being watched. A metric with no known series can trigger a total no-data alert; per-series scopes need a known series to watch.\n\nEvaluation windows have presets and a **Custom** duration in whole minutes. **All time** looks back to the metric's creation and is unavailable for no-data conditions. The frequency controls how often the rule is checked; it is separate from the window.\n\nThe no-data preview checks the current window with the same rules as the evaluator. If the window is incomplete it reports that no verdict is available. It does not simulate scheduling or occurrence thresholds.\n\nA blinking red indicator means an alert is currently open. Ended alerts remain shaded on the chart as history, with their original condition and end time, but do not keep the indicator blinking.\n",
|
|
53
|
-
"guides/pages": "# Pages\n\n## Overview\n\nPages are customizable dashboards that display one or more metrics in a single view. They refresh automatically while you are viewing a live time range and can be shared publicly or served through a custom domain.\n\n## Creating a Page\n\nTo create a new page:\n\n1. Navigate to the **Pages** section in the sidebar.\n2. Click the **\"New Page\"** button.\n3. Configure the following fields:\n - **Name**: Enter a descriptive name for your page, like \"Quarterly Dashboard\" or \"Performance\".\n - **Time Range**: This synchronizes the time range of all metrics on the page.\n - **Data Interval**: Choose the interval used to group the data. It defaults to **Day**.
|
|
53
|
+
"guides/pages": "# Pages\n\n## Overview\n\nPages are customizable dashboards that display one or more metrics in a single view. They refresh automatically while you are viewing a live time range and can be shared publicly or served through a custom domain.\n\n## Creating a Page\n\nTo create a new page:\n\n1. Navigate to the **Pages** section in the sidebar.\n2. Click the **\"New Page\"** button.\n3. Configure the following fields:\n - **Name**: Enter a descriptive name for your page, like \"Quarterly Dashboard\" or \"Performance\".\n - **Time Range**: This synchronizes the time range of all metrics on the page.\n - **Data Interval**: Choose the interval used to group the data. It defaults to **Day**. Intervals that do not suit the time range are disabled. A custom range disables intervals too fine for its length, such as **Minute** across a month.\n4. Click **Add Page** to save and generate your new dashboard.\n\n## Time ranges and live updates\n\nUse the range picker to choose **Last hour**, **Last 7 days**, **Last 30 days**, **Last 90 days**, **Last year**, **All time**, or a custom selection. A page can also have a different saved default range, which the picker lists alongside these.\n\nRelative ranges, including All time, follow the present. Charts refresh at the interval they display: minute buckets refresh each minute and hourly buckets each hour. If you brush into history or choose fixed custom dates, that selection stays still and automatic refresh pauses. Choose a relative range again to follow new data.\n\nThe status page created during onboarding starts with Last hour and one-minute intervals. Its first charts may take a few minutes to prepare. The page is initially private: **Open your status page** opens the internal page, and **Publish page** takes you to Sharing settings. Turn on **Publish page** and save when you want others to see it.\n\nRecent intervals can still be processing. Uptime and calendar cells show those intervals in gray;\nhover them to see **Processing**. Line, area, and bar charts shade the unfinished edge. Older gaps\nremain **No data**. Pie and radar timelines show completed snapshots, and refresh pauses while you\nscrub through older snapshots. Stats and gauges keep completed values visible while the next\nresults are processing.\n\n## Adding Metrics\n\nOnce your page is created:\n\n1. Click the **Settings** button on your page.\n2. Navigate to the **Layout** tab.\n3. Click the **Add Metric** button. You'll see a dropdown of all of your available metrics.\n4. Select the metrics you want to display on the page.\n\nEach metric keeps its own chart settings on the page, including the trend lines and forecast saved on its **Analysis** tab. When forecasts are enabled on both the page and the metric, public line, area, and bar charts show a forecast button. Visitors can change the horizon and model, hide the forecast, or return to the metric default. These choices affect only their current view; they do not change the saved settings.\n\n## Layout & Sizing\n\nYou can customize the layout of your page by rearranging and resizing metric cards:\n\n- **Drag and Drop**: Drag metrics to rearrange their order on the page.\n- **Card Sizing**: Toggle each metric between full-width and half-width. Use full-width for metrics that need more space (like detailed line charts) and half-width to display two metrics side by side.\n\n## Visibility & Sharing\n\nPages can be shared publicly so that anyone with the link can view them, without requiring authentication.\n\n- **Public**: Toggle public visibility from the page settings. When enabled, the page is accessible via a public URL.\n- **Access Code**: Optionally require a shared access code. Visitors enter it once per browser. Changing or clearing the code requires visitors to enter it again.\n- **Show Trend Lines On Public Page**: Show or hide trend lines across the public page. Enabled by default; each metric must also have trend lines enabled.\n- **Show Forecasts On Public Page**: Show or hide forecasts and their controls across the public page. Enabled by default; each metric must also have forecasting enabled.\n- **Show Alerts On Public Page**: Enable this setting to show monitor alert markers on the public page. Alerts are hidden by default and must also be enabled on the underlying metric.\n- **Custom Domain**: You can serve pages through your own branded hostname (e.g., `status.yourcompany.com`). Configure a Page domain once in the **Domains** section of team settings. Every public page owned by the team is available from that hostname at its permanent generated slug. You can also give a page a readable path such as `/weekly-health` independently on each custom domain, or set that path to `/` so the page opens at the hostname root.\n\n## Best Practices\n\n- **Use pages to monitor key metrics**: Pages are a great way to keep an eye on the most important metrics for your team or project.\n- **Share pages with stakeholders**: Public pages let stakeholders stay informed without needing a Tailglow account.\n- **Customize pages for different teams**: Create different pages for different teams to focus on the metrics that matter most to them.\n- **Use card sizing strategically**: Put high-level summary metrics (stat, gauge) at half-width and detailed time-series charts (line, area) at full-width for the best readability.\n",
|
|
54
54
|
"guides/custom-domains": "# Custom Domains\n\n## Overview\n\nCustom domains allow you to serve your Tailglow pages and ingest data through your own branded domain instead of the default Tailglow URLs. For example, instead of using `pages.tailglow.io/your-page`, you can use `analytics.yourdomain.com/your-page`.\n\nYou can use custom domains for:\n\n- **Custom Pages**: Serve your public status pages and dashboards from your own domain.\n- **Custom Ingest**: Send analytics data through your own domain for first-party tracking.\n\nCustom domains are available on the Enterprise plan.\nA team can have up to 10 custom domains in total across Ingest and Page domains.\n\nEach hostname has one traffic purpose. You can configure several Ingest domains and several Page domains for the same team.\n\n## Adding a Custom Domain\n\nTo add a custom domain to your team:\n\n1. Navigate to your **Team Settings** page.\n2. Click the **\"Domains\"** tab.\n3. In either the **Ingest domains** or **Page domains** table, click the add button.\n4. Enter your custom domain (e.g., `analytics.yourdomain.com`).\n5. Click **Add Ingest Domain** or **Add Page Domain**, matching the table you chose.\n\nThe table you add the hostname from determines its purpose. After adding it, verify domain ownership and configure routing through a two-step DNS process.\n\n## DNS Verification\n\nSetting up a custom domain is a two-step process: first verify ownership, then configure traffic routing.\n\n### Step 1: Ownership Verification (TXT Record)\n\nBefore your domain can be used, you must verify that you own it by adding a TXT record:\n\n1. Go to your DNS provider's management console.\n2. Add a TXT record:\n - **Name/Host**: The full name shown in Tailglow. For `analytics.yourdomain.com`, it is `_tailglow-verify.analytics.yourdomain.com`.\n - **Value**: The verification token shown in your domain settings\n3. Wait for DNS propagation. Timing depends on your provider and the record's TTL.\n4. Click **Check DNS** in the drawer, or **Check Now** beside the domain status, to check the TXT record.\n\n### Step 2: Routing Setup\n\nOnce ownership is verified, configure routing so traffic reaches Tailglow:\n\n**CNAME Record**\n\nA domain that serves traffic has to be a subdomain such as `analytics.yourdomain.com`. A root domain\nlike `yourdomain.com` cannot hold a CNAME, so it can be verified but not used for ingest or pages.\n\n1. Go to your DNS provider's management console.\n2. Add a CNAME record:\n - **Name/Host**: The name shown in your domain settings. Your provider may display the same host relative to the DNS zone, such as `analytics`.\n - **Value/Target**: The CNAME target shown in your domain settings.\n3. Wait for DNS propagation.\n4. Click **Check DNS** to check the routing configuration.\n\nIf your DNS provider offers proxying, use **DNS only** while setting up the domain. The hostname must\npoint directly to the target shown in Tailglow so its certificate can be provisioned and renewed. A\nproxied record can leave certificate provisioning stuck or failed.\n\n### Ongoing Checks\n\nTailglow re-checks every record hourly, not just during setup. Both records have to keep resolving\nfor the domain to keep working.\n\nIf a record stops resolving, the domain keeps serving while you fix it:\n\n- **Ownership (TXT)**: 24 hours\n- **Routing (CNAME)**: 7 days\n\nPut the record back within that window and nothing is interrupted. Past it, Tailglow stops serving\nthe domain until the record resolves again. Adding it back and clicking **Check DNS** restores\nservice on the next successful check.\n\nA check that cannot reach your DNS provider at all does not count against these windows. Only a\nlookup that completes and finds the record missing or pointing somewhere else starts the clock.\n\n## SSL Certificate Provisioning\n\nOnce your domain is verified and routing is configured, Tailglow requests an SSL certificate. The certificate status can report:\n\n- **None**: No certificate has been requested yet (pre-verification).\n- **Provisioning**: Certificate provisioning is in progress after routing verification.\n- **Active**: The certificate is provisioned and your domain is ready for HTTPS traffic.\n- **Failed**: Certificate provisioning failed. Check the error message in your domain settings for details.\n\nCertificate provisioning often takes a few minutes, but it can take longer. Certificates renew\nautomatically while the required DNS records continue to point directly to Tailglow.\n\n## Traffic Purpose\n\nThe table used to create a domain assigns its traffic purpose, and one hostname can serve only one\npurpose at a time. In **Domain Details**, the **Used for** chip shows the active purpose.\n\nAn Ingest domain can be used as a first-party endpoint for sending analytics data. It keeps the ingest hostname under your domain and may reduce blocking by tools that specifically target known third-party analytics hosts.\n\nA Page domain serves every public page owned by the team. Each Page's permanent generated slug works on the Tailglow hostname and every custom hostname. A Page can also be given a readable path on one specific domain, such as `status.yourcompany.com/weekly-health`, and another Page domain can choose a different path for the same Page.\n\nSet that path to `/` to serve the Page at the hostname's root, so `status.yourcompany.com` opens it directly. Only one Page can hold a given path on a domain, including the root. Paths are a single segment: `/weekly-health` is valid, `/team/weekly-health` is not.\n\nPages served from your own hostname remove Tailglow branding and use your team logo as the browser tab icon (favicon). You can update the logo in your team settings.\n\nTo remove the active purpose without deleting the hostname, open **Domain Details** and click **Stop\nusing for Ingest** or **Stop using for Pages**. Tailglow explains what the change affects and asks\nyou to hold the confirm button, because the teardown cannot be cancelled once it starts. The change\ncan take time to finish.\n\n## Troubleshooting\n\n### DNS Verification Failed\n\nIf verification fails:\n\n- **Check DNS propagation**: Use a tool like [whatsmydns.net](https://www.whatsmydns.net) to verify your records have propagated.\n- **Verify record values**: Ensure the CNAME target exactly matches what Tailglow shows.\n- **Check for conflicting records**: Remove any conflicting A or AAAA records if using CNAME.\n- **Wait and retry**: Propagation time depends on your DNS provider and the record's TTL.\n\n### Certificate Provisioning Stuck\n\nIf certificate status shows \"provisioning\" for more than 24 hours:\n\n- **Verify DNS is correct**: Certificate provisioning requires DNS to be properly configured.\n- **Check for CAA records**: If you have CAA records, ensure they allow certificate issuance.\n- **Contact support**: If issues persist, reach out to support@tailglow.io.\n\n### Domain Shows \"Failed\"\n\nIf certificate provisioning failed:\n\n- Check the error message in your domain settings.\n- Verify DNS configuration is still correct.\n- Try removing and re-adding the domain.\n\n## Removing a Custom Domain\n\nTo remove a custom domain:\n\n1. Navigate to **Team Settings** > **Domains**.\n2. Click the edit button for the domain you want to remove.\n3. Hold the trash button labeled **Hold to remove domain** until the action confirms.\n\nRemoval can take time, and the row remains visible with a pending state until it finishes. Public Pages stop using the hostname as soon as removal begins, while other services may take longer. Do not rely on the hostname after starting removal. Adding it again later requires new ownership verification.\n\n## Best Practices\n\n- **Use dedicated subdomains**: Hostnames such as `ingest.yourdomain.com` and `status.yourdomain.com` make each traffic purpose explicit. Page domains require a subdomain.\n- **Keep DNS records**: Don't remove DNS records after verification. They are re-checked hourly, and removing one eventually takes the domain out of service. See [Ongoing Checks](#ongoing-checks).\n- **Monitor certificate status**: Check your domain status periodically to ensure certificates remain active.\n- **Plan for propagation**: When adding new domains, allow time for DNS propagation before relying on them in production.\n",
|
|
55
55
|
"guides/api-keys": "# API Keys\n\n## Overview\n\nAPI keys allow you to authenticate with the Tailglow API programmatically, without a user session. They are useful for backend integrations, CI/CD pipelines, and scripts that need to interact with Tailglow on behalf of your team.\n\nAPI keys are separate from **ingest keys**. Ingest keys are used to send data to your ingest servers, while API keys are used to interact with the Tailglow management API (e.g., creating projects, managing views, querying metrics).\n\n## Creating an API Key\n\nTo create a new API key:\n\n1. Navigate to your **Team Settings**.\n2. Select the **API Keys** tab.\n3. Click the plus button labeled **Create an API key**.\n4. Configure the following fields:\n - **Name**: A descriptive name for the key (2-60 characters).\n - **Quick fill**: Optionally start with **Common** or **Full access**, or click **Clear** to remove all selected scopes.\n - **Scopes**: Choose which permissions the key should have. You can only assign permissions that you yourself have.\n5. Click **Create API Key**.\n\nThe API key will be displayed once after creation. Copy and store it securely -- you will not be able to see it again.\n\n## Using an API Key\n\nInclude the API key in the `Authorization` header of your requests:\n\n```bash\ncurl -X GET \"https://api.tailglow.io/v1/projects\" \\\n -H \"Authorization: Bearer tg_api_your_api_key\"\n```\n\nYou can also create a key from the terminal with `tglow login --method api-key`: approve it in the browser, choose its permissions, and the CLI stores it. See [CLI](/guides/cli#authenticating).\n\n## Key Properties\n\n- **last4**: The last 4 characters of the key, for identification.\n- **scopes**: The permissions assigned to the key.\n- **last_used_at**: When the key last authenticated a request, accurate to five minutes.\n\n## Managing API Keys\n\nFrom the **API Keys** tab in your team settings, you can:\n\n- **View all keys**: See each key's name, last 4 characters, and when it was last used. A key created before Tailglow began recording use shows **No recorded use** until its next request.\n- **Update a key**: Change the name or scopes of an existing key.\n- **Delete a key**: Permanently revoke a key. This takes effect immediately.\n\n## Security\n\n- API keys inherit the scopes you assign to them. Follow the principle of least privilege -- only grant the permissions the key needs.\n- Quick-fill choices only preselect scopes in the creation form. Review them before saving and remove any\n permission the integration does not need.\n- The available quick-fill choices are **Common** and **Full access**. **Clear** removes all scopes.\n- Unlike human roles, API keys do not require `projects:read`. A key may be limited to one narrow\n operation.\n- Rotate keys periodically by creating a new key, updating your integrations, and deleting the old one.\n- Never commit API keys to version control. Use environment variables or a secrets manager.\n",
|
|
56
|
-
"guides/billing": "# Billing\n\n## Overview\n\nBilling in Tailglow is managed at the team level. Each team has a billing plan, payment methods, and access to invoices and credit notes. Billing settings are accessible from the **Team Settings** page under the **Billing** tab.\n\n## Free server trial\n\nAn eligible new account can start one server in its team's first project without a payment method. The three-day trial starts when that server first comes up, so time spent waiting for it to start does not count against the trial. Creating another project or team does not renew your account's trial.\n\nThe trial grants the team **72 server-hours** of credit: one running server uses one server-hour each hour. Two servers share the same balance and use two server-hours each hour. Server-hours are shared across all projects on the team and stay available until spent. Tailglow staff can extend a trial; an extension adds the matching server-hours and brings back a trial server that was paused.\n\n## Included allowances\n\nEvery team has these allowances, whether or not it ran a trial:\n\n- **Tailglow AI tokens:** a new team starts with 10 million tokens for the Tailglow-provided model. Input, output and cached tokens count; reasoning tokens are included in output tokens, not counted twice. The balance resets each month once your latest invoice is paid, as described below. Your own provider keys are billed by that provider and do not use these tokens.\n- **5 GB-months of storage every month:** each monthly invoice includes the first 5 GB-months of storage on every plan. This is a usage allowance, not a 5 GB capacity limit: holding 5 GB for a full month uses 5 GB-months. Included storage that goes unused does not carry over.\n\nThe meters at the top of the current month's estimate on the billing page show how much of your included storage and Tailglow AI allowance you have used, including usage not yet invoiced. A meter turns red once that allowance is used up. Reading it spends nothing: allowances are deducted when usage is invoiced, and the current estimate already includes them. Tailglow staff can refill your AI tokens to your plan's monthly allowance.\n\n### Trial deadline and payment methods\n\nWithout a payment method, the initial server pauses when the three-day trial ends. Billable server time stops at that deadline, even if infrastructure cleanup runs later. Adding a card after the deadline does not make that cleanup gap billable. You have seven more days to add a payment method and resume the paused server; after that, the server is removed.\n\nWith a payment method, servers can keep running after the trial deadline and you can add capacity in any project. All running servers draw from the shared server-hour credit. Once it is spent, usage is charged at your team's normal rate. Manage Servers shows the selected monthly cost. If you have permission to read billing, it also estimates how much longer the team's available server credit will last at the selected server count.\n\nThe server deadline and the credit balances are separate. The deadline controls when a cardless server pauses; it does not expire unused AI or server credits. For the Tailglow-provided model, Pro teams without a payment method need remaining AI credits before each model call. Calls already in progress may finish as credits run out; any excess is covered by Tailglow and will not appear as a later charge. Teams with a payment method, and Enterprise teams, keep using the model after their balance runs out: remaining credits are deducted when invoicing and further usage is billed on the next invoice. Your own provider key bypasses this allowance check entirely.\n\n### Credits across billing months\n\nAll server runtime is metered, and eligible promotional units are deducted centrally when the invoice is created. A trial crossing a month boundary shares one 72-server-hour balance across both months. Each month's credited hours use that month's prorated server rate. Estimates do not spend credits, and retrying an invoice does not spend them again. Included storage comes off first, then promotional credits, then other billing discounts. The invoice shows full metered usage, **Included storage**, a separate **Promotional credit**, any negotiated **Discount**, and the amount due.\n\nWhen your team's latest monthly invoice is paid, **Enterprise teams receive a reset to 100 million Tailglow AI tokens**, including teams that pay by invoice without a saved payment method. **Pro teams with a payment method receive a reset to 10 million tokens**. This is included, not a prepaid purchase or an additional charge. Unused AI credits, including higher balances, are replaced by the monthly allowance. Usage since the end of the invoiced month counts against it, so paying late never makes usage free. Paying an older invoice after a newer one is issued does not reset the balance, and paying the same invoice again does not reset it twice. Pro teams without a payment method keep their remaining tokens but receive no monthly reset.\n\nSelect **Tailglow** in Chat to use these tokens. The information drawer in Agents settings shows current rates, including the 15% service fee. All teams share one saved Tailglow rate for each billing month. Customer price changes take effect in the next month, even if provider costs or the underlying model change sooner; invoices use the saved rate for the month of usage. Ordinary input, cached input, cache writes, and output are billed separately; reasoning is already included in output. Automatic managed conversation compaction is covered by Tailglow.\n\n## Billing Settings\n\nTo access billing:\n\n1. Click your team name in the top toolbar.\n2. Click the settings icon in the top-right corner.\n3. Navigate to the **Billing** tab.\n\nFrom here you can manage:\n\n- **Billing Email**: Set a billing-specific email address for invoices and payment notifications.\n- **Billing Plan**: View your current billing plan.\n- **Billing Address**: Set your team's billing address for invoices.\n\n## Estimated Invoice\n\nA card named for the current period lists each billable line with the usage behind it, and an **Estimated Total** row that sums them and shows the date the period is invoiced.\n\nEach amount reads as `$2.60 (Est $43.35)`. The first figure is what the line has accrued so far. The figure in brackets is what it becomes by the time the invoice is issued, assuming the month carries on as it has been:\n\n- **Storage** keeps the data you are holding now and keeps growing at the rate measured over the last two weeks. A line with no recent growth is carried forward flat, and deleting data never projects the bill downward.\n- **Compute** keeps the servers that are running today running for the rest of the period. Scaling up or down moves this figure immediately.\n- **AI tokens** continue at the rate used so far this period. In the first day of a period there is not enough usage to project from, so the line reports what it has.\n- **Subscription** shows a single figure. It is committed for the whole period, so there is nothing to project.\n\nThree things to know when reading it:\n\n- The bracketed figure is a forecast, not a measurement. It assumes today's usage continues, so it moves whenever your storage or servers change. The figure on the left is the only one that is a fact.\n- Usage is measured from the hours it was actually held, which is why storage is billed in GB-months rather than the gigabytes you are storing right now. Two days of storage in a thirty day month bills for two days.\n- Both figures are shown before tax. Any applicable tax is calculated when the invoice is issued, so a team in a taxable jurisdiction pays more than the estimate shows.\n\n### Billing through the API\n\n`GET /v1/teams/:team_id/billing` returns the current billing period, accrued and projected charges, server usage, the server-hour and AI credit balances, and your included storage. Every response has the same fields. It requires `billing:read` and credentials scoped to that team.\n\nThe response has `object: \"billing\"`. Its totals show accrued charges. `projected_line_items`, `projected_subtotal_cents`, `projected_discount_cents`, and `projected_total_cents` estimate this period's invoice.\n\nThe `promo_*` fields report the stored balances after the last invoice. The `available_promo_*` fields subtract accrued, uninvoiced usage. Use the available balances when showing how much credit is left to use. `included_storage_gb_months` is your plan's monthly storage allowance and `available_included_storage_gb_months` is what this month has not used yet. `calculated_at` identifies the snapshot time; `uninvoiced_server_hours` can include the preceding month until its invoice is saved. Reading the summary never creates an invoice or deducts credits.\n\n## Payment Methods\n\nYou can add and manage payment methods from the billing settings.\n\n### Adding a Payment Method\n\n1. Click the plus button in the **Payment Methods** card.\n2. In **Add New Payment Method**, enter the cardholder name, card details, and billing address.\n3. Click **Add Payment Method**.\n\nThe first payment method added becomes the default. You can change the default at any time.\n\n### Managing Payment Methods\n\n- **Set as default**: Open the payment method's actions menu and select **Set As Default**.\n- **Delete**: Open the actions menu and select **Delete Payment Method**. You cannot delete the last payment method on a team, regardless of its plan.\n\n## Invoices\n\nThe **Invoices** card shows each invoice's number, period end date, total, and status. Use the link button to open an available hosted invoice in a new tab.\n\n## Credit Notes\n\nCredits are granted by Tailglow staff and applied automatically when an invoice is paid, so no action is needed to redeem one. They are not deducted from the estimate, which reports usage rather than what is left to pay. Contact support to check a balance.\n\n## Discounts\n\nActive discounts are applied automatically when each invoice is calculated.\n\n## Billing Activity\n\nThe **Billing activity** card lists what changed your team's billing over the last 90 days: invoices being issued, paid, failing or voided, payment methods being added, changed or removed, plan changes, the free trial starting, being extended, pausing and ending, Tailglow AI resets and refills, discounts, and ingestion being paused or resumed for an unpaid invoice. Each entry shows who made the change. **Tailglow** marks changes made automatically, such as a payment arriving from your card, and **Tailglow Admin** marks changes made by Tailglow staff. It appears once there is activity to show.\n\nAnyone with billing read access can see this list, and it is available through `GET /v1/logs?type=billing`. Your invoices and receipts remain the permanent record of what you paid.\n\n## Account Status and Billing\n\nYour team's billing status directly affects your account:\n\n- **Active**: Everything is working normally.\n- **Delinquent**: Tailglow emails your billing contact after a failed payment. A non-Enterprise team can become delinquent if an invoice becomes uncollectible or remains unpaid into a later billing cycle. Ingestion is paused until every unpaid invoice is paid or voided, and then resumes automatically.\n- **Restricted**: Project and team resources are read-only, and ingestion is paused. Billing and payment-method updates remain available so an Owner can resolve the issue, but payment methods cannot be deleted while the team is restricted.\n\nSee the [Teams](/guides/teams) guide for more details on team statuses.\n",
|
|
56
|
+
"guides/billing": "# Billing\n\n## Overview\n\nBilling in Tailglow is managed at the team level. Each team has a billing plan, payment methods, and access to invoices and credit notes. Billing settings are accessible from the **Team Settings** page under the **Billing** tab.\n\n## Free server trial\n\nAn eligible new account can start one server in its team's first project without a payment method. The three-day trial starts when that server first comes up, so time spent waiting for it to start does not count against the trial. Creating another project or team does not renew your account's trial.\n\nThe trial grants the team **72 server-hours** of credit: one running server uses one server-hour each hour. Two servers share the same balance and use two server-hours each hour. Server-hours are shared across all projects on the team and stay available until spent. Tailglow staff can extend a trial; an extension adds the matching server-hours and brings back a trial server that was paused.\n\n## Included allowances\n\nEvery team has these allowances, whether or not it ran a trial:\n\n- **Tailglow AI tokens:** a new team starts with 10 million tokens for the Tailglow-provided model. Input, output and cached tokens count; reasoning tokens are included in output tokens, not counted twice. The balance resets each month once your latest invoice is paid, as described below. Your own provider keys are billed by that provider and do not use these tokens.\n- **5 GB-months of storage every month:** each monthly invoice includes the first 5 GB-months of storage on every plan. This is a usage allowance, not a 5 GB capacity limit: holding 5 GB for a full month uses 5 GB-months. Included storage that goes unused does not carry over.\n\nThe meters at the top of the current month's estimate on the billing page show how much of your included storage and Tailglow AI allowance you have used, including usage not yet invoiced. A meter turns red once that allowance is used up. Reading it spends nothing: allowances are deducted when usage is invoiced, and the current estimate already includes them. Tailglow staff can refill your AI tokens to your plan's monthly allowance.\n\n### Trial deadline and payment methods\n\nWithout a payment method, the initial server pauses when the three-day trial ends. Billable server time stops at that deadline, even if infrastructure cleanup runs later. Adding a card after the deadline does not make that cleanup gap billable. You have seven more days to add a payment method and resume the paused server; after that, the server is removed.\n\nWith a payment method, servers can keep running after the trial deadline and you can add capacity in any project. All running servers draw from the shared server-hour credit. Once it is spent, usage is charged at your team's normal rate. Manage Servers shows the selected monthly cost. If you have permission to read billing, it also estimates how much longer the team's available server credit will last at the selected server count.\n\nThe server deadline and the credit balances are separate. The deadline controls when a cardless server pauses; it does not expire unused AI or server credits. For the Tailglow-provided model, Pro teams without a payment method need remaining AI credits before each model call. Calls already in progress may finish as credits run out; any excess is covered by Tailglow and will not appear as a later charge. Teams with a payment method, and Enterprise teams, keep using the model after their balance runs out: remaining credits are deducted when invoicing and further usage is billed on the next invoice. Your own provider key bypasses this allowance check entirely.\n\n### Credits across billing months\n\nAll server runtime is metered, and eligible promotional units are deducted centrally when the invoice is created. A trial crossing a month boundary shares one 72-server-hour balance across both months. Each month's credited hours use that month's prorated server rate. Estimates do not spend credits, and retrying an invoice does not spend them again. Included storage comes off first, then promotional credits, then other billing discounts. Each invoice line shows the usage and rate behind its charge, for example **Compute (2.229 server-months at $99.00)**. Under a line come any **Included storage**, a separate **Promotional credit**, and any negotiated discount, followed by the amount due.\n\nWhen your team's latest monthly invoice is paid, **Enterprise teams receive a reset to 100 million Tailglow AI tokens**, including teams that pay by invoice without a saved payment method. **Pro teams with a payment method receive a reset to 10 million tokens**. This is included, not a prepaid purchase or an additional charge. Unused AI credits, including higher balances, are replaced by the monthly allowance. Usage since the end of the invoiced month counts against it, so paying late never makes usage free. Paying an older invoice after a newer one is issued does not reset the balance, and paying the same invoice again does not reset it twice. Pro teams without a payment method keep their remaining tokens but receive no monthly reset.\n\nSelect **Tailglow** in Chat to use these tokens. The information drawer in Agents settings shows current rates, including the 15% service fee. All teams share one saved Tailglow rate for each billing month. Customer price changes take effect in the next month, even if provider costs or the underlying model change sooner; invoices use the saved rate for the month of usage. Ordinary input, cached input, cache writes, and output are billed separately; reasoning is already included in output. Automatic managed conversation compaction is covered by Tailglow.\n\n## Billing Settings\n\nTo access billing:\n\n1. Click your team name in the top toolbar.\n2. Click the settings icon in the top-right corner.\n3. Navigate to the **Billing** tab.\n\nFrom here you can manage:\n\n- **Billing Email**: Set a billing-specific email address for invoices and payment notifications.\n- **Billing Plan**: View your current billing plan.\n- **Billing Address**: Set your team's billing address for invoices.\n\n## Estimated Invoice\n\nA card named for the current period lists each billable line with the usage behind it, and an **Estimated Total** row that sums them and shows the date the period is invoiced.\n\nEach amount reads as `$2.60 (Est $43.35)`. The first figure is what the line has accrued so far. The figure in brackets is what it becomes by the time the invoice is issued, assuming the month carries on as it has been:\n\n- **Storage** keeps the data you are holding now and keeps growing at the rate measured over the last two weeks. A line with no recent growth is carried forward flat, and deleting data never projects the bill downward.\n- **Compute** keeps the servers that are running today running for the rest of the period. Scaling up or down moves this figure immediately.\n- **AI tokens** continue at the rate used so far this period. In the first day of a period there is not enough usage to project from, so the line reports what it has.\n- **Subscription** shows a single figure. It is committed for the whole period, so there is nothing to project.\n\nThree things to know when reading it:\n\n- The bracketed figure is a forecast, not a measurement. It assumes today's usage continues, so it moves whenever your storage or servers change. The figure on the left is the only one that is a fact.\n- Usage is measured from the hours it was actually held, which is why storage is billed in GB-months rather than the gigabytes you are storing right now. Two days of storage in a thirty day month bills for two days.\n- Both figures are shown before tax. Any applicable tax is calculated when the invoice is issued, so a team in a taxable jurisdiction pays more than the estimate shows.\n\n### Billing through the API\n\n`GET /v1/teams/:team_id/billing` returns the current billing period, accrued and projected charges, server usage, the server-hour and AI credit balances, and your included storage. Every response has the same fields. It requires `billing:read` and credentials scoped to that team.\n\nThe response has `object: \"billing\"`. Its totals show accrued charges. `projected_line_items`, `projected_subtotal_cents`, `projected_discount_cents`, and `projected_total_cents` estimate this period's invoice.\n\nThe `promo_*` fields report the stored balances after the last invoice. The `available_promo_*` fields subtract accrued, uninvoiced usage. Use the available balances when showing how much credit is left to use. `included_storage_gb_months` is your plan's monthly storage allowance and `available_included_storage_gb_months` is what this month has not used yet. `calculated_at` identifies the snapshot time; `uninvoiced_server_hours` can include the preceding month until its invoice is saved. Reading the summary never creates an invoice or deducts credits.\n\n## Payment Methods\n\nYou can add and manage payment methods from the billing settings.\n\n### Adding a Payment Method\n\n1. Click the plus button in the **Payment Methods** card.\n2. In **Add New Payment Method**, enter the cardholder name, card details, and billing address.\n3. Click **Add Payment Method**.\n\nThe first payment method added becomes the default. You can change the default at any time.\n\n### Managing Payment Methods\n\n- **Set as default**: Open the payment method's actions menu and select **Set As Default**.\n- **Delete**: Open the actions menu and select **Delete Payment Method**. You cannot delete the last payment method on a team, regardless of its plan.\n\n## Invoices\n\nThe **Invoices** card shows each invoice's number, the month it bills, its total, and its status. Use the link button to open an available hosted invoice in a new tab.\n\n## Credit Notes\n\nCredits are granted by Tailglow staff and applied automatically when an invoice is paid, so no action is needed to redeem one. They are not deducted from the estimate, which reports usage rather than what is left to pay. Contact support to check a balance.\n\n## Discounts\n\nActive discounts are applied automatically when each invoice is calculated. A discount appears on the invoice under the line it reduces. A percentage discount is named by its percentage, for example **65% discount ($650.00 off)**, and a fixed-amount discount appears as **Discount** with the amount taken off.\n\n## Billing Activity\n\nThe **Billing activity** card lists what changed your team's billing over the last 90 days: invoices being issued, paid, failing or voided, payment methods being added, changed or removed, plan changes, the free trial starting, being extended, pausing and ending, Tailglow AI resets and refills, discounts, and ingestion being paused or resumed for an unpaid invoice. Each entry shows who made the change. **Tailglow** marks changes made automatically, such as a payment arriving from your card, and **Tailglow Admin** marks changes made by Tailglow staff. It appears once there is activity to show.\n\nAnyone with billing read access can see this list, and it is available through `GET /v1/logs?type=billing`. Your invoices and receipts remain the permanent record of what you paid.\n\n## Account Status and Billing\n\nYour team's billing status directly affects your account:\n\n- **Active**: Everything is working normally.\n- **Delinquent**: Tailglow emails your billing contact after a failed payment. A non-Enterprise team can become delinquent if an invoice becomes uncollectible or remains unpaid into a later billing cycle. Ingestion is paused until every unpaid invoice is paid or voided, and then resumes automatically.\n- **Restricted**: Project and team resources are read-only, and ingestion is paused. Billing and payment-method updates remain available so an Owner can resolve the issue, but payment methods cannot be deleted while the team is restricted.\n\nSee the [Teams](/guides/teams) guide for more details on team statuses.\n",
|
|
57
57
|
"guides/changelog": "# Changelog\n\nNo changelog entries yet.\n",
|
|
58
58
|
"guides/chat": "# Chat\n\n## Overview\n\nChat is an AI assistant built into Tailglow. Ask questions about your metrics, views, collections, and records in natural language, across any project on your team. Chat uses your account's permissions for supported Tailglow actions, so it can only see and change what you can.\n\n## Getting started\n\n**Tailglow** uses AI managed by Tailglow. When several models are available, use the model picker to choose one. Click the model and effort label to change your selection. When only one model is available, this opens its effort choices directly. Free tokens are shared across your team and its projects. They apply first, then usage is billed to the team if it has a payment method or is on the Enterprise plan.\n\nTo use your own provider account, add an Anthropic or OpenAI key in **Team Settings > Agents**, then select that provider's model in Chat. Adding or deleting a key does not switch an existing conversation's payer. If its selected model becomes unavailable, choose another model explicitly. A new chat opens on your saved model when it's available; otherwise it opens on Tailglow or another model you can use, and your saved choice returns once it's available again.\n\nThe **Tailglow** name stays the same when the underlying model changes. Open the information drawer in Agents settings, or click the free-token balance beside the chat picker, to see the current provider, model, supported token rates, and balance. The displayed rates include a 15% service fee; no additional service fee is added afterward.\n\nEvery new team starts with 10 million Tailglow AI tokens. When your team's latest monthly invoice is paid, **Enterprise teams receive a reset to 100 million tokens**, including teams that pay by invoice without a saved payment method. **Pro teams with a payment method receive a reset to 10 million tokens**. This is included, not a prepaid purchase or an additional charge. Unused AI credits, including higher balances, are replaced by the monthly allowance. Usage since the end of the invoiced month counts against it. Pro teams without a payment method keep their remaining tokens but receive no monthly reset.\n\nChat history remains accessible when sending needs action. Without a model, or without enough credits on a Pro team with no payment method, the composer explains what to change. Your draft is kept. Provider-key models do not spend Tailglow credits. Teams with a payment method, and Enterprise teams, can continue after their free balance reaches zero; that usage is billed.\n\n## Using Chat\n\nOpen Chat with **Command + /** or the chat icon. It is available on every page: inside a project, and on team pages such as billing, users, and roles.\n\n1. Open Chat.\n2. Type a question and press Enter.\n\nChat reads the page you are on, so a question about \"this view\" or \"this metric\" means the one in front of you. If you move to a different project mid-conversation, Chat notices and keeps going. When that makes a request ambiguous, it asks rather than guessing.\n\nResponses stream as they are generated. You can attach images to a message.\n\n### Example questions\n\n- \"What are the top 5 endpoints by error rate this week?\"\n- \"Show me the trend of page views over the last 30 days.\"\n- \"Why did latency spike yesterday at 3pm?\"\n- \"Create the same metric in my sandbox project.\"\n- \"Which roles can delete metrics?\"\n\n## Chat history\n\nOpen the chat panel and switch to the chat list to revisit previous conversations. Your chats follow you between projects, so the list is the same wherever you open it.\n\nThe assistant keeps useful notes as it works. For long managed conversations, it also summarizes older work near a 250,000-token context target. Recent exchanges, complete tool calls and results, and your current request remain available alongside that summary. The full transcript remains in your chat history.\n\nCompaction runs between model calls. If a summary fails, the assistant retains the original context and uses additional headroom toward a 400,000-token operating limit, subject to the provider's capacity. It tries to reduce older context before asking you to shorten an unusually large request. Automatic managed compaction is covered by Tailglow and does not spend your free tokens.\n\nToken billing uses the provider's reported usage for every generation, including tool continuations. Input, cached input, cache writes, and output are separate categories; reasoning is already included in output. An estimate used to fit context is never used to calculate your bill. Stopping a response can leave a provider generation finishing in the background; its reported usage still counts. If usage cannot be recovered, the unknown remainder is covered by Tailglow, while the answer already received remains available.\n\n## Managing chats\n\nDeleting a chat removes it from your chat list. Its messages may be retained for billing records.\n\nChats are scoped to your user account within a team. Other team members cannot see them.\n\n## Permissions\n\nChat access is controlled by the **Chats** permission in your role. Read access opens chat history, write access sends messages, and delete access removes chats.\n",
|
|
59
59
|
"guides/checks": "# Checks\n\nA check fetches an endpoint on a schedule and records whether it answered. It is the fastest thing in Tailglow to set up, and the only one that needs nothing else to exist first: give it a URL and you have real data within a minute, without instrumenting anything or sending us a single event.\n\nUse a check when you want to know that something is up. Use a [pull](/guides/pulls) when you want what the endpoint returns.\n\n## Set up a check\n\nPick a source for the results to land in, name the check, and give it the HTTPS URL to watch. Everything else has a working default.\n\n**Schedule** decides how often it runs, down to once a minute. **Collection** decides where the results land inside the source, and you name it yourself, because which collection a check writes to is the thing that decides what it charts alongside.\n\nPoint several checks at the same collection and they chart together: one strip per check, with an overall row above them taking the worst result of any check in each bucket. That is a status page. Give each check its own collection to keep them apart.\n\n**Test check** on the Activity tab fetches the endpoint once, right now, and tells you what came back. It stores nothing, so use it freely while you get the configuration right.\n\n## What gets recorded\n\nEvery run writes exactly one row, whether it succeeded or not:\n\n| Field | Meaning |\n| ------------------ | -------------------------------------------------------------------- |\n| `check_id` | Which check ran |\n| `checked_at` | The scheduled time of the run, not the moment the row was written |\n| `up` | `1` when the endpoint answered with a 2xx, `0` when it did not |\n| `http_status` | The status it answered with, or `0` when nothing answered |\n| `response_time_ms` | How long the request took |\n| `error_stage` | `request` when nothing answered, `response` when it answered wrongly |\n| `error_message` | What went wrong |\n| `attempts` | `2` when a failure was confirmed by a second check |\n\nBecause `up` is a number, the average of `up` across a period is the uptime for that period, as a fraction between 0 and 1. A view that multiplies it by 100 first reads as a percentage instead, which is the form thresholds are written in: the Colors tab's uptime preset expects `99` and `99.9`, not `0.99` and `0.999`. Views built by setup carry that field already, as `uptime_percent`.\n\nA run that was never dispatched writes no row at all. Tailglow did not ask the endpoint anything, so it makes no claim about whether it was up, and the gap renders as no data rather than as downtime.\n\n## Confirming failures\n\n**Confirm failures after** waits the given number of seconds and checks once more before recording a failure. A single refused connection is a flake as often as an outage, and without this one blip becomes a red cell on your chart. When the second attempt succeeds, the run is recorded as up.\n\n## Charting uptime\n\n1. Create a view over the checks collection.\n2. Create a metric on that view: display value **Average**, over the `up` field. Group by `check_id` if several checks share the collection.\n3. Set the chart type to **Uptime**, colour mode **By value**, and use the uptime preset in the Colors tab. Rules are read worst-first, so red comes before amber before green.\n4. Add a monitor with a **sustained** condition if you want an alert. The chart shows every bucket; the monitor decides what counts as an outage worth waking someone for.\n\n## A check never stops itself\n\nThis is the difference between a check and a pull. A pull that has been failing for 72 hours is switched off, because a collector with nothing to collect is only wasting requests. A check keeps going for as long as the outage lasts, because documenting that outage is the entire job. Your chart stays honest through the worst week you have.\n\nA check stops only when you pause it.\n\n## Limits\n\nEach request times out after 20 seconds, and a confirmation attempt after 10. A POST request body is capped at 4 KB.\n\n## Security\n\nEndpoints must be public HTTPS addresses. Tailglow re-validates the address on every single run, not just when you save the check, and rejects private, loopback and link-local targets. That re-validation is what stops a hostname that resolved publicly at save time from being repointed at an internal address later.\n\nHeaders are stored encrypted and never returned. Responses list only the header names you configured.\n",
|