Secure your API requests with Twenty’s authentication system supporting API keys, OAuth tokens, and JWT.
Authentication Methods
Twenty supports multiple authentication methods:
API Keys Best for server-to-server integrations and scripts
OAuth Tokens For user-authorized third-party applications
JWT Tokens Session-based authentication for web applications
Personal Access Tokens Long-lived tokens for personal use
API Keys
API keys provide programmatic access to your workspace.
Creating API Keys
Navigate to Settings → API & Webhooks
Click Create API Key
Give it a descriptive name
Copy the key immediately (it’s only shown once)
API keys grant full access to your workspace. Store them securely and never commit them to version control.
Using API Keys
Include the API key in the Authorization header:
curl https://api.twenty.com/graphql \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ people { edges { node { id firstName } } } }"}'
API Key Best Practices
Environment Variables Store API keys in environment variables, never hardcode
Separate Keys Use different keys for development, staging, and production
Rotate Regularly Rotate keys periodically and when team members leave
Least Privilege Use workspace permissions to limit API key access
Revoking API Keys
Go to Settings → API & Webhooks
Find the key to revoke
Click Delete
Confirm deletion
Revoking an API key immediately invalidates it. Any applications using that key will receive 401 errors.
OAuth Authentication
For applications that act on behalf of users.
OAuth Flow
Step 1: Authorization Request
Redirect user to Twenty’s authorization endpoint:
https://api.twenty.com/auth/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=https://your-app.com/callback&
response_type=code&
scope=read:people write:people
Your OAuth application’s client ID
URL to redirect to after authorization
Must be code for authorization code flow
Space-separated list of permissions requested
Optional state parameter for CSRF protection
Step 2: Handle Callback
Twenty redirects to your callback URL with an authorization code:
https://your-app.com/callback?code=AUTH_CODE&state=STATE_VALUE
Step 3: Exchange Code for Token
curl -X POST https://api.twenty.com/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://your-app.com/callback"
}'
Response:
{
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"refresh_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "Bearer" ,
"expires_in" : 1800
}
Step 4: Use Access Token
curl https://api.twenty.com/graphql \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ people { edges { node { id firstName } } } }"}'
Refresh Tokens
Access tokens expire after 30 minutes. Use refresh token to get new access token:
curl -X POST https://api.twenty.com/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
JWT Tokens
For web application sessions.
Login with Credentials
curl -X POST https://api.twenty.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected] ",
"password": "password"
}'
Response:
{
"accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"refreshToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"user" : {
"id" : "user-id" ,
"email" : "[email protected] " ,
"firstName" : "John" ,
"lastName" : "Doe"
}
}
JWT Structure
Twenty JWTs contain:
{
"sub" : "user-id" ,
"workspaceId" : "workspace-id" ,
"email" : "[email protected] " ,
"iat" : 1709546400 ,
"exp" : 1709548200
}
Token Expiration
Default token lifetimes:
Access Token - 30 minutes
Refresh Token - 90 days
Login Token - 15 minutes
File Token - 1 day
Password Reset Token - 5 minutes
Configure via environment variables:
ACCESS_TOKEN_EXPIRES_IN = 30m
REFRESH_TOKEN_EXPIRES_IN = 90d
LOGIN_TOKEN_EXPIRES_IN = 15m
FILE_TOKEN_EXPIRES_IN = 1d
PASSWORD_RESET_TOKEN_EXPIRES_IN = 5m
Permissions
Workspace Roles
API access is controlled by workspace roles:
Admin - Full access to all data and settings
Member - Access to assigned records
Custom Roles - Granular permission control
Permission Flags
API keys inherit permissions from the workspace:
READ - Read access to objects
WRITE - Create and update records
DELETE - Delete records
API_KEYS_AND_WEBHOOKS - Manage API keys and webhooks
Check Permissions
query GetCurrentUser {
currentUser {
id
email
role
permissions {
object
actions
}
}
}
Rate Limiting
Default Limits
100 requests per minute per API key
Shared across GraphQL and REST APIs
Per workspace rate limits
Adjust via environment variables:
API_RATE_LIMITING_TTL = 60000 # Window in milliseconds
API_RATE_LIMITING_LIMIT = 100 # Requests per window
Every response includes rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1709546460
Handle 429 Errors
async function makeRequestWithRateLimit ( fn ) {
try {
return await fn ();
} catch ( error ) {
if ( error . response ?. status === 429 ) {
const resetTime = error . response . headers [ 'x-ratelimit-reset' ];
const waitTime = ( resetTime * 1000 ) - Date . now () + 1000 ; // +1s buffer
console . log ( `Rate limited. Waiting ${ waitTime } ms` );
await new Promise ( resolve => setTimeout ( resolve , waitTime ));
return await fn (); // Retry
}
throw error ;
}
}
Security Best Practices
Use HTTPS Only
Always use HTTPS in production to encrypt API keys in transit
Store Secrets Securely
Use environment variables or secret managers, never hardcode
Rotate Keys Regularly
Change API keys periodically and when team members leave
Monitor Usage
Track API usage to detect unauthorized access
Principle of Least Privilege
Grant minimum permissions necessary
Secure Storage
// Use environment variables
const apiKey = process . env . TWENTY_API_KEY ;
// Or use dotenv
require ( 'dotenv' ). config ();
const apiKey = process . env . TWENTY_API_KEY ;
Secret Managers
AWS Secrets Manager
HashiCorp Vault
Azure Key Vault
const AWS = require ( 'aws-sdk' );
const secretsManager = new AWS . SecretsManager ();
async function getApiKey () {
const secret = await secretsManager . getSecretValue ({
SecretId: 'twenty-api-key' ,
}). promise ();
return JSON . parse ( secret . SecretString ). apiKey ;
}
OAuth Implementation
Build OAuth-based integrations:
Complete OAuth Example
const express = require ( 'express' );
const session = require ( 'express-session' );
const axios = require ( 'axios' );
const app = express ();
app . use ( session ({
secret: 'your-session-secret' ,
resave: false ,
saveUninitialized: false ,
}));
const TWENTY_AUTH_URL = 'https://api.twenty.com/auth/authorize' ;
const TWENTY_TOKEN_URL = 'https://api.twenty.com/auth/token' ;
const CLIENT_ID = process . env . TWENTY_CLIENT_ID ;
const CLIENT_SECRET = process . env . TWENTY_CLIENT_SECRET ;
const REDIRECT_URI = 'https://your-app.com/callback' ;
// Step 1: Redirect to Twenty
app . get ( '/connect' , ( req , res ) => {
const state = generateRandomString ();
req . session . oauthState = state ;
const authUrl = ` ${ TWENTY_AUTH_URL } ? ${
new URLSearchParams ({
client_id: CLIENT_ID ,
redirect_uri: REDIRECT_URI ,
response_type: 'code' ,
scope: 'read:people write:people' ,
state ,
})
} ` ;
res . redirect ( authUrl );
});
// Step 2: Handle callback
app . get ( '/callback' , async ( req , res ) => {
const { code , state } = req . query ;
// Verify state
if ( state !== req . session . oauthState ) {
return res . status ( 403 ). send ( 'Invalid state' );
}
try {
// Exchange code for tokens
const response = await axios . post ( TWENTY_TOKEN_URL , {
grant_type: 'authorization_code' ,
code ,
client_id: CLIENT_ID ,
client_secret: CLIENT_SECRET ,
redirect_uri: REDIRECT_URI ,
});
const { access_token , refresh_token } = response . data ;
// Store tokens securely
req . session . accessToken = access_token ;
req . session . refreshToken = refresh_token ;
res . send ( 'Connected successfully!' );
} catch ( error ) {
console . error ( 'OAuth error:' , error );
res . status ( 500 ). send ( 'Authentication failed' );
}
});
// Use access token
app . get ( '/people' , async ( req , res ) => {
const accessToken = req . session . accessToken ;
if ( ! accessToken ) {
return res . redirect ( '/connect' );
}
try {
const response = await axios . post (
'https://api.twenty.com/graphql' ,
{
query: '{ people { edges { node { id firstName } } } }' ,
},
{
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
},
}
);
res . json ( response . data );
} catch ( error ) {
if ( error . response ?. status === 401 ) {
// Token expired, try refresh
return res . redirect ( '/refresh' );
}
throw error ;
}
});
function generateRandomString () {
return Math . random (). toString ( 36 ). substring ( 7 );
}
JWT Validation
For custom authentication flows:
Verify JWT Token
const jwt = require ( 'jsonwebtoken' );
function verifyToken ( token , appSecret ) {
try {
const decoded = jwt . verify ( token , appSecret );
return {
valid: true ,
userId: decoded . sub ,
workspaceId: decoded . workspaceId ,
};
} catch ( error ) {
if ( error . name === 'TokenExpiredError' ) {
return { valid: false , reason: 'expired' };
}
return { valid: false , reason: 'invalid' };
}
}
Middleware Example
function authenticateJWT ( req , res , next ) {
const authHeader = req . headers . authorization ;
if ( ! authHeader || ! authHeader . startsWith ( 'Bearer ' )) {
return res . status ( 401 ). json ({ error: 'Missing authorization header' });
}
const token = authHeader . substring ( 7 );
const result = verifyToken ( token , process . env . APP_SECRET );
if ( ! result . valid ) {
return res . status ( 401 ). json ({ error: `Invalid token: ${ result . reason } ` });
}
req . userId = result . userId ;
req . workspaceId = result . workspaceId ;
next ();
}
app . use ( '/api' , authenticateJWT );
Multi-Workspace Authentication
For applications supporting multiple workspaces:
class TwentyClient {
constructor () {
this . workspaces = new Map ();
}
// Add workspace credentials
addWorkspace ( workspaceId , apiKey ) {
this . workspaces . set ( workspaceId , {
apiKey ,
client: axios . create ({
baseURL: 'https://api.twenty.com' ,
headers: {
'Authorization' : `Bearer ${ apiKey } ` ,
},
}),
});
}
// Get client for workspace
getClient ( workspaceId ) {
const workspace = this . workspaces . get ( workspaceId );
if ( ! workspace ) {
throw new Error ( `Workspace ${ workspaceId } not configured` );
}
return workspace . client ;
}
// Make request to specific workspace
async query ( workspaceId , query , variables ) {
const client = this . getClient ( workspaceId );
const response = await client . post ( '/graphql' , {
query ,
variables ,
});
return response . data ;
}
}
// Usage
const twentyClient = new TwentyClient ();
twentyClient . addWorkspace ( 'workspace-1' , 'api-key-1' );
twentyClient . addWorkspace ( 'workspace-2' , 'api-key-2' );
const people1 = await twentyClient . query ( 'workspace-1' , PEOPLE_QUERY );
const people2 = await twentyClient . query ( 'workspace-2' , PEOPLE_QUERY );
Troubleshooting
Causes:
API key is invalid or revoked
API key not in Authorization header
Token expired
Solutions:
Verify API key is correct
Check header format: Authorization: Bearer KEY
Refresh token if expired
Generate new API key if needed
Causes:
Insufficient permissions
Workspace role doesn’t allow operation
Custom permissions not granted
Solutions:
Check user role in workspace settings
Request admin to grant permissions
Verify API key has required permissions
Token expires too quickly
Solutions:
Implement token refresh logic
Use refresh tokens to get new access tokens
Increase token lifetime in server config (if self-hosting)
Cause:
CORS policy prevents browser requestsSolution:
Make API requests from your backend, not browser:Browser -> Your Backend -> Twenty API
This also keeps API keys secure.
Testing Authentication
Test API Key
# Simple test query
curl https://api.twenty.com/graphql \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}'
# Should return: {"data": {"__typename": "Query"}}
Test OAuth Flow
Navigate to /connect in your application
Authorize the application
Verify redirect to callback with code
Check tokens are received and stored
Make test API request
Next Steps
GraphQL API Learn the GraphQL API
JavaScript SDK SDK with built-in auth
Webhooks Secure webhook endpoints