# Create Team

> POST /v1/teams · Access management API

Source: https://developer.kustomer.com/api/access-management/create-team

```http
POST https://{orgname}.api.kustomerapp.com/v1/teams
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.team.create`; or legacy `org.admin.team.write`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Creates a Team.

## About Teams

A team is a named group of users, such as "Billing" or "Tier 2". Conversations can be assigned to a team, routing uses teams to decide which queues and capacity rules apply to an agent, and teams can carry role groups that grant their members permissions.

Admins manage teams in Kustomer's user settings, and agents see them wherever work is assigned: a conversation can be assigned to a team as well as to a person. A user can be on any number of teams. Each team stores its members as a list of [user](https://developer.kustomer.com/api/access-management#users) IDs, and routing configuration for the team lives separately in [team routing settings](https://developer.kustomer.com/api/queues-and-routing#team-routing-settings).

#### Fields

| Field | Meaning |
| --- | --- |
| `name` | Required, up to 256 characters |
| `displayName` | Optional label, up to 256 characters |
| `icon` | Optional icon name |
| `members` | User IDs of the team's members. Each must be a user in your org. |
| `externalId` | Optional ID from another system, set when creating |
| `createdAt`, `importedAt` | Optional on create, for teams migrated from another system |

#### Membership

There are two ways to change who is on a team. Sending `members` in `PUT` or `PATCH /v1/teams/{id}` replaces the whole list. `POST /v1/teams/{id}/members` adds the user IDs you send and `DELETE /v1/teams/{id}/members` removes them, leaving everyone else in place. Use the add and remove endpoints when more than one system manages membership, so one doesn't overwrite another's changes. A request can carry up to 1,000 member IDs, and a change that would push the team over its maximum number of active members fails with `409`. To get the members' names and emails, pass the IDs to [`GET /v1/users/{ids}`](https://developer.kustomer.com/api/access-management/get-multiple-users-by-ids).

#### Deleting and role groups

Deleting a team is a soft delete: it's marked deleted rather than removed, so it still appears in lists. Pass `deleted=false` to `GET /v1/teams` or `GET /v1/users/{id}/teams` to return only active teams. Teams can have [role groups](https://developer.kustomer.com/api/access-management#role-groups) assigned, which add to their members' permissions. Filter teams by role group with `roleGroups`, and add `include=role-groups` when listing a user's teams to get the role groups with them.

#### Which endpoint to use

| To | Call |
| --- | --- |
| List teams | [`GET /v1/teams`](https://developer.kustomer.com/api/access-management/get-teams) |
| Find the teams a user is on | [`GET /v1/users/{id}/teams`](https://developer.kustomer.com/api/access-management/get-a-users-teams) |
| Read one team and its member IDs | [`GET /v1/teams/{id}`](https://developer.kustomer.com/api/access-management/get-team-by-id) |
| Create a team | [`POST /v1/teams`](https://developer.kustomer.com/api/access-management/create-team) |
| Add or remove specific members | [`POST`](https://developer.kustomer.com/api/access-management/add-team-member) or [`DELETE /v1/teams/{id}/members`](https://developer.kustomer.com/api/access-management/remove-team-member) |
| Change some of a team's fields | [`PATCH /v1/teams/{id}`](https://developer.kustomer.com/api/access-management/update-team-attributes) |
| Replace a team's fields | [`PUT /v1/teams/{id}`](https://developer.kustomer.com/api/access-management/update-team-by-id) |
| Delete a team | [`DELETE /v1/teams/{id}`](https://developer.kustomer.com/api/access-management/delete-team) |

`PUT` requires `name` and clears optional fields you leave out, while `PATCH` changes only the fields you send, so prefer `PATCH` for small edits. Reading teams works with agent or admin read roles; changes need a team write role. List endpoints are paginated with `page` and `pageSize` (up to 500), and `fields` limits which attributes are returned.

Related: [Users](https://developer.kustomer.com/api/access-management#users), [Role groups](https://developer.kustomer.com/api/access-management#role-groups), [Team routing settings](https://developer.kustomer.com/api/queues-and-routing#team-routing-settings)

### Recipes

#### 1. Sync a team's members from a directory group

Make a Kustomer team match a group in your identity provider or HR system, adding and removing only the people who differ.

**Step 1.** [`GET /v1/teams`](https://developer.kustomer.com/api/access-management/get-teams.md). Find the team's ID. There's no filter by name, so list the active teams and match on `name`.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/teams?deleted=false&pageSize=500" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
TEAM_ID=$(kusty api GET /v1/teams --query deleted=false --all \
  | jq -r 'select(.data.attributes.name == "Tier 2") | .data.id')
```

**Step 2.** [`GET /v1/users`](https://developer.kustomer.com/api/access-management/get-users.md). Turn each email address in the directory group into a Kustomer user ID, one lookup per address. Skip addresses with no user, or invite them first.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users?email=$EMAIL" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
while read -r EMAIL; do
  kusty api GET /v1/users --query email=$EMAIL | jq -r '.data[0].id // empty'
done < group-emails.txt | sort -u > wanted.txt
```

**Step 3.** [`GET /v1/teams/{id}`](https://developer.kustomer.com/api/access-management/get-team-by-id.md). Read the team's current `members` and work out who to add and who to remove.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/teams/$TEAM_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/teams/$TEAM_ID | jq -r '.data.attributes.members[]' | sort -u > current.txt
comm -13 current.txt wanted.txt | jq -R . | jq -sc . > add.json
comm -23 current.txt wanted.txt | jq -R . | jq -sc . > remove.json
```

**Step 4.** [`POST /v1/teams/{id}/members`](https://developer.kustomer.com/api/access-management/add-team-member.md). Add the missing people. This endpoint adds to the list rather than replacing it, so a change an admin makes at the same time isn't lost. Adding people who would take the team over its member limit fails with `409`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/teams/$TEAM_ID/members" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"members\": $(cat add.json)}"
```

```bash title="kusty"
kusty api POST /v1/teams/$TEAM_ID/members --field "members:=$(cat add.json)"
```

**Step 5.** [`DELETE /v1/teams/{id}/members`](https://developer.kustomer.com/api/access-management/remove-team-member.md). Remove the people who are no longer in the group.

```bash title="cURL"
curl --request DELETE "https://api.kustomerapp.com/v1/teams/$TEAM_ID/members" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"members\": $(cat remove.json)}"
```

```bash title="kusty"
kusty api DELETE /v1/teams/$TEAM_ID/members --field "members:=$(cat remove.json)" --confirm --yes
```

> Skip a call when its list is empty: if none of the IDs you send is a user in your org, the API answers `422`. IDs that aren't users are otherwise dropped without an error, and a malformed ID fails with `400`. Avoid sending the whole list with `PUT` or `PATCH /v1/teams/{id}`, which replaces `members` and can undo someone else's change.

#### 2. List who's on each team

Print every active team with its members' names, for a staffing page or an access review.

**Step 1.** [`GET /v1/teams`](https://developer.kustomer.com/api/access-management/get-teams.md). List the active teams. Each one's `members` is a list of user IDs.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/teams?deleted=false&pageSize=500" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/teams --query deleted=false --all --output jsonl > teams.jsonl
```

**Step 2.** [`GET /v1/users/{ids}`](https://developer.kustomer.com/api/access-management/get-multiple-users-by-ids.md). Look up all the members at once by joining their IDs with commas. When a list has a single ID the response's `data` is one object instead of an array, so handle both. Split very long lists into batches to keep the URL short.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users/$USER_IDS" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
USER_IDS=$(jq -r '.data.attributes.members[]' teams.jsonl | sort -u | paste -sd, -)
kusty api GET /v1/users/$USER_IDS \
  | jq '.data | if type == "array" then . else [.] end | map({(.id): .attributes.displayName}) | add' > names.json
```

**Step 3.** Print each team with its members' names.

```bash title="kusty"
jq -r --slurpfile names names.json \
  '.data.attributes | "\(.name): \([.members[] | $names[0][.] // .] | join(", "))"' teams.jsonl
```

#### 3. React when someone joins or leaves a team

Keep another system's groups in step with Kustomer teams by handling team update events instead of polling.

**Step 1.** Subscribe an [outbound webhook](https://developer.kustomer.com/docs/events/outbound-webhooks) to `kustomer.team.update`. Kustomer sends it when a team's name or members change, including through the add and remove member endpoints.

**Step 2.** Compare `changes.attributes.members.before` with `after` to see who joined and who left. When the event has no `members` change, only another attribute such as the name changed.

```bash title="kusty"
jq -c '.changes.attributes.members // empty
  | {joined: (.after - .before), left: (.before - .after)}' event.json
```

**Step 3.** [`GET /v1/users/{ids}`](https://developer.kustomer.com/api/access-management/get-multiple-users-by-ids.md). Look up the people who joined or left to get their email addresses for the other system.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users/$USER_IDS" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/users/$USER_IDS \
  | jq -r '.data | if type == "array" then .[] else . end | [.id, .attributes.email] | @tsv'
```

> Events can arrive more than once or out of order, so apply the team's current `members` from `GET /v1/teams/{id}` when exact membership matters. See [delivery and retries](https://developer.kustomer.com/docs/events/delivery-and-retries).

### Example object

A real record from [`GET /v1/teams/{id}`](https://developer.kustomer.com/api/access-management/get-team-by-id.md):

```json
{
  "type": "team",
  "id": "5828e52f7c7f551a00b5b731",
  "attributes": {
    "name": "Sales Team",
    "createdAt": "2016-11-13T22:11:59.985Z",
    "updatedAt": "2016-11-13T22:11:59.985Z",
    "modifiedAt": "2016-11-13T22:11:59.985Z",
    "members": [
      "57bb5d2e32e83a130052e94e",
      "57bb5d2e32e83a130052e94f"
    ]
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57bb5d2e32e83a130052e94e"
      },
      "data": {
        "type": "org",
        "id": "57bb5d2e32e83a130052e94e"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/57bb5d2e32e83a130052e94f"
      },
      "data": {
        "type": "user",
        "id": "57bb5d2e32e83a130052e94f"
      }
    },
    "modifiedBy": {
      "links": {
        "self": "/v1/users/57bb5d2e32e83a130052e94f"
      },
      "data": {
        "type": "user",
        "id": "57bb5d2e32e83a130052e94f"
      }
    }
  },
  "links": {
    "self": "/v1/teams/5828e52f7c7f551a00b5b731"
  }
}
```

Fields:

- `attributes.name` (string): Required, up to 256 characters. There's no lookup by name, so list teams and match on it yourself.
- `attributes.modifiedAt` (string): When a person or API key last changed the team. `updatedAt` also moves when Kustomer changes it.
- `attributes.members` (array): User IDs of the team's members. Sending `members` on `PUT` or `PATCH` replaces the whole list, and IDs that aren't users in your org are dropped. Use the add and remove member endpoints to change only some.

### Relationships

- Points to **Users** via `relationships.members` (list): The users on a team, from the team's `members` list of user IDs. A user can be on any number of teams; `GET /v1/users/{id}/teams` lists one user's teams.
- Points to **Role groups** via `relationships.roleGroups` (list): Role groups assigned to a team add to every member's permissions. Find the teams that carry a role group with `roleGroups` on `GET /v1/teams`.
- Referenced by **Knowledge base articles** via `relationships.accessTeams` (list): Teams whose members are allowed to see an internal knowledge base article, set on a draft with `accessTeams` and applied when it's published.
- Referenced by **Saved searches** via `relationships.teamVisibilities` (list): The teams a saved search is shown to, from the `id` of each `teamVisibilities` entry. Each entry's `visibility` says whether the team sees it in the searches list, in autopilot, or both.
- Referenced by **Shortcuts** via `relationships.accessTeams` (list): The teams a shortcut is limited to, from its `accessTeams` list of up to 10 team IDs. With no teams or users listed, everyone in the org can use the shortcut.
- Referenced by **Team routing settings** via `relationships.team`: The team these routing settings belong to. Each team has at most one team routing settings record, addressed by the team's ID.
- Referenced by **Work items** via `relationships.team`: The team the agent holding this work item was working as. Its team routing settings supplied the queues and capacity.
- Referenced by **Work sessions** via `relationships.team`

### Permissions

Fewest roles for a read-only integration: `org.permission.team.read`. For every endpoint: `org.permission.team.update`, `org.permission.team.read`, `org.permission.team.create`, `org.permission.team.delete`.

### Events

Webhook events:

- `kustomer.team.create`: A team is created
- `kustomer.team.update`: A team's name or members change

## Request body

Content type: `application/json`

- `icon` (string): (max length 64)
- `name` (string, required): (min length 1; max length 256)
- `displayName` (string): (min length 1; max length 256)
- `createdAt` (string<date-time>)
- `importedAt` (string<date-time>)
- `members` (array<string>): (max items 1000; unique items)
- `externalId` (string): (max length 256)

Example:

```json
{
  "icon": "string",
  "name": "example_name",
  "displayName": "Example display name",
  "createdAt": "2026-03-14T15:09:26.535Z",
  "importedAt": "2026-03-14T15:09:26.535Z",
  "externalId": "crm-10293"
}
```

## Responses

### 201

Created

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `name` (string, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `modifiedAt` (string, required)
    - `members` (array<string>, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `createdBy` (object, required)
    - `modifiedBy` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "team",
    "id": "5828e52f7c7f551a00b5b731",
    "attributes": {
      "name": "Support Team",
      "createdAt": "2016-11-13T22:11:59.985Z",
      "updatedAt": "2016-11-13T22:11:59.985Z",
      "modifiedAt": "2016-11-13T22:11:59.985Z",
      "members": [
        "57bb5d2e32e83a130052e94f"
      ]
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/57bb5d2e32e83a130052e94e"
        },
        "data": {
          "type": "org",
          "id": "57bb5d2e32e83a130052e94e"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/57bb5d2e32e83a130052e94f"
        },
        "data": {
          "type": "user",
          "id": "57bb5d2e32e83a130052e94f"
        }
      },
      "modifiedBy": {
        "links": {
          "self": "/v1/users/57bb5d2e32e83a130052e94f"
        },
        "data": {
          "type": "user",
          "id": "57bb5d2e32e83a130052e94f"
        }
      }
    },
    "links": {
      "self": "/v1/teams/5828e52f7c7f551a00b5b731"
    }
  }
}
```

### 400

Bad Request - Invalid request parameters or body

### 401

Unauthorized - Authentication required or invalid credentials

### 403

Forbidden - Insufficient permissions

## Example request

```bash
curl --request POST \
  --url 'https://api.kustomerapp.com/v1/teams' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "icon": "string",
  "name": "example_name",
  "displayName": "Example display name",
  "createdAt": "2026-03-14T15:09:26.535Z",
  "importedAt": "2026-03-14T15:09:26.535Z",
  "externalId": "crm-10293"
}'
```
