# Organization API Tokens

Create scoped API tokens for service integrations, webhooks, and automated workflows at the organization level.

# Organization API Tokens

Organization API Tokens (OTK) provide org-level programmatic access to TeamDay services. Unlike [Personal Access Tokens](https://docs.teamday.ai/api/tokens) which are tied to a specific user, org tokens are shared across your organization and can be restricted to specific services.

---

## When to Use Org Tokens vs PATs

| Feature | Org Token (`otk_`) | Personal Token (`td_`) |
|---------|-------------------|----------------------|
| **Scope** | Organization-level | User-level |
| **Management** | Any org member | Only the creator |
| **Service scoping** | Restrict to specific services (e.g., Newsletter only) | Full access matching user permissions |
| **Use case** | Service integrations, webhooks, signup forms | CI/CD, scripts, agent authentication |
| **Identity** | Not tied to a specific user | Inherits creator's permissions |
| **Unlimited expiry** | Supported | Not supported |

**Use org tokens when:**
- Integrating external services (CRM sync, signup forms, webhooks)
- Building automations that shouldn't be tied to a specific team member
- You want to restrict access to a specific service (e.g., Newsletter only)
- The token needs to outlive any individual team member's tenure

**Use PATs when:**
- Authenticating as yourself in scripts or CLI
- Running CI/CD pipelines tied to your account
- You need access to the full TeamDay API (agents, spaces, executions)

---

## Quick Start

### 1. Generate a Token

1. Open **Organization Settings** (gear icon in sidebar)
2. Click the **Org Tokens** tab
3. Click **Generate Token**
4. Enter a descriptive name (e.g., "Newsletter Sync", "Signup Webhook")
5. Select scopes (which services the token can access)
6. Choose an expiry (default: 90 days, or unlimited)
7. **Copy the token immediately** — it's shown only once

### 2. Use the Token

Include your token in the `Authorization` header:

```bash
curl -H "Authorization: Bearer otk_your-token-here" \
  -H "Content-Type: application/json" \
  https://cc.teamday.ai/api/services/newsletter/subscribers
```

Or store it as an environment variable:

```bash
export TEAMDAY_ORG_TOKEN="otk_your-token-here"

curl -H "Authorization: Bearer $TEAMDAY_ORG_TOKEN" \
  https://cc.teamday.ai/api/services/newsletter/subscribers
```

---

## Token Format

```
otk_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdE
```

- **Prefix:** `otk_` (identifies org tokens, distinct from `td_` PATs)
- **Body:** 43 characters, base64url encoded
- **Storage:** SHA-256 hash stored in database — plaintext never persisted

---

## Scopes

Org tokens support **scopes** that restrict which services the token can access. This follows the principle of least privilege — a token for newsletter sync shouldn't be able to access other services.

| Scope | Access |
|-------|--------|
| `all` | Full access to every service |
| `newsletter` | Subscribers, campaigns, send emails |

More scopes will be added as new services are built (e.g., `seo`, `analytics`, `design-studio`).

**Scope enforcement:** When a scoped token tries to access a service it doesn't have permission for, the API returns:

```json
{
  "statusCode": 403,
  "message": "Token does not have access to the 'seo' service. Required scope: 'seo' or 'all'."
}
```

---

## Token Expiry Options

| Duration | Use case |
|----------|----------|
| 7 days | Testing and development |
| 30 days | Short-term integrations |
| 90 days | **Recommended** for most integrations |
| 180 days | Long-running integrations |
| 365 days | Annual rotations |
| Unlimited | Permanent integrations (use with caution) |

**When a token expires:** API requests return `401 Unauthorized` with the message `"Organization token expired"`. Generate a new token to continue.

---

## API Reference

### Generate Token

```http
POST /api/organizations/{orgId}/tokens
```

**Request body:**

```json
{
  "name": "Newsletter Sync",
  "scopes": ["newsletter"],
  "expiresInDays": 90
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | required | Descriptive name (1-100 chars) |
| `scopes` | string[] | `["all"]` | Services this token can access |
| `expiresInDays` | number | `90` | Days until expiry (0 = unlimited, max 365) |

**Response:**

```json
{
  "token": "otk_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdE",
  "id": "abc123",
  "name": "Newsletter Sync",
  "scopes": ["newsletter"],
  "expiresAt": "2026-05-25T00:00:00.000Z"
}
```

### List Tokens

```http
GET /api/organizations/{orgId}/tokens
```

**Response:**

```json
{
  "tokens": [
    {
      "id": "abc123",
      "name": "Newsletter Sync",
      "tokenPreview": "otk_****a1b2c3d4",
      "scopes": ["newsletter"],
      "createdBy": "user_uid",
      "createdAt": "2026-02-24T00:00:00.000Z",
      "expiresAt": "2026-05-25T00:00:00.000Z",
      "lastUsedAt": "2026-02-24T12:30:00.000Z"
    }
  ],
  "count": 1
}
```

### Revoke Token

```http
POST /api/organizations/{orgId}/tokens/{tokenId}/revoke
```

**Response:**

```json
{
  "success": true
}
```

---

## Integration Examples

### Add a Newsletter Subscriber

```bash
curl -X POST https://cc.teamday.ai/api/services/newsletter/subscribers \
  -H "Authorization: Bearer $TEAMDAY_ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "name": "Jane Doe",
    "tags": ["website-signup"]
  }'
```

### Sync Contacts from a Signup Form

```typescript
// In your website's signup handler
async function onSignup(email: string, name: string) {
  await fetch('https://cc.teamday.ai/api/services/newsletter/subscribers', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.TEAMDAY_ORG_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ email, name, tags: ['signup-form'] }),
  })
}
```

### Bulk Import via CSV

```bash
curl -X POST https://cc.teamday.ai/api/services/newsletter/subscribers/import \
  -H "Authorization: Bearer $TEAMDAY_ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "email,name,tags\njane@example.com,Jane Doe,vip;beta\njohn@example.com,John Smith,beta"
  }'
```

### Send a Test Email

```bash
curl -X POST https://cc.teamday.ai/api/services/newsletter/test/send \
  -H "Authorization: Bearer $TEAMDAY_ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "you@example.com",
    "subject": "Test from TeamDay Newsletter",
    "html": "<h1>Hello!</h1><p>Your newsletter integration is working.</p>"
  }'
```

### Webhook Handler (Node.js)

```typescript
// Express/Fastify handler for syncing new users to newsletter
app.post('/webhook/new-user', async (req, res) => {
  const { email, name } = req.body

  const response = await fetch(
    'https://cc.teamday.ai/api/services/newsletter/subscribers',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.TEAMDAY_ORG_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email, name, tags: ['user'] }),
    }
  )

  if (!response.ok) {
    console.error('Failed to sync subscriber:', await response.text())
  }

  res.json({ ok: true })
})
```

---

## Security Best Practices

- **Use the narrowest scope possible** — don't use `all` when `newsletter` is sufficient
- **Set an expiry** — unlimited tokens are convenient but riskier if compromised
- **Store in environment variables** — never hardcode tokens in source code
- **Use separate tokens per integration** — revoke one without breaking others
- **Monitor "last used" timestamps** — unused tokens may indicate stale integrations
- **Revoke tokens when team members leave** — org tokens aren't tied to a user, but review access regularly

---

## Error Responses

### 401 — Invalid Token

```json
{
  "statusCode": 401,
  "message": "Unauthorized. Invalid or expired organization token"
}
```

**Causes:** Token doesn't exist, was revoked, or has expired.

### 403 — Scope Denied

```json
{
  "statusCode": 403,
  "message": "Token does not have access to the 'seo' service. Required scope: 'newsletter' or 'all'."
}
```

**Causes:** Token's scopes don't include the service you're trying to access.

### 403 — Not an Org Member

```json
{
  "statusCode": 403,
  "message": "Not a member of this organization"
}
```

**Causes:** You're trying to manage tokens for an org you don't belong to.

---

## Related Documentation

- [Personal Access Tokens](https://docs.teamday.ai/api/tokens) — User-level API tokens
- [Authentication](https://docs.teamday.ai/api/authentication) — Overview of all auth methods
- [Newsletter API](https://docs.teamday.ai/guides/newsletter-api) — Newsletter service integration guide
- [API Endpoints](https://docs.teamday.ai/api/endpoints) — Full API reference
