# List teams

> Every team in a company, for administrative reads.

Canonical page: https://www.rundiffusion.com/docs/api/teams/list
Endpoint: GET /api/v2/teams
Authorization: OAuth device flow or Company API Access Token

---

Every team in a company. This is an administrative view for tooling that manages
an organization, not an account picker: the accounts **you** can act under are on
[Get identity](/docs/api/me), which already returns them.

Reading it requires the `MANAGE_TEAMS` permission on the company, because
listing a company's teams exposes its structure and
[Get a team](/docs/api/teams/get) then exposes every member's email address.

That permission comes from the caller's **company role**, which is a separate
grant from their team role. [Get identity](/docs/api/me) returns it under
`company_permissions`, not in the `permissions` map beside it. See
[Two roles, two maps](/docs/api/me#two-roles-two-maps).

`GET /api/v2/teams`

Authorization: OAuth device flow or Company API Access Token

## Request

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | string | Yes | Bearer token. This endpoint accepts an OAuth device flow token or a Company API Access Token. A Personal API Access Token is refused, because it reaches only its owner's own resources and is not scoped to a company. See [Authentication](/docs/api/authentication). |

**Query**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `cursor` | string | No | Opaque pagination token. When a response has has_more true, pass its next_cursor here to get the following page. Do not construct or parse one. |
| `limit` | integer | No | Page size, from 1 to 100. Default: `24` |
| `company_id` | string | No | The company to read. Required with OAuth, which names a person rather than a company. A Company API Access Token already names its company, so it does not need this. [Get identity](/docs/api/me) returns it on every TEAM account. |
| `is_active` | boolean | No | Filter by activation state. true returns activated teams, false returns deactivated ones. Omit it to get both, which is the default. |

### Naming the company

A **Company API Access Token** already names the company it acts on, so it needs
no parameter:

```bash
curl "https://api2.rundiffusion.com/api/v2/teams" \
  -H "Authorization: Bearer $RUNDIFFUSION_COMPANY_TOKEN"
```

**OAuth** is a user credential with no company of its own, and a user can belong
to teams in more than one company, so there is nothing to infer. Name the company
with `company_id`, which [Get identity](/docs/api/me) returns on every
TEAM account:

```bash
curl "https://api2.rundiffusion.com/api/v2/teams?company_id=Cm4pQ7…" \
  -H "Authorization: Bearer $RUNDIFFUSION_TOKEN"
```

Note that `company_id` is not the account selection `team_id` performs
everywhere else: this endpoint answers for a company, never for one team inside
it.

A **Personal API Access Token** is refused.

## Response

`200 OK` with one page of the company's teams, newest first.

```json
{
  "data": [
    {
      "id": "2D8NEx…",
      "created": "2026-03-14T09:02:11+00:00",
      "name": "Design Studio",
      "is_active": true
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

| Name | Type | Description |
| --- | --- | --- |
| `data` | array | The teams on this page, newest first. Empty when the company has none, which is not an error. |
| `has_more` | boolean | Whether more pages exist beyond this one. This is the flag to branch on when paging. |
| `next_cursor` | string \| null | Pass this back as cursor to fetch the next page. Null on the last page. |

### Team object

| Name | Type | Description |
| --- | --- | --- |
| `id` | string | The team ID. This is the value you send as team_id on the endpoints that take an account selection. |
| `created` | string (ISO 8601) \| null | When the team was created, in UTC. |
| `name` | string \| null | The team's display name. |
| `is_active` | boolean | Whether the team is activated. A deactivated team still appears here unless you filter it out. |

## Paging

Cursor paginated, the same way as every other listing. Read `has_more`, and when
it is true send `next_cursor` back as `cursor`.

The cursor is tied to the filter it was issued under. Changing `is_active`
partway through a walk invalidates it and returns `400 INVALID_CURSOR` rather
than quietly resuming somewhere else in a different result set, so keep the
filter constant for the length of a walk.

## Errors

**Status codes**

| Name | Type | Description |
| --- | --- | --- |
| `403` | PERMISSION_DENIED | The caller lacks MANAGE_TEAMS on the company, or presented a Personal API Access Token, or presented a user credential without naming a company. The message says which. |
| `404` | TEAM_NOT_FOUND | The company named does not exist, or has no teams the caller may read. |
| `400` | INVALID_CURSOR | The cursor no longer matches this listing, which happens if you change is_active partway through a walk. Restart the walk without a cursor. |
| `401` | UNAUTHENTICATED | Missing, malformed, or expired token. Refresh it and retry. |
| `429` | RATE_LIMITED | Too many requests. See [Rate limits](/docs/api/rate-limits#teams). |
