# Teams

## Team Model

### Fields

- **`object`** `"team"`

- **`id`** `string`

- **`name`** `string`

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

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

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

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

- **`logo_url`** `string | null`

- **`status`** [`TeamStatus`](/api/teams#team-status)

   Account standing. Any status other than `active` stops ingest: `delinquent` marks a missed
   payment, `restricted` also makes the API read-only apart from the billing fields needed to
   recover the account, and `blocked` is applied manually by Tailglow.

- **`billing_email`** `string`

- **`billing_plan`** [`BillingPlan`](/api/teams#billing-plan)

- **`billing_address`** [`Address`](/api/teams#address-model)

   Postal address that appears on the team's invoices.

- **`storage_gb`** `number`

   Stored data across the team's projects, 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 the team's servers have accrued in the current calendar-month billing period,
   counted up to the last refresh.

- **`billing_period_input_tokens`** `number`

   AI input tokens consumed through Tailglow-provided model access this billing period, counting
   only tokens charged at the full input rate. Anything served from or written to the prompt cache
   is counted separately below. Chats running on your own provider keys are not counted at all.

- **`billing_period_output_tokens`** `number`

   AI output tokens consumed through Tailglow-provided model access this billing period.

- **`billing_period_cache_read_tokens`** `number`

   Input tokens served from the prompt cache this billing period, charged at the cache rate.

- **`billing_period_cache_write_tokens`** `number`

   Input tokens written to the prompt cache this billing period, charged at the cache rate.

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

   When the cached billing quantities were last recalculated; null before the first pass.

- **`max_servers_per_project`** `number`

   How many servers each project may run. Scale requests beyond it are rejected.

- **`is_mfa_required`** `boolean`

   Whether every member must verify with two-factor authentication before their session can act.

- **`is_tailglow_ai_enabled`** `boolean`

   Whether members may run the assistant on Tailglow's model, billed to this team. When false,
   each member must add their own provider key before they can use the assistant at all.

- **`has_payment_method`** `boolean`

   Whether the team has a payment method on file.

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

   When the team's free trial window ends. A time in the past means the trial has ended and the
   trial servers are being settled; null when the team has no trial window.

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

   When the team used its once-per-account server trial; retained after the trial is settled.

- **`promo_server_hours`** `number`

   Shared server-hour credits remaining after the last invoice; unbilled usage is not deducted.

- **`promo_ai_tokens`** `number`

   Shared Tailglow AI token credits remaining after the last invoice.

- **`tgl_generations_count`** `number`

   Automatic transform-script generations counted against the hourly budget. The counter rolls
   over lazily: after the window elapses it keeps its last value until the next generation.

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

   When the counted window ends or ended. A past timestamp means no generation has happened since.

### Referenced Types

#### ISODateString

`ISODateString`

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

#### TeamStatus

`"active" | "delinquent" | "restricted" | "blocked"`

#### BillingPlan

`"pro_v1" | "enterprise_v1"`

## Team Billing Model

### Fields

- **`is_estimate_complete`** `boolean`

   False when a saved AI rate needs support; monetary estimates are incomplete until resolved.

- **`object`** `"billing"`

- **`team_id`** `string`

- **`calculated_at`** [`ISODateString`](/api/teams#iso-date-string)

- **`billing_plan`** [`BillingPlan`](/api/teams#billing-plan)

- **`has_payment_method`** `boolean`

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

   Cardless trial deadline; unused promotional units do not expire at this time.

- **`server_count`** `number`

   Non-surge capacity across the team, including provisioning and excluding idle/terminating.

- **`uninvoiced_server_hours`** `number`

   Billable server-hours since the last saved invoice, including preceding-month usage.

- **`promo_server_hours`** `number`

   Stored server-hour balance after the last invoice.

- **`promo_ai_tokens`** `number`

   Stored Tailglow-funded token balance after the last invoice.

- **`available_promo_server_hours`** `number`

   Server-hour credits remaining after accrued, uninvoiced usage.

- **`available_promo_ai_tokens`** `number`

   Tailglow-funded token credits remaining after accrued, uninvoiced usage.

- **`included_storage_gb_months`** `number`

   Storage, in GB-months, the plan credits on every monthly invoice.

- **`available_included_storage_gb_months`** `number`

   Included storage this period has not used yet, in GB-months.

- **`period_start_at`** [`ISODateString`](/api/teams#iso-date-string)

   First moment of the billing period being estimated, in UTC.

- **`period_end_at`** [`ISODateString`](/api/teams#iso-date-string)

   First moment of the following period, so the period is `[start, end)`.

- **`billed_at`** [`ISODateString`](/api/teams#iso-date-string)

   When the invoice for this period is calculated, which is after the period has closed.

- **`line_items`** [`InvoiceEstimateLineItem[]`](/api/teams#billing-line-item-model)

   One entry per billable line. Lines that accrued nothing are present with a zero quantity.

- **`subtotal_cents`** `number`

- **`discount_cents`** `number`

- **`total_cents`** `number`

   What the period has accrued so far, after discounts. Excludes tax, which Stripe calculates when
   the invoice is finalized, and any credit note balance, which is applied at payment.

- **`projected_line_items`** [`InvoiceEstimateLineItem[]`](/api/teams#billing-line-item-model)

   The same period carried to `billed_at`: storage held and grown at its recent rate, servers that
   are up staying up, tokens continuing at the month-to-date rate. A subscription appears
   unchanged because it is committed for the whole period. A forecast rather than a measurement,
   so it assumes today's usage continues and moves the moment a server is added or removed.
   Discounts here are read as of `billed_at`, so one that lapses mid-period counts toward
   `total_cents` but not toward this.

- **`projected_subtotal_cents`** `number`

- **`projected_discount_cents`** `number`

- **`projected_total_cents`** `number`

### Referenced Types

#### ISODateString

`ISODateString`

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

#### BillingPlan

`"pro_v1" | "enterprise_v1"`

#### BillingLineItemType

`"compute" | "storage" | "subscription" | "ai_input" | "ai_output" | "ai_cache_read" | "ai_cache_write"`

## Billing Line Item Model

### Fields

- **`line_item_type`** [`BillingLineItemType`](/api/teams#billing-line-item-type)

- **`description`** `string`

   The label this line carries on the issued invoice, so an estimate reads like the bill.

- **`quantity`** `number`

   Units billed, in the line item's own unit: GB-months, server-months, or token millions.

- **`unit_price_cents`** `number`

- **`subtotal_cents`** `number`

- **`discount_cents`** `number`

   Zero when no discount applies to this line item.

- **`total_cents`** `number`

- **`discount_id`** `string | null`

   The negotiated discount applied in addition to promo credits, or null.

- **`discount_percent`** `number | null`

   Set only for percent discounts; a fixed-amount discount reports its value in cents.

### Referenced Types

#### ISODateString

`ISODateString`

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

#### BillingLineItemType

`"compute" | "storage" | "subscription" | "ai_input" | "ai_output" | "ai_cache_read" | "ai_cache_write"`

## Billing Estimate Model

### Fields

- **`line_items`** [`InvoiceEstimateLineItem[]`](/api/teams#billing-line-item-model)

   One entry per billable line, in the order the invoice lists them. Lines that accrued nothing
   are present with a zero quantity.

- **`subtotal_cents`** `number`

- **`discount_cents`** `number`

- **`total_cents`** `number`

### Referenced Types

#### ISODateString

`ISODateString`

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

#### BillingLineItemType

`"compute" | "storage" | "subscription" | "ai_input" | "ai_output" | "ai_cache_read" | "ai_cache_write"`

## Address Model

### Fields

- **`thoroughfare`** `string | null`

   Street line: street number and street name.

- **`premise`** `string | null`

   Unit, suite, or building within the street address.

- **`sublocality`** `string | null`

   District or neighborhood within the city, where addresses use one.

- **`locality`** `string | null`

   City or town.

- **`administrative_area`** `string | null`

   State, province, or region.

- **`postal_code`** `string | null`

   ZIP or postal code.

- **`country`** `string | null`

   Two-letter ISO country code.

### Referenced Types

#### ISODateString

`ISODateString`

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

## List Teams

### Endpoint

Retrieve a list of teams the caller belongs to.

```http
GET /v1/teams
```

### Query Parameters

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

- **`name`** [`TextFilter`](/api/teams#text-filter)
  Filter by team name, written as `operator:value`. For example `contains:acme` or `equals:Acme Inc`. Optional.

- **`status`** [`TeamStatus`](/api/teams#team-status)
  Filter by team status, written as `operator:value`. For example `equals:active` or `in:active,delinquent`. Optional.

- **`billing_plan`** [`BillingPlan`](/api/teams#billing-plan)
  Filter by billing plan, written as `operator:value`. For example `in:pro_v1,enterprise_v1`. Optional.

- **`created_at`** [`DateFilter`](/api/teams#date-filter)
  Filter by creation date, written as `operator:value`. For example `gt:2026-01-01` or `between:2026-01-01,2026-02-01`. Optional.

- **`deleted_at`** [`NullableDateFilter`](/api/teams#nullable-date-filter)
  Filter by scheduled deletion date. Use `null` for teams that are not scheduled for deletion, `not:null` for teams that are. 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: Team[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- API key callers only ever receive the single team the key belongs to.
- Teams scheduled for deletion are only returned to their Owners.
- `after` and `before` are mutually exclusive.

## Retrieve Team

### Endpoint

Retrieve a single team.

```http
GET /v1/teams/:team_id
```

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

### Response

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

### Comments

- You can only retrieve the team your credentials are scoped to.

## Retrieve Team Billing

### Endpoint

Retrieve the team's current billing summary.

```http
GET /v1/teams/:team_id/billing
```

**Scope:** `billing:read`

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

### Response

Team billing retrieved

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

### Comments

- Requires `billing:read` and credentials scoped to this team. Every response includes accrued and projected charges, stored and available promotional credits, and server usage.
- Available credits account for uninvoiced usage, including the preceding month before its invoice is saved. Reading this summary does not consume credits or create an invoice.
- Amounts exclude tax and credit notes applied at payment. Projections assume current usage continues; the final invoice can differ.

## Create Team

### Endpoint

Create a new team owned by the authenticated user.

```http
POST /v1/teams
```

### Request Body

- **`name`** `string`
  Display name for the team. Optional. Minimum length: `2`. Maximum length: `60`.

- **`billing_email`** `string (email)`
  Email address that receives invoices and billing notifications. Optional. Maximum length: `64`.

- **`billing_plan`** [`BillingPlan`](/api/teams#billing-plan)
  Billing plan for the team. Optional.

- **`status`** [`TeamStatus`](/api/teams#team-status)
  Account status for the team. Optional.

- **`is_mfa_required`** `boolean`
  Whether every member must sign in with multi-factor authentication. Optional.

- **`max_servers_per_project`** `integer`
  Maximum number of servers each project in the team can run. Optional. Minimum: `1`. Maximum: `1000`.

- **`billing_address`** `object`
  Billing address printed on invoices. Optional.

- **`logo_url`** `string`
  Team logo, sent as a JPEG or PNG base64 data URI. Optional.

### Response

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

### Comments

- Teams cannot be created with an API key. Use a signed-in user session.
- `name` defaults to `Team` and `billing_email` defaults to the authenticated user's email address when they are omitted.
- The team starts on the `pro_v1` plan with a generated logo. `billing_plan`, `status`, `logo_url`, `is_mfa_required`, `billing_address`, and `max_servers_per_project` are ignored on create. Send them to the update endpoint instead.
- There is a maximum number of teams a single user can create.
- The authenticated user is added to the new team as its Owner.
- `billing_email` cannot use the `@tailglow.io` domain.
- `logo_url` must be a JPEG or PNG base64 data URI of at most 5 MB.

## Update Team

### Endpoint

Update an existing team.

```http
POST /v1/teams/:team_id
```

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

### Request Body

- **`name`** `string`
  Display name for the team. Optional. Minimum length: `2`. Maximum length: `60`.

- **`billing_email`** `string (email)`
  Email address that receives invoices and billing notifications. Optional. Maximum length: `64`.

- **`billing_plan`** [`BillingPlan`](/api/teams#billing-plan)
  Billing plan for the team. Optional.

- **`status`** [`TeamStatus`](/api/teams#team-status)
  Account status for the team. Optional.

- **`expected_status`** [`TeamStatus`](/api/teams#team-status)
  The status the team had when you loaded it. Required with `status`; the change is refused if the status has changed since. Optional.

- **`is_mfa_required`** `boolean`
  Whether every member must sign in with multi-factor authentication. Optional.

- **`is_tailglow_ai_enabled`** `boolean`
  Whether members may run the AI assistant on Tailglow's model, billed to this team. When false, each member must add their own provider key before they can use the assistant. Optional.

- **`max_servers_per_project`** `integer`
  Maximum number of servers each project in the team can run. Optional. Minimum: `1`. Maximum: `1000`.

- **`billing_address`** `object`
  Billing address printed on invoices. Optional.

- **`logo_url`** `string`
  Team logo, sent as a JPEG or PNG base64 data URI. Optional.

- **`deleted_at`** [`ISODateString | null`](/api/teams#iso-date-string)
  Date and time when the team is scheduled to be permanently deleted. Optional.

### Response

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

### Comments

- Teams cannot be updated with an API key. Use a signed-in user session.
- At least one field must be provided.
- Only team Owners can change billing, security, or account settings. Other members can update `name` and `logo_url` only.
- `status`, `max_servers_per_project`, and `deleted_at` are managed by Tailglow and cannot be set by team members.
- `status` requires `expected_status`, the status you loaded; the change is refused with a conflict if the team's status has changed since.
- Changing `is_mfa_required` requires a recently verified multi-factor sign-in.
- Moving to a plan that bills a payment method requires a valid payment method on the team.
- `billing_email` cannot use the `@tailglow.io` domain.
- `logo_url` must be a JPEG or PNG base64 data URI of at most 5 MB.

## Delete Team

### Endpoint

Schedule a team for deletion.

```http
DELETE /v1/teams/:team_id
```

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

### Response

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

### Comments

- Teams cannot be deleted with an API key. Use a signed-in user session.
- Only the team Owner can delete a team.
- The team must be active. Contact support to delete a suspended or blocked team.
- Deletion is scheduled, not immediate. Permanent removal happens 40 days later, the team's projects and API keys are cancelled, and every signed-in member is signed out. An owner who signs back in before then cancels the deletion.

## List Team Members

### Endpoint

Retrieve a list of members in a team.

```http
GET /v1/teams/:team_id/users
```

**Scope:** `users:read`

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

### Query Parameters

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

- **`name`** [`TextFilter`](/api/teams#text-filter)
  Filter by member name, written as `operator:value`. For example `contains:riley` or `starts_with:Ri`. Optional.

- **`status`** [`UserStatus`](/api/users#user-status)
  Filter by member account status, written as `operator:value`. For example `in:active,blocked`. 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: User[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- You can only list members of the team your credentials are scoped to.
- `after` and `before` are mutually exclusive.

## Retrieve Team Member

### Endpoint

Retrieve a single member of a team.

```http
GET /v1/teams/:team_id/users/:user_id
```

**Scope:** `users:read`

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

- **`user_id`** `string` -- **Required**
  Unique identifier of the user.

### Response

User retrieved

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

### Comments

- You can only retrieve members of the team your credentials are scoped to.

## Add Team Member

### Endpoint

Add a person to a team. This does not create a standalone user account: an existing Tailglow account is matched by email address, and an account is created for the address only when none exists yet.

```http
POST /v1/teams/:team_id/users
```

**Scope:** `users:write`

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

### Request Body

- **`name`** `string`
  Display name for the member. Optional. Minimum length: `2`. Maximum length: `60`.

- **`role_id`** `string` -- **Required**
  Role the member holds in this team.

- **`email`** `string (email)` -- **Required**
  Email address of the person to add. They receive a sign-in link at this address, and an account is created for them if they do not already have one.

### Response

New user was added to the team

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

### Comments

- The person receives an email with a sign-in link for the team.
- Someone who is already a member of the team cannot be added again.
- Only Owners can assign the Owner role.
- There is a maximum number of members per team.
- Blocked and locked accounts cannot be added to a team.
- You can assign only scopes that your own authorization has.

## Update Team Member

### Endpoint

Update a member of a team.

```http
POST /v1/teams/:team_id/users/:user_id
```

**Scope:** `users:write`

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

- **`user_id`** `string` -- **Required**
  Unique identifier of the user.

### Request Body

- **`name`** `string`
  Display name for the member. Optional. Minimum length: `2`. Maximum length: `60`.

- **`role_id`** `string`
  Role the member holds in this team. Optional.

### Response

User updated

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

### Comments

- You cannot change your own role.
- You can only change your own name. Another member's name is theirs to change.
- You cannot assign a role that holds scopes you do not hold yourself, and only Owners can assign or replace the Owner role.
- Changing a member's role signs them out and emails them the new role.
- A member's email address cannot be changed. Remove them and add them again with the new address.

## Remove Team Member

### Endpoint

Remove a member from a team.

```http
DELETE /v1/teams/:team_id/users/:user_id
```

**Scope:** `users:delete`

### Path Parameters

- **`team_id`** `string` -- **Required**
  Unique identifier of the team.

- **`user_id`** `string` -- **Required**
  Unique identifier of the user.

### Response

User removed from team

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

### Comments

- You cannot remove yourself from a team. Delete the team instead if you are its last member.
- You cannot remove a member whose role holds scopes you do not hold yourself, and only Owners can remove another Owner.
- The last Owner of a team cannot be removed.
- The member is emailed to let them know they were removed. Their Tailglow account is not deleted, only their membership in this team.

