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:
| Method | Use case |
|---|---|
| Service token | Server-side scripts, CI, local operator agents, and production automation |
| Browser session | Teamday web app requests from a signed-in user |
| OAuth | MCP 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-Idand tokenlast_used_atto audit activity.
Supported service-token scopes:
| Scope | Purpose |
|---|---|
* | Full access for legacy or owner-managed integrations |
agents:read, agents:write | Read or mutate agents |
chats:read, chats:write | Read chats or send/create chat work |
jobs:read, jobs:write | Read jobs or approve/cancel/resume/iterate work |
missions:read, missions:write | Read or mutate scheduled work |
files:read, files:write | Read or mutate files, folders, projects, repositories, and apps |
resources:read, resources:write | Read or mutate data resources and batch/query surfaces |
webhooks:read, webhooks:write | Read or mutate event delivery configuration and signing secrets |
tokens:read, tokens:write | Read or mutate service tokens |
mcp | Use 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-matrixlists method, path, operation ID, and required scopes. - Protected operations declare
403 ForbiddenInsufficientScopeand429 RateLimitedresponses.
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:
| Scope | Purpose |
|---|---|
mcp | Use 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_endpointtoken_endpointregistration_endpointdevice_authorization_endpointgrant_types_supported: authorization code andurn:ietf:params:oauth:grant-type:device_codecode_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:
| Response | Client action |
|---|---|
authorization_pending | Continue polling at the current interval. |
slow_down | Increase the interval by 5 seconds for this and all subsequent polls. |
access_denied | Stop; access was declined or is no longer allowed. |
expired_token | Stop; start a new device authorization if the user wants to retry. |
invalid_grant | Stop; 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-Idfor 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:
- Create a replacement token.
- Deploy the replacement secret.
- Verify the integration works.
- 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.