# Projects

## Project Model

### Fields

- **`object`** `"project"`

- **`id`** `string`

   Project identifier prefixed with `prj_`.

- **`team_id`** `string`

- **`team_name`** `string`

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

   This team's trial lifecycle window; promotional units are shared across its projects.

- **`name`** `string`

- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)

   Metrics collection begins at this time.

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

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

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

   Scheduled deletion time; `null` when deletion is not scheduled.

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

   Most recent authenticated ingest time; `null` if none has occurred.

- **`late_data_window_minutes`** `number`

   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. 0 means the watermark is final as it advances. Data sent live is never affected.

- **`server_endpoint`** `string | null`

   Project-specific ingest endpoint; `null` while unavailable.

- **`queued_server_count`** `number | null`

   Server count a scale request asked for that is waiting to be applied, because a platform update
   is in progress or the project has not finished updating to the current platform version. `null`
   when nothing is queued.

- **`storage_gb`** `number`

   Stored data for this project, in gigabytes of uncompressed data: raw ingested files and
   artifacts, plus view output and minute-level rollups. Recalculated hourly; `billing_updated_at`
   is the last refresh.

- **`billing_period_server_months`** `number`

   Server-months this project's servers have accrued in the current calendar-month billing period,
   counted up to the last refresh.

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

   Last billing-cache refresh; `null` before the first calculation.

### Referenced Types

#### ISODateString

`ISODateString`

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

## Late Data Model

### Fields

- **`object`** `"late_data_file"`

- **`id`** `string`

   Unique identifier, prefixed with `ldf_`.

- **`view_id`** `string`

- **`view_name`** `string`

   Display name of the view whose data carried the late events.

- **`excluded_count`** `number`

   How many events were kept out of charts because their timestamps fell behind the project's late
   data window. The events themselves are stored; only their aggregation waits.

- **`min_event_at`** [`ISODateString`](/api/projects#iso-date-string)

   The earliest event time among the excluded events. A re-backfill folds back to this instant.

- **`max_event_at`** [`ISODateString`](/api/projects#iso-date-string)

   The latest event time among the excluded events.

- **`exclusion_below`** [`ISODateString`](/api/projects#iso-date-string)

   The boundary the events fell behind. Events at or after it were included normally.

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

   When a re-backfill was requested for these events; null until requested.

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

   When the re-backfill finished folding the events into charts; null until complete.

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

### Referenced Types

#### ISODateString

`ISODateString`

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

## Live Pipeline Model

### Fields

- **`object`** `"project_pipeline_live"`

- **`observed_at`** [`ISODateString`](/api/projects#iso-date-string)

- **`pending_records`** `number | null`

   Valid spooled records awaiting write, including processing. Null when the queue is unknown.

- **`processing_records`** `number | null`

   Pending records currently being processed.

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

   Oldest contributing queue observation, independent of the server heartbeat.

- **`pending_is_estimated`** `boolean`

   The queue changed during inspection; available counts are estimates from durable records.

- **`state`** `"fresh" | "stale" | "unavailable"`

   Queue freshness across all current project servers, including deployment surges.

- **`contributing_servers`** `number`

- **`fresh_servers`** `number`

- **`window_start_at`** [`ISODateString`](/api/projects#iso-date-string)

   Inclusive start of the last completed minute.

- **`window_end_at`** [`ISODateString`](/api/projects#iso-date-string)

   Exclusive end of the last completed minute.

- **`throughput_records`** `number | null`

   Records processed in the completed minute. Null when any contributor lacks telemetry.

- **`errors`** `number | null`

   Rejected requests and dropped frames in the completed minute, not a record count.

- **`pipeline_error_counts`** `Partial | null`

   Map from each `PipelineErrorReason` key to its error count for the completed minute. Omitted
   keys had no errors; `null` means telemetry is incomplete.

### Referenced Types

#### ISODateString

`ISODateString`

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

#### PipelineErrorReason

`"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"`

## Usage Sample Model

### Fields

- **`object`** `"usage_sample"`

- **`sampled_at`** [`ISODateString`](/api/projects#iso-date-string)

   When the reading was taken. Readings are hourly, on the hour. With `interval=day` this is the
   timestamp of the day's last reading, not midnight.

- **`collection_gb`** `number`

   Gigabytes of raw event data held in the project's collections, measured uncompressed, exactly
   as it is billed.

- **`artifact_gb`** `number`

   Gigabytes of artifacts extracted from the project's events, measured uncompressed.

- **`view_gb`** `number`

   Gigabytes of view records the project's views produced, measured uncompressed.

- **`rollup_gb`** `number`

   Gigabytes of rollup data behind the project's charts, measured uncompressed. Counts the minute
   tier only, matching what invoices charge for.

- **`total_gb`** `number`

   The four components added together: everything the project stored at this reading.

### Referenced Types

#### ISODateString

`ISODateString`

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

## Attention Item Model

### Fields

- **`object`** `"attention_item"`

- **`type`** [`AttentionItemType`](/api/projects#attention-item-type)

- **`tier`** [`AttentionTier`](/api/projects#attention-tier)

- **`resource_id`** `string | null`

   The resource to open to act on the condition: the metric behind a firing monitor, the view
   behind a failing transform, otherwise the check, view, metric, drain, pull or server itself.
   Null when the item covers the project as a whole, which is the case for refused requests.

- **`resource_name`** `string | null`

   The name shown for the condition, which can belong to a related resource: a firing monitor's
   item names the monitor while `resource_id` opens its metric. Null whenever `resource_id` is
   null.

- **`count`** `number | null`

   How much of the condition there is, in the unit that fits it: requests refused (not records)
   over the last 24 hours, or the last hour for capacity, series a monitor is firing on, a view's
   failing transforms, events waiting on a shape change, consecutive failures of a check, pull, or
   drain, or a hot server's average load in percent. Null when the condition has no quantity.

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

   When the condition started, as accurately as Tailglow knows. Null when nothing on record marks
   a beginning: a check that has never once succeeded, or a server under sustained load, which is
   judged from an average rather than a start.

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

   When the condition stopped, for a `resolved` item: to the minute within the last hour, to the
   end of the hour before that. Null while the condition is still true, including while a server
   that was refusing has not reported since.

- **`detail`** `string | null`

   A short machine-readable qualifier that narrows the type: the refusal reason group, the firing
   monitor's id, or `cpu` or `mem` for the load that made a server hot. A stable identifier meant
   for branching in code, never a sentence to display. Null when the type needs no qualifier.

### Referenced Types

#### ISODateString

`ISODateString`

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

#### AttentionItemType

`"ingest_refusals" | "capacity_refusals" | "monitor_firing" | "check_down" | "transform_error" | "metric_error" | "join_error" | "shape_deferral" | "pull_suspended" | "drain_suspended" | "drain_unverified" | "servers_hot"`

The condition an attention item reports. Every value is a state Tailglow entered on its own, so
nothing a project deliberately turned off is ever reported here.

#### AttentionTier

`"losing_data" | "firing" | "stalled" | "suspended" | "resolved"`

How urgent an attention item is, most urgent first. `losing_data` means requests are being
refused right now in a way that loses data unless the sender acts, or were until a server that
was refusing stopped reporting. `firing` means something the project asked to be told about is
currently true, or a server has run under sustained heavy load. `stalled` means a pipeline
stopped making progress and existing 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; the item stays for a while so the loss is still visible, and says when it ended.

## List Projects

### Endpoint

Retrieve a list of projects for the current team.

```http
GET /v1/projects
```

**Scope:** `projects:read`

### Query Parameters

- **`order_by`** `string`
  Field used to order the projects. Optional. Defaults to `"name"`. Allowed values: `"created_at"`, `"name"`.

- **`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: Project[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- When neither `sort` nor `order_by` is provided, the route defaults `sort` to `"asc"`.
- `after` and `before` are mutually exclusive.

## Retrieve Project

### Endpoint

Retrieve a single project.

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

**Scope:** `projects:read`

### Path Parameters

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

### Response

Project retrieved

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

## Retrieve Project Pipeline

### Endpoint

Retrieve ingest activity across all of a project's servers.

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

**Scope:** `servers:read`

### Path Parameters

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

### Query Parameters

- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)
  Start time (ISO format, default: 12 hours ago). Optional.

- **`end_at`** [`ISODateString`](/api/projects#iso-date-string)
  End time (ISO format, default: now). Optional.

- **`interval`** `string`
  Aggregation interval. Optional. Defaults to `"minute"`. Allowed values: `"minute"`, `"hour"`.

### Response

Project pipeline retrieved

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

### Comments

- 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.

## Retrieve Live Project Pipeline

### Endpoint

Retrieve the current project queue and ingest totals for the last completed minute.

```http
GET /v1/projects/:project_id/pipeline/live
```

**Scope:** `servers:read`

### Path Parameters

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

### Response

Live project pipeline retrieved

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

### Comments

- 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.
- Throughput and error totals cover the last completed minute. Errors count requests or frames, and totals are null when telemetry is incomplete.

## Create Project

### Endpoint

Create a new project for the current team.

```http
POST /v1/projects
```

**Scope:** `projects:write`

### Request Body

- **`name`** `string` -- **Required**
  Display name for the project. Minimum length: `1`. Maximum length: `60`.

- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)
  Date and time when the project began collecting data. Optional.

### Response

Your project has been created.

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

## Update Project

### Endpoint

Update an existing project.

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

**Scope:** `projects:write`

### Path Parameters

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

### Request Body

- **`name`** `string`
  Display name for the project. Optional. Minimum length: `1`. Maximum length: `60`.

- **`start_at`** [`ISODateString`](/api/projects#iso-date-string)
  Date and time when the project began collecting data. Optional.

- **`late_data_window_minutes`** `integer`
  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`.

- **`deleted_at`** `null`
  Set to null to cancel a scheduled project deletion. Optional.

### Response

Your project has been updated.

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

## Delete Project

### Endpoint

Schedule a project for deletion.

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

**Scope:** `projects:delete`

### Path Parameters

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

### Response

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

### Comments

- The project is hidden immediately and permanently removed 7 days later. Restore it before then by updating it with `deleted_at` set to null.

## Force Delete Project

### Endpoint

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

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

**Scope:** `projects:delete`

### Path Parameters

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

### Response

Project queued for permanent deletion.

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

### Comments

- Works on a live project as well as one already scheduled for deletion.
- Every source, collection, view, metric and record in the project is destroyed immediately, and its servers are torn down. Nothing here can be restored.

## List Late Data

### Endpoint

Retrieve the events held out of charts by the project's late data window, grouped per data file.

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

**Scope:** `projects:read`

### Path Parameters

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

### Query Parameters

- **`order_by`** `string`
  Field used to order the late data records. Optional. Defaults to `"created_at"`. Allowed values: `"created_at"`, `"min_event_at"`.

- **`refolded`** `string`
  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"`.

- **`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: LateDataFile[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- Events with overridden timestamps beyond the late data window are stored and counted here instead of entering charts. Data sent live is never held.
- Records with `refolded_at` set have already been folded into charts by a completed re-backfill; filter with `refolded=false` for records still waiting.
- `after` and `before` are mutually exclusive.

## Re-backfill Late Data

### Endpoint

Start a re-backfill that folds one late data record's events into charts.

```http
POST /v1/projects/:project_id/late_data/:late_data_file_id/re_backfill
```

**Scope:** `projects:write`

### Path Parameters

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

- **`late_data_file_id`** `string` -- **Required**
  Unique identifier of the late data.

### Response

Re-backfill queued. The events will appear in charts as the fold completes.

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

### Comments

- Idempotent: requesting a re-backfill that is already queued or complete returns the record unchanged.
- 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.

## Retrieve Project Usage

### Endpoint

Retrieve how much the project stored over a time range.

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

**Scope:** `projects:read`

### Path Parameters

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

### Query Parameters

- **`start_at`** [`ISODateString`](/api/projects#iso-date-string) -- **Required**
  Start of the range, inclusive (ISO format).

- **`end_at`** [`ISODateString`](/api/projects#iso-date-string)
  End of the range, inclusive (ISO format, default: now). Optional.

- **`interval`** `string`
  Reading cadence returned: every hourly reading, or one reading per UTC day. Optional. Defaults to `"hour"`. Allowed values: `"hour"`, `"day"`.

### Response

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

### Comments

- Readings are taken hourly and are the same measurements the project is billed on. Gigabytes are logical (uncompressed) bytes.
- `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.
- Readings start when Tailglow began recording them, so a range reaching further back returns nothing for the hours before that.
- `start_at` must be earlier than `end_at`, and the range cannot be longer than 400 days.

## Retrieve Project Attention

### Endpoint

Retrieve everything in the project that is asking for a human right now.

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

**Scope:** `projects:read`

### Path Parameters

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

### Response

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

### Comments

- 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.
- `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.
- 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.
- 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`.
- 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.

