# Team

The team a key or token belongs to: its plan and limits, where it stands on the sending ladder, the sender identity campaigns print, its brand kit, whether AI assist is on, and the people on it.

## Who can call it

A key and a token each belong to exactly one team, so there is no team ID to pass — the route is singular.

| Credential | GET /team | PATCH /team | GET /team/members |
| --- | --- | --- | --- |
| `full_access` | 200 | 200 | 200 |
| `sending_access` | `401 restricted_api_key` | `401 restricted_api_key` | `401 restricted_api_key` |
| OAuth token with `team:read` | 200 | `403 invalid_permission` | `403 invalid_permission` |
| OAuth token with `team:write` | `403 invalid_permission` | 200 | `403 invalid_permission` |
| OAuth token with every scope | 200 | 200 | `403 invalid_permission` |

Scopes are listed on the [authentication](https://www.rasket.com/docs/authentication) page. Membership is in the class no scope reaches: a connected app never learns who is on your team.

## What the team object carries

- `sending.sandbox_regions` lists the regions still in our sending sandbox. There, mail only goes to verified identities, the mailbox simulator, and any address on a domain verified in Rasket, including its subdomains. Any other recipient gets `403 validation_error` with `details.recipient`. The list is empty when no region is in the sandbox.
- `sso` is a summary — `status`, `issuer`, `client_id`, `enforced` and `domains` — or `null` before a connection exists. It never carries the client secret and never lists members.
- `PATCH /team` writes `name`, the sender identity, `ai_assist_enabled` and `brand_kit`. Renaming needs a team admin: a `full_access` key can, and an OAuth token is `403 invalid_permission` on `name` whatever its scopes.
- Single sign-on is configured in the dashboard only, because it decides who may sign in.

## Endpoints

### `GET /team`

The team this credential belongs to: plan, limits, sender identity, brand kit, AI and SSO.

Retrieve the team:

```sh
curl -X GET "https://api.rasket.com/team" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/team", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/team",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "team",
  "id": "0199c4a2-7b1e-7d30-8f2a-4c6e9b1d3a50",
  "name": "Acme",
  "slug": "acme",
  "created_at": "2026-09-01T09:00:00.000Z",
  "risk_state": "normal",
  "plan": {
    "code": "pro_50k",
    "name": "Pro 50k",
    "limits": {
      "included_emails": 50000,
      "daily_cap": null,
      "domain_limit": 10,
      "webhook_endpoint_limit": 5,
      "retention_days": 30
    }
  },
  "sender_identity": {
    "sender_name": "Acme",
    "postal_address": {
      "line1": "1 Example Street",
      "city": "Springfield",
      "postal_code": "12345",
      "country": "US"
    }
  },
  "ai_assist_enabled": false,
  "brand_kit": {
    "logo_url": "https://cdn.example.com/acme/logo.png",
    "primary_color": "#1a73e8",
    "accent_color": "#f59e0b",
    "font": "system",
    "button_style": "rounded",
    "company_name": "Acme, Inc.",
    "address": "1 Example Street, Springfield 12345",
    "footer_text": "You are receiving this because you have an Acme account."
  },
  "sending": {
    "sandbox_regions": [],
    "tier": 2,
    "tier_name": "paid",
    "next_unlock": null
  },
  "sso": null
}
```

- `sso` is `null` until the team has a single sign-on connection. When it has one, it is a summary — `status`, `issuer`, `client_id`, `enforced` and `domains` — and never the client secret.
- `sending` is where the team stands on the sending ladder: `tier` 0–3 (`new`, `verified`, `paid`, `trusted`) bounds daily and monthly volume together with the plan, and `next_unlock` names the one thing that raises it (`verify_domain`, `add_payment_method`, `operator_review`) or is `null`.
- The member list is not here: it is `GET /team/members`.
- Reachable with a `full_access` key, or an OAuth token holding `team:read`.

### `PATCH /team`

Rename the team, change the sender identity broadcasts print, switch AI assist, or set the brand your templates wear.

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `name` | string | The team's display name. Trimmed, one line, 1 to 80 characters. It does not have to be unique — two teams may share a name. Renaming needs a team admin. |
| `sender_name` | string | The name broadcast footers print. One line, at most 200 characters. |
| `postal_address` | object | `{ line1, line2, city, state, postal_code, country }`, with `country` an upper-case ISO 3166-1 alpha-2 code. Every broadcast footer prints it. |
| `ai_assist_enabled` | boolean | Turn AI assist on or off. Turning it on sends the content you ask about to the model provider; recipient addresses are never included. `true` needs AI assist to be configured for the account and is `503 service_unavailable` when it is not; `false` always works. |
| `brand_kit` | object | `{ logo_url, primary_color, accent_color, font, button_style, company_name, address, footer_text }` — the design your templates wear. Sent **whole**: every field is required, and a field sent as `null` goes back to our default. `logo_url` is an https link to your wide logo — upload one with `POST /team/brand-kit/uploads`; the two colours are six-digit hex; `font` is one of `system`, `helvetica`, `arial`, `verdana`, `georgia`, `times`, `courier`; `button_style` is `square`, `rounded` or `pill`; the three text fields are one line each. |

Update the team:

```sh
curl -X PATCH "https://api.rasket.com/team" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Acme Billing"
}'
```

```ts
const response = await fetch("https://api.rasket.com/team", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Acme Billing"
  }),
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.patch(
    "https://api.rasket.com/team",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "name": "Acme Billing"
  },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "team",
  "id": "0199c4a2-7b1e-7d30-8f2a-4c6e9b1d3a50",
  "name": "Acme Billing",
  "slug": "acme",
  "created_at": "2026-09-01T09:00:00.000Z",
  "risk_state": "normal",
  "plan": {
    "code": "pro_50k",
    "name": "Pro 50k",
    "limits": {
      "included_emails": 50000,
      "daily_cap": null,
      "domain_limit": 10,
      "webhook_endpoint_limit": 5,
      "retention_days": 30
    }
  },
  "sender_identity": {
    "sender_name": "Acme",
    "postal_address": {
      "line1": "1 Example Street",
      "city": "Springfield",
      "postal_code": "12345",
      "country": "US"
    }
  },
  "ai_assist_enabled": false,
  "brand_kit": {
    "logo_url": "https://cdn.example.com/acme/logo.png",
    "primary_color": "#1a73e8",
    "accent_color": "#f59e0b",
    "font": "system",
    "button_style": "rounded",
    "company_name": "Acme, Inc.",
    "address": "1 Example Street, Springfield 12345",
    "footer_text": "You are receiving this because you have an Acme account."
  },
  "sending": {
    "sandbox_regions": [],
    "tier": 2,
    "tier_name": "paid",
    "next_unlock": null
  },
  "sso": null
}
```

- Every field is optional and at least one is required. A team with no postal address yet must send one before `sender_name` alone is accepted.
- `brand_kit` fills `{{brand.logo_url}}`, `{{brand.primary_color}}`, `{{brand.accent_color}}`, `{{brand.font}}`, `{{brand.button_radius}}`, `{{brand.company_name}}`, `{{brand.address}}` and `{{brand.footer_text}}` in a template, at render time, from this one place. They are not template variables: you never send them with an email, and a caller cannot override one for a single send. A token whose field you leave `null` renders as our neutral default, never as visible text.
- Renaming does not change the team's `slug`. The slug is the team's web address, it is set when the team is created, and links you have already shared keep working.
- Only a team admin may rename a team. A `full_access` key is its team's admin; an OAuth token is `403 invalid_permission` on `name` whatever scopes it holds.
- Reachable with a `full_access` key, or an OAuth token holding `team:write`.

### `POST /team/brand-kit/uploads`

Host a PNG, JPG or SVG and get the URL to use as your brand kit's logo.

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `content` (required) | string | The image, base64-encoded. A PNG or JPG up to 256 KB and 4096 pixels on each side, or an SVG up to 32 KB with no script, links, animation or references to other files. The type is read from the file itself. |

Upload a brand kit image:

```sh
curl -X POST "https://api.rasket.com/team/brand-kit/uploads" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "content": "iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAIAAADfzGvmAAAAJElEQVR42mOQKn4xqBDDqINGHTTqoFEHjTpo1EGjDhp1EH0RAEATXz1jjnFXAAAAAElFTkSuQmCC"
}'
```

```ts
const response = await fetch("https://api.rasket.com/team/brand-kit/uploads", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAIAAADfzGvmAAAAJElEQVR42mOQKn4xqBDDqINGHTTqoFEHjTpo1EGjDhp1EH0RAEATXz1jjnFXAAAAAElFTkSuQmCC"
  }),
});

const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://api.rasket.com/team/brand-kit/uploads",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "content": "iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAIAAADfzGvmAAAAJElEQVR42mOQKn4xqBDDqINGHTTqoFEHjTpo1EGjDhp1EH0RAEATXz1jjnFXAAAAAElFTkSuQmCC"
  },
)

print(response.json())
```

#### Response `201`

```json
{
  "object": "brand_kit_upload",
  "url": "https://share.example.com/brand-kit/0199c4a2-7b1e-7d30-8f2a-4c6e9b1d3a50/43e1e7051d7d473c40eff09f9eb77fe830bb3ba32e8861e7ffe6c9cfb9e2ede5.png",
  "content_type": "image/png",
  "bytes": 93,
  "sha256": "43e1e7051d7d473c40eff09f9eb77fe830bb3ba32e8861e7ffe6c9cfb9e2ede5",
  "width": 48,
  "height": 16
}
```

- This hosts the file and does not change your brand kit. To use it, send the `url` as `brand_kit.logo_url` in `PATCH /team`.
- EXIF, XMP and text metadata is removed from a PNG or JPG before it is stored. An SVG is stored exactly as sent, or refused with one reason per problem.
- Uploading the same file again returns the same URL. A file stays online after you replace or remove your logo, because emails you already sent still show it. A project can upload up to 100 different files.
- PNG and JPG show in every mail app. Some mail apps do not show SVG images.
- Reachable with a `full_access` key, or an OAuth token holding `team:write`.

### `GET /team/members`

The people on the team, with their role and whether they use MFA.

List team members:

```sh
curl -X GET "https://api.rasket.com/team/members" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/team/members", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/team/members",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "team_member",
      "id": "0199c4a2-7b1e-7d30-8f2a-4c6e9b1d3a51",
      "user_id": "0199c4a2-7b1e-7d30-8f2a-4c6e9b1d3a52",
      "email": "ronald.williams@example.com",
      "name": "Ronald Williams",
      "role": "admin",
      "mfa_enabled": true,
      "sso_exempt": false,
      "created_at": "2026-09-01T09:00:00.000Z"
    }
  ]
}
```

- A `full_access` key only. No OAuth scope reaches this route: who is on a team is never shared with a connected app.
