Docs · api

Authentication

How to authenticate with the Teamday API using service tokens, browser sessions, and OAuth for MCP clients.

Teamday supports three authentication paths:

MethodUse case
Service tokenServer-side scripts, CI, local operator agents, and production automation
Browser sessionTeamday web app requests from a signed-in user
OAuthMCP clients using authorization code with PKCE or device authorization; Teamday CLI device login

For most API integrations, use a service token:

Authorization: Bearer <teamday-service-token>

Service Tokens

Create service tokens from Teamday's Security or API access settings, or through the API when signed in:

POST /api/service-tokens
Content-Type: application/json

{
  "name": "Production support integration",
  "scopes": ["agents:read", "chats:write", "jobs:read"]
}

The response includes the token once. Store it in a secret manager or environment variable.

export TEAMDAY_TOKEN="<teamday-service-token>"

Use it on every request:

curl https://app.teamday.ai/api/agents \
  -H "Authorization: Bearer $TEAMDAY_TOKEN"

Service tokens are intentionally integration-specific:

  • Create one token per automation or agent runner.
  • Tokens are bound to the issuing user and organization.
  • Tokens can be scoped by resource family and action.
  • File and folder endpoints take absolute organization paths; access stays inside the token's organization and the agent's read/write paths.
  • Revoke a single token without affecting other integrations.
  • Use X-Request-Id and token last_used_at to audit activity.

Supported service-token scopes:

ScopePurpose
*Full access for legacy or owner-managed integrations
agents:read, agents:writeRead or mutate agents
chats:read, chats:writeRead chats or send/create chat work
jobs:read, jobs:writeRead jobs or approve/cancel/resume/iterate work
missions:read, missions:writeRead or mutate scheduled work
files:read, files:writeRead or mutate files, folders, projects, repositories, and apps
resources:read, resources:writeRead or mutate data resources and batch/query surfaces
webhooks:read, webhooks:writeRead or mutate event delivery configuration and signing secrets
tokens:read, tokens:writeRead or mutate service tokens
mcpUse Teamday MCP tools

When a bearer service token lacks a required scope, Teamday returns 403 insufficient_scope with required_scopes in the error details and a WWW-Authenticate scope hint.

The required scope for each operation is machine-readable in https://app.teamday.ai/api/openapi.json:

  • Every protected operation includes x-teamday-required-scopes.
  • The top-level x-teamday-service-token-scope-matrix lists method, path, operation ID, and required scopes.
  • Protected operations declare 403 ForbiddenInsufficientScope and 429 RateLimited responses.

OAuth For MCP Clients

MCP clients should discover Teamday's OAuth metadata:

https://app.teamday.ai/.well-known/oauth-authorization-server
https://app.teamday.ai/.well-known/oauth-protected-resource

Supported scope:

ScopePurpose
mcpUse Teamday MCP tools for the authorized organization and user

Teamday supports authorization-code OAuth with PKCE for dynamic MCP clients, and device authorization for clients that implement the device grant. Adding device authorization to the server does not change how an existing MCP client logs in; clients using browser callbacks continue to use that flow.

OAuth metadata advertises:

  • authorization_endpoint
  • token_endpoint
  • registration_endpoint
  • device_authorization_endpoint
  • grant_types_supported: authorization code and urn:ietf:params:oauth:grant-type:device_code
  • code_challenge_methods_supported: ["S256"]
  • resource metadata through /.well-known/oauth-protected-resource

Device Authorization

Device authorization connects a remote terminal without a local browser callback or port forwarding. For the Teamday CLI, run:

teamday login --device --server=https://app.teamday.ai

Keep the command running, open the displayed link on any device, and sign in. Review the application, organization, access, and matching terminal code before selecting Approve connection. The CLI finishes connecting and saves its credentials. See the CLI guide for installation and credential management.

The CLI uses the built-in teamday-cli client with scope cli. Successful login creates a full-access (*) service token bound to your user and the displayed organization. It has no automatic expiry; revoke it in Settings → Service tokens. This login does not configure another application's MCP credentials.

Implementing An MCP Device Client

Teamday implements the OAuth device authorization grant. Discover endpoints from the authorization-server metadata above. A device client must register through the registration_endpoint; a client metadata URL used for authorization-code login is not sufficient.

Register a public, device-only client without a redirect URI:

POST /register
Content-Type: application/json

{
  "grant_types": ["urn:ietf:params:oauth:grant-type:device_code"],
  "token_endpoint_auth_method": "none",
  "scope": "mcp"
}

Use the returned client_id to request a code. Send this request and token polling requests as form-encoded bodies:

POST /oauth/device/code
Content-Type: application/x-www-form-urlencoded

client_id=<registered-client-id>&scope=mcp

The response contains device_code, user_code, verification_uri, verification_uri_complete, expires_in (600 seconds), and interval (5 seconds). Show the verification link and user code to the user; keep device_code private. Opening verification_uri_complete prefills the code but still requires review and approval in the browser.

After waiting at least interval seconds, poll the token endpoint:

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&client_id=<registered-client-id>&device_code=<device-code>

Handle polling responses as follows:

ResponseClient action
authorization_pendingContinue polling at the current interval.
slow_downIncrease the interval by 5 seconds for this and all subsequent polls.
access_deniedStop; access was declined or is no longer allowed.
expired_tokenStop; start a new device authorization if the user wants to retry.
invalid_grantStop; the code is unknown, already consumed, or belongs to another client.

Reduce polling frequency after connection timeouts, and stop when the code expires. A successful response returns access_token, token_type: "Bearer", scope: "mcp", and expires_in. Unlike CLI service tokens, these MCP OAuth access tokens expire after the returned expires_in duration. No refresh token is issued; authorize again after expiry. Each device code can be exchanged successfully only once.

Security Defaults

  • Service tokens are hashed at rest.
  • Service-token scopes are enforced before route handlers for bearer-authenticated API calls.
  • Tokens can be revoked without deleting the user.
  • Token plaintext is returned only once at creation.
  • User-bound OAuth tokens preserve the authorizing user and organization boundary.
  • MCP resource access is scoped to the authorized organization and user.
  • API responses include X-Request-Id for audit and support correlation.
  • Webhook signing secrets rotate independently from API tokens.

Token Rotation

Use one token per integration so it can be revoked independently. Rotate production tokens on a schedule and immediately after exposure.

Recommended pattern:

  1. Create a replacement token.
  2. Deploy the replacement secret.
  3. Verify the integration works.
  4. Revoke the old token.

For first-class rotation, call:

POST /api/service-tokens/{id}/rotate
Content-Type: application/json

{
  "name": "Production support integration rotated",
  "scopes": ["agents:read", "chats:write", "jobs:read"]
}

The response returns the replacement token once and immediately revokes the old token.

To narrow a token without rotating it:

PATCH /api/service-tokens/{id}
Content-Type: application/json

{
  "scopes": ["jobs:read"]
}

Authentication Errors

Missing or invalid credentials return 401. Include the X-Request-Id response header when debugging.

{
  "code": "unauthorized",
  "message": "Authentication failed.",
  "status": 401,
  "request_id": "req_..."
}

Older endpoints may return a compact legacy body such as { "error": "not authenticated" } while the route surface is being normalized.