@tailglow/cli 0.6.0 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailglow/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "description": "The Tailglow command line. Every command is generated from the Tailglow API reference, so the CLI and the documentation can never describe different APIs.",
6
6
  "bin": {
@@ -14,7 +14,7 @@ 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",
@@ -37,8 +37,8 @@ export const DOCS_PAGES = {
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",