Organization API Tokens
Organization API Tokens (OTK) provide org-level programmatic access to TeamDay services. Unlike Personal Access 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
- Open Organization Settings (gear icon in sidebar)
- Click the Org Tokens tab
- Click Generate Token
- Enter a descriptive name (e.g., “Newsletter Sync”, “Signup Webhook”)
- Select scopes (which services the token can access)
- Choose an expiry (default: 90 days, or unlimited)
- Copy the token immediately — it’s shown only once
2. Use the Token
Include your token in the Authorization header:
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:
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 fromtd_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:
{
"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
POST /api/organizations/{orgId}/tokens
Request body:
{
"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:
{
"token": "otk_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdE",
"id": "abc123",
"name": "Newsletter Sync",
"scopes": ["newsletter"],
"expiresAt": "2026-05-25T00:00:00.000Z"
}
List Tokens
GET /api/organizations/{orgId}/tokens
Response:
{
"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
POST /api/organizations/{orgId}/tokens/{tokenId}/revoke
Response:
{
"success": true
}
Integration Examples
Add a Newsletter Subscriber
curl -X POST https://cc.teamday.ai/api/services/newsletter/subscribers \
-H "Authorization: Bearer $TEAMDAY_ORG_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"name": "Jane Doe",
"tags": ["website-signup"]
}'
Sync Contacts from a Signup Form
// 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
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\[email protected],Jane Doe,vip;beta\[email protected],John Smith,beta"
}'
Send a Test Email
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": "[email protected]",
"subject": "Test from TeamDay Newsletter",
"html": "<h1>Hello!</h1><p>Your newsletter integration is working.</p>"
}'
Webhook Handler (Node.js)
// 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
allwhennewsletteris 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
{
"statusCode": 401,
"message": "Unauthorized. Invalid or expired organization token"
}
Causes: Token doesn’t exist, was revoked, or has expired.
403 — Scope Denied
{
"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
{
"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 — User-level API tokens
- Authentication — Overview of all auth methods
- Newsletter API — Newsletter service integration guide
- API Endpoints — Full API reference