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

FeatureOrg Token (otk_)Personal Token (td_)
ScopeOrganization-levelUser-level
ManagementAny org memberOnly the creator
Service scopingRestrict to specific services (e.g., Newsletter only)Full access matching user permissions
Use caseService integrations, webhooks, signup formsCI/CD, scripts, agent authentication
IdentityNot tied to a specific userInherits creator’s permissions
Unlimited expirySupportedNot 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:

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 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.

ScopeAccess
allFull access to every service
newsletterSubscribers, 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

DurationUse case
7 daysTesting and development
30 daysShort-term integrations
90 daysRecommended for most integrations
180 daysLong-running integrations
365 daysAnnual rotations
UnlimitedPermanent 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
}
FieldTypeDefaultDescription
namestringrequiredDescriptive name (1-100 chars)
scopesstring[]["all"]Services this token can access
expiresInDaysnumber90Days 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 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

{
  "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.