# Pages

## Page Model

### Fields

- **`object`** `"page"`

- **`id`** `string`

   Unique identifier, prefixed with `pag_`.

- **`team_id`** `string`

- **`project_id`** `string`

- **`name`** `string`

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

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

- **`is_public`** `boolean`

   Whether the page is reachable by anyone holding its URL.

- **`public_url`** `string`

   Where the page is served once published. The address resolves only while `is_public` is true.

- **`slug`** `string`

   The random segment of the public URL. Changes whenever the URL is refreshed.

- **`access_code`** `string | null`

   Code a visitor must enter to view the public page; null when none is set.

- **`components`** [`Component[]`](/api/components#model)

   The components displayed on the page, in the order they appear.

- **`time_range`** [`AnalyticsTimeRange`](/api/pages#analytics-time-range)

   Window every chart on the page covers.

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

   Start of the window, when the time range is custom.

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

   End of the window, when the time range is custom.

- **`interval`** [`ChartInterval`](/api/pages#chart-interval)

   Bucket size the charts use.

- **`public_ui_show_alerts`** `boolean`

   Whether alert markers are drawn on the charts of the public page.

- **`public_ui_show_trends`** `boolean`

   Whether trend lines enabled on each metric are shown publicly. Defaults to true.

- **`public_ui_show_forecast`** `boolean`

   Whether forecasts enabled on each metric are shown publicly. Defaults to true.

- **`domain_routes`** [`PageDomainRoute[]`](/api/pages#domain-route-model)

   The custom domains this page is published on.

### Referenced Types

#### ISODateString

`ISODateString`

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

#### ComponentCardSize

`"1/1" | "1/2"`

#### MetricStatus

`"initializing" | "waiting_for_transforms" | "backfilling" | "active" | "error" | "cancelled"`

#### NullHandling

`"skip" | "count_as_zero"`

#### EmptyBucketHandling

`"zero" | "gaps"`

#### BackfillStrategy

`"newest_first" | "oldest_first"`

#### ChartFamily

`"cartesian" | "radial" | "geographic" | "temporal" | "hierarchical"`

#### ChartType

`"line" | "area" | "bar" | "pie" | "scatter" | "radar" | "stat" | "gauge" | "calendar" | "uptime"`

#### ChartColorMode

`"by_series" | "by_intensity" | "by_value"`

#### ChartColor

`"blue" | "red" | "amber" | "green" | "teal" | "purple" | "pink"`

#### ChartCurve

`"linear" | "step" | "smooth"`

#### ForecastHorizon

`"next_7_days" | "next_30_days" | "next_90_days" | "end_of_quarter" | "end_of_year" | "next_year"`

How far past now a chart projects its forecast. Values are relative to the moment the chart is
viewed, so a saved horizon keeps projecting the same distance ahead as time passes rather than
expiring on a fixed date. `end_of_quarter` and `end_of_year` run to the end of the calendar
period that contains today, in UTC.

#### ForecastModel

`"auto" | "linear" | "exponential" | "logarithmic" | "logistic" | "sinusoidal"`

The curve a forecast is fitted with. `auto` follows the best-fitting model for the series, which
is recalculated as data arrives and can therefore change between views; naming a model pins the
projection to that curve.

#### MetricChartValue

`"count" | "average" | "sum" | "min" | "max" | "last" | "cumulative_sum" | "cumulative_count" | "p50" | "p95" | "p99" | "count_unique"`

Aggregation applied to a metric's values. Use `last` when records are snapshots of a persistent
thing such as a deal, user, or inventory item: each time bucket contains the latest reading for
that series in the bucket. Within the requested range, the last observed value carries forward
across complete empty intervals instead of reading them as zero.

#### AnalyticsTimeRange

`"last_hour" | "last_6_hours" | "today" | "last_24_hours" | "yesterday" | "this_week" | "last_week" | "this_month" | "last_month" | "this_quarter" | "last_quarter" | "this_year" | "last_year" | "last_7_days" | "last_30_days" | "last_90_days" | "last_365_days" | "all_time" | "custom" | "next_7_days" | "next_30_days" | "next_90_days"`

The window a chart reads. Stored as a plain string rather than a database enum: the set is
presentation, not something any query filters on, and the forward ranges in particular are
expected to change as we learn what people forecast over.

A `next_*` range ends after now, which is what turns a fitted trend into a visible forecast. The
measured half of such a range is still measured; only the part past now is projected.

#### ChartInterval

`"minute" | "hour" | "day" | "month" | "auto"`

The Data Interval _setting_ (`Metric.ui_chart_interval` and the aggregation `interval` query
param). `"auto"` means the server picks the finest-safe tier for the current view, so brush-zoom
naturally drills into a finer bucket. The RESOLVED tier returned by aggregation is always a plain
`DataInterval`.

## Page Domain Route Model

### Fields

- **`object`** `"page_domain_route"`

- **`id`** `string`

   Unique identifier, prefixed with `pdr_`.

- **`domain_id`** `string`

- **`page_id`** `string`

- **`domain`** `string`

   The domain name itself.

- **`pathname`** `string`

   Path the page is served at on that domain, leading slash included. `/` is the domain root.

- **`public_url`** `string`

   The full address the page is reachable at.

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

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

### Referenced Types

#### ISODateString

`ISODateString`

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

## List Pages

### Endpoint

List a project's pages.

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

**Scope:** `pages:read`

### Path Parameters

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

### Query Parameters

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

- **`team_id`** `string`
  Return only pages in this team. Optional.

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

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

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

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

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

### Response

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

### Comments

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

## Retrieve Page

### Endpoint

Retrieve a single page.

```http
GET /v1/projects/:project_id/pages/:page_id
```

**Scope:** `pages:read`

### Path Parameters

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

- **`page_id`** `string` -- **Required**
  Unique identifier of the page.

### Response

Page retrieved

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

### Comments

- Includes every component on the page and the custom domains it is published to.

## Create Page

### Endpoint

Create a page that displays a set of metrics.

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

**Scope:** `pages:write`

### Path Parameters

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

### Request Body

- **`name`** `string` -- **Required**
  Display name for the page. Minimum length: `1`. Maximum length: `64`.

- **`is_public`** `boolean`
  Whether the page is reachable by anyone holding its URL. Optional.

- **`public_ui_show_alerts`** `boolean`
  Whether alert markers are drawn on the charts of the public page. Optional.

- **`public_ui_show_trends`** `boolean`
  Whether trend lines enabled on each metric are shown on the public page. Defaults to true. Optional.

- **`public_ui_show_forecast`** `boolean`
  Whether forecasts enabled on each metric are shown on the public page. Defaults to true. Optional.

- **`access_code`** `string | null`
  Code a visitor must enter to view the public page. Send null for no code. Optional.

- **`time_range`** [`AnalyticsTimeRange`](/api/pages#analytics-time-range)
  Window every chart on the page covers. Optional.

- **`interval`** [`ChartInterval`](/api/pages#chart-interval)
  Bucket size the charts use. Set to auto to pick one from the time range. Optional.

- **`custom_range_start_at`** [`ISODateString`](/api/pages#iso-date-string)
  Start of the window, when the time range is custom. Optional.

- **`custom_range_end_at`** [`ISODateString`](/api/pages#iso-date-string)
  End of the window, when the time range is custom. Optional.

### Response

Your page has been created

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

### Comments

- A page is private until `is_public` is set. Publishing one generates the slug that forms its public URL.
- A `custom` time range requires both `custom_range_start_at` and `custom_range_end_at`, at least 30 minutes apart, with the start before the end.
- An `access_code` is trimmed, and an empty one is stored as no code at all.

## Update Page

### Endpoint

Change a page's name, time range, or public access.

```http
POST /v1/projects/:project_id/pages/:page_id
```

**Scope:** `pages:write`

### Path Parameters

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

- **`page_id`** `string` -- **Required**
  Unique identifier of the page.

### Request Body

- **`name`** `string`
  Display name for the page. Optional. Minimum length: `1`. Maximum length: `64`.

- **`is_public`** `boolean`
  Whether the page is reachable by anyone holding its URL. Optional.

- **`public_ui_show_alerts`** `boolean`
  Whether alert markers are drawn on the charts of the public page. Optional.

- **`public_ui_show_trends`** `boolean`
  Whether trend lines enabled on each metric are shown on the public page. Defaults to true. Optional.

- **`public_ui_show_forecast`** `boolean`
  Whether forecasts enabled on each metric are shown on the public page. Defaults to true. Optional.

- **`access_code`** `string | null`
  Code a visitor must enter to view the public page. Send null for no code. Optional.

- **`time_range`** [`AnalyticsTimeRange`](/api/pages#analytics-time-range)
  Window every chart on the page covers. Optional.

- **`interval`** [`ChartInterval`](/api/pages#chart-interval)
  Bucket size the charts use. Set to auto to pick one from the time range. Optional.

- **`custom_range_start_at`** [`ISODateString`](/api/pages#iso-date-string)
  Start of the window, when the time range is custom. Optional.

- **`custom_range_end_at`** [`ISODateString`](/api/pages#iso-date-string)
  End of the window, when the time range is custom. Optional.

### Response

Your page has been updated

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

### Comments

- Send only the fields you are changing. Anything omitted keeps its current value.
- Making a page private clears its public URL. Making it public again issues a new slug, so any previously shared link stops working.
- A `custom` time range requires both `custom_range_start_at` and `custom_range_end_at`, at least 30 minutes apart, with the start before the end. The rule is checked against the page as it will be after the update, not against the fields you send.
- An `access_code` is trimmed, and an empty one clears the code.

## Add Page Domain

### Endpoint

Publish a page on one of your verified domains.

```http
POST /v1/projects/:project_id/pages/:page_id/domains/:domain_id
```

**Scopes:** `pages:write` + `domains:read`

### Path Parameters

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

- **`page_id`** `string` -- **Required**
  Unique identifier of the page.

- **`domain_id`** `string` -- **Required**
  Unique identifier of the domain.

### Request Body

- **`pathname`** `string` -- **Required**
  Path this page is served from on one custom Page domain. `/` serves the root.

### Response 201

Custom Page URL saved

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

### Response 200

Custom Page URL saved

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

### Comments

- The domain has to be verified before a page can be served on it.
- Sending this again for the same domain moves the page to the new pathname rather than creating a second route.
- Send `/` to serve the page at the domain root.
- `pathname` is trimmed, lowercased, given a leading slash and stripped of any trailing slash before it is stored, so `Status`, `/status` and `status/` all resolve to the same path rather than becoming separate routes.
- `pathname` is either `/`, which serves the domain root, or a single segment of lowercase letters, numbers and single hyphens. Nested paths such as `/team/status` are rejected.
- A small set of paths is reserved by Tailglow Pages and cannot be used.
- Only one page can hold a given path on a domain, including `/`.
- A stored `pathname` is at most 64 characters. The limit is applied after normalization, so the leading slash counts toward it even when you leave it off, while surrounding whitespace and a trailing slash do not.
- A `pathname` longer than 256 characters is rejected before any of that, counting whatever you send including whitespace.
- An empty or blank `pathname` is rejected. Delete the route to stop serving the page on a domain.

## Remove Page Domain

### Endpoint

Stop serving a page on one of your domains.

```http
DELETE /v1/projects/:project_id/pages/:page_id/domains/:domain_id
```

**Scopes:** `pages:write` + `domains:read`

### Path Parameters

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

- **`page_id`** `string` -- **Required**
  Unique identifier of the page.

- **`domain_id`** `string` -- **Required**
  Unique identifier of the domain.

### Response

Custom Page URL removed

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

### Comments

- The page stays reachable on its Tailglow URL if it is still public.

## Refresh Page URL

### Endpoint

Issue a new public URL for a page.

```http
POST /v1/projects/:project_id/pages/:page_id/refresh
```

**Scope:** `pages:write`

### Path Parameters

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

- **`page_id`** `string` -- **Required**
  Unique identifier of the page.

### Response

Your page slug has been updated

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

### Comments

- Use this when a shared link should stop working. The old URL returns a 404 immediately.

## Delete Page

### Endpoint

Delete a page.

```http
DELETE /v1/projects/:project_id/pages/:page_id
```

**Scope:** `pages:delete`

### Path Parameters

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

- **`page_id`** `string` -- **Required**
  Unique identifier of the page.

### Response

Your page has been deleted.

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

### Comments

- The metrics the page displayed are not affected.

