Skip to main content

Authentication

MeetLoyd supports multiple authentication methods to fit your security requirements.

Authentication Methods

MethodBest ForSecurity Level
JWT TokensWeb/mobile appsHigh
API KeysServer-to-serverHigh
SSO/SAMLEnterpriseVery High
OAuth 2.0Third-party appsHigh

JWT Authentication

Login Flow

// 1. User logs in
const response = await fetch('https://app.meetloyd.com/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@company.com',
password: 'secret'
})
});

const { accessToken, refreshToken, expiresIn } = await response.json();

// 2. Use access token for requests
const agents = await fetch('https://app.meetloyd.com/api/v1/agents', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});

// 3. Refresh when expired
const refreshResponse = await fetch('https://app.meetloyd.com/api/v1/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${refreshToken}`
}
});

Token Lifecycle

Login → Access Token (15 min) → Refresh → New Access Token
Refresh Token (7 days)
TokenLifetimePurpose
Access Token15 minutesAPI requests
Refresh Token7 daysGet new access token

Token Contents

Access tokens contain:

{
"sub": "user-123",
"email": "user@company.com",
"tenantId": "tenant-456",
"role": "admin",
"permissions": ["agent.create", "agent.read", "..."],
"iat": 1705312200,
"exp": 1705313100
}

API Keys

For server-to-server communication:

Create API Key

const key = await client.apiKeys.create({
name: 'Production Server',
permissions: ['agent.read', 'agent.chat'],
expiresAt: '2025-01-01' // Optional
});

console.log(key.key); // sk_live_xxxx... (shown once)
console.log(key.id); // For management

Using API Keys

// In Authorization header
const response = await fetch('https://app.meetloyd.com/api/v1/agents', {
headers: {
'Authorization': 'Bearer sk_live_xxxx...'
}
});

// Or as query parameter (not recommended)
const response = await fetch('https://app.meetloyd.com/api/v1/agents?api_key=sk_live_xxxx...');

API Key Best Practices

// ✅ Good: Limited permissions, named clearly
await client.apiKeys.create({
name: 'prod-web-readonly',
permissions: ['agent.read', 'conversation.read']
});

// ✅ Good: Set expiration
await client.apiKeys.create({
name: 'temp-integration',
expiresAt: '2024-03-01'
});

// ❌ Bad: Full permissions, no name
await client.apiKeys.create({
permissions: ['*']
});

Rotate API Keys

// Create new key before revoking old one
const newKey = await client.apiKeys.create({
name: 'Production Server v2',
permissions: ['agent.read', 'agent.chat']
});

// Update your servers with new key
// ...

// Revoke old key
await client.apiKeys.revoke('old-key-id');

List and Manage Keys

// List all keys
const keys = await client.apiKeys.list();

for (const key of keys.data) {
console.log({
id: key.id,
name: key.name,
lastUsed: key.lastUsedAt,
createdAt: key.createdAt
});
}

// Revoke a key
await client.apiKeys.revoke('key-123');

Multi-Factor Authentication (MFA)

Enable MFA

// Generate TOTP secret
const setup = await client.auth.setupMfa();

console.log(setup.secret); // Base32 secret
console.log(setup.qrCode); // QR code URL for authenticator app

// Verify and enable
await client.auth.enableMfa({
code: '123456' // From authenticator app
});

Login with MFA

// Step 1: Initial login
const loginResult = await client.auth.login({
email: 'user@company.com',
password: 'secret'
});

if (loginResult.mfaRequired) {
// Step 2: Verify MFA
const tokens = await client.auth.verifyMfa({
sessionToken: loginResult.sessionToken,
code: '123456'
});
}

Recovery Codes

// Generate recovery codes during MFA setup
const codes = await client.auth.generateRecoveryCodes();

// Returns 10 single-use codes
// [
// "XXXX-XXXX-XXXX",
// "YYYY-YYYY-YYYY",
// ...
// ]

// Use recovery code when locked out
await client.auth.verifyMfa({
sessionToken: loginResult.sessionToken,
recoveryCode: 'XXXX-XXXX-XXXX'
});

Require MFA for Roles

// Admin setting
await client.settings.update({
authentication: {
mfaRequired: true, // All users
// Or specific roles
mfaRequiredRoles: ['owner', 'admin']
}
});

Role-Based Access Control (RBAC)

MeetLoyd provides 106 granular permissions across 15 categories for enterprise-grade access control.

Built-in Roles

RoleDescriptionScopePermission Count
ownerFull access, billing, tenant managementWorkspaceAll 106
adminFull access except billing, tenant deletionWorkspace~95
ai_engineerTechnical focus: agents, tools, workflowsWorkspace~70
memberStandard access for daily operationsTeam-scoped~45
viewerRead-only accessTeam-scoped~35

Role Hierarchy

owner


admin


ai_engineer


member


viewer

Assign Roles

// Invite user with role
await client.users.invite({
email: 'newuser@company.com',
role: 'member',
teamIds: ['team-123'] // Optional: restrict to teams
});

// Change user role
await client.users.updateRole('user-123', {
role: 'admin'
});

Granular Permissions

MeetLoyd provides 106 granular permissions organized into categories:

Permission Format

{resource}:{action}

Examples:
- agents:read, agents:write, agents:delete, agents:execute
- workflows:read, workflows:write, workflows:delete, workflows:execute
- schedules:read, schedules:write, schedules:execute
- access-reviews:read, access-reviews:write, access-reviews:approve

Permission Categories

CategoryPermissionsDescription
Core Platform59agents, apps, teams, users, billing, tools, workflows, etc.
Tier 1 - Critical15secrets, schedules, memory, charters, avatars
Tier 2 - Compliance14governance, audit-alerts, retention, siem, security, access-reviews
Tier 3 - Operational18scim, sessions, conversations, skills, delegation, voice, manifests

Check Permissions

// Check if user has permission
const canCreate = await client.auth.hasPermission('agent.create');

// Get all permissions
const permissions = await client.auth.getPermissions();

Custom Permission Sets

// Create API key with specific permissions
await client.apiKeys.create({
name: 'Chat Only',
permissions: [
'agent.read',
'agent.chat',
'conversation.read',
'conversation.create'
]
});

Tenant Isolation

Multi-Tenant Architecture

Each organization (tenant) is completely isolated:

Tenant A                    Tenant B
┌─────────────┐ ┌─────────────┐
│ Users │ │ Users │
│ Agents │ │ Agents │
│ Data │ │ Data │
│ Settings │ │ Settings │
└─────────────┘ └─────────────┘
↓ ↓
Database A Database B
(isolated) (isolated)

Tenant Context

Every API request is scoped to a tenant:

// From JWT claims
{
"tenantId": "tenant-123",
"sub": "user-456"
}

// All queries automatically filtered
SELECT * FROM agents WHERE tenant_id = 'tenant-123'

Cross-Tenant Access

Not allowed by default. For enterprise multi-org:

// Admin can access child organizations
const childOrgs = await client.organizations.listChildren();

Session Management

Active Sessions

// List user's active sessions
const sessions = await client.auth.listSessions();

for (const session of sessions) {
console.log({
id: session.id,
device: session.deviceInfo,
ip: session.ipAddress,
lastActive: session.lastActiveAt,
createdAt: session.createdAt
});
}

// Revoke a session
await client.auth.revokeSession('session-123');

// Revoke all sessions (logout everywhere)
await client.auth.revokeAllSessions();

Session Security

// Admin settings
await client.settings.update({
sessions: {
maxActiveSessions: 5,
sessionTimeout: '24h',
idleTimeout: '1h',
requireReauthFor: ['billing', 'security-settings']
}
});

IP Allowlisting

Enterprise feature to restrict access by IP:

await client.settings.update({
security: {
ipAllowlist: {
enabled: true,
addresses: [
'203.0.113.0/24', // Office network
'198.51.100.10' // VPN exit
],
bypassForSso: false // Also applies to SSO
}
}
});

Password Policies

await client.settings.update({
authentication: {
passwordPolicy: {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventReuse: 5, // Can't reuse last 5 passwords
maxAge: 90 // Days until password expires
}
}
});

Authentication Troubleshooting

"Invalid Token"

  1. Check token hasn't expired
  2. Verify token is for correct environment (live vs test)
  3. Ensure token is properly formatted in header

"Permission Denied"

  1. Check user's role has required permission
  2. Verify API key includes needed permissions
  3. Check team membership for scoped resources

"Account Locked"

After too many failed attempts:

// Admin can unlock
await client.users.unlock('user-123');

Agent Identity

For agent-to-agent authentication and delegation, MeetLoyd provides a separate identity stack based on SPIFFE, W3C Verifiable Credentials, and OAuth 2.0 Token Exchange. This goes beyond human user authentication — agents get cryptographically verifiable identities, signed capability badges, and short-lived delegation tokens.

See Agent Identity for the full agent identity documentation.


Next: Learn about Agent Identity for agent-to-agent trust, or SSO & SCIM for enterprise identity management.