API Keys
Secure programmatic access with organization-scoped API keys, authentication, and management.
By the end of this guide, you'll have set up API keys for secure programmatic access to your organization's resources, with proper authentication, scoping, and management capabilities.
Overview
The SaaS Boilerplate includes a comprehensive API key system that enables secure programmatic access to organization resources without requiring user authentication. Key features include:
- Organization scoping: API keys are tied to specific organizations with proper data isolation
- Secure generation: Cryptographically secure keys with 'sk_' prefix for easy identification
- Flexible expiration: Keys can never expire or have custom expiration dates
- Enable/disable control: Instantly revoke access by disabling keys
- Bearer token authentication: Standard HTTP Authorization header authentication
- Role-based access: API keys inherit organization permissions and require role validation
- Audit trail: Creation notifications and proper logging for security monitoring
- One-time display: Keys are shown only once during creation for security
The system integrates deeply with the authentication layer, automatically creating virtual sessions for API key requests while maintaining organization context and billing information.
Architecture
API Key Service
The API key system consists of several integrated components:
// src/@saas-boilerplate/features/api-key/procedures/api-key.procedure.ts
const ApiKeyFeatureProcedure = igniter.procedure({
handler: async (_, { context }) => ({
apikey: {
create: async (input) => {
// Generate secure key: sk_ + 32 random bytes
const apiKey = `sk_${crypto.randomBytes(32).toString('hex')}`
return context.services.database.apiKey.create({ data: { ...input, key: apiKey } })
},
findManyByOrganization: async (orgId) => { /* ... */ },
update: async (params) => { /* ... */ },
delete: async (params) => { /* ... */ }
}
})
})Authentication Integration
API keys are validated through the authentication procedure, which checks for Bearer tokens when no regular session exists:
// src/@saas-boilerplate/features/auth/procedures/auth.procedure.ts
getSession: async (options) => {
// Check for API key in Authorization header
const authHeader = request.headers.get('Authorization')
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7)
const apiKey = await context.services.database.apiKey.findUnique({
where: { key: token, enabled: true },
include: { organization: true }
})
if (apiKey) {
// Create virtual session with organization context
return { user: null, organization: apiKey.organization }
}
}
}Database Schema
API keys are stored with organization relationships and security metadata:
Prop
Type
Setting Up API Keys
Access API Keys Management
Navigate to your organization's settings and locate the API Keys section. Only organization owners and admins can access this area.
Create Your First API Key
Click "Create API Key" and provide a descriptive name. Choose whether the key should never expire or set a custom expiration date.
Copy and Store the Key
The API key will be displayed only once for security. Copy it immediately and store it securely - it cannot be retrieved later.
Configure Expiration (Optional)
Set an expiration date if you want the key to automatically become invalid, or leave it as "never expires" for permanent access.
Backend Usage (Procedures & Controllers)
Creating API Keys Programmatically
Use the API key procedure to create keys in your business logic. Note that ApiKeyFeatureProcedure must be included in the controller's use array to access context.apikey:
// In a controller or procedure
export const createApiKeyEndpoint = igniter.mutation({
use: [ApiKeyFeatureProcedure(), AuthFeatureProcedure()],
handler: async ({ context }) => {
const session = await context.auth.getSession({
requirements: 'authenticated',
roles: ['owner', 'admin']
})
const newApiKey = await context.apikey.create({
description: 'Webhook Integration Key',
neverExpires: false,
expiresAt: new Date('2024-12-31'),
organizationId: session.organization.id
})
// Send notification about new key
await context.services.notification.send({
type: 'API_KEY_CREATED',
context: { organizationId: session.organization.id },
data: {
description: newApiKey.description,
keyPreview: newApiKey.key.slice(-4)
}
})
return response.created(newApiKey)
}
})API Key Authentication in Controllers
Protect endpoints that should accept API key authentication:
// Controller that accepts both user and API key auth
export const protectedEndpoint = igniter.query({
use: [AuthFeatureProcedure()],
handler: async ({ context }) => {
const session = await context.auth.getSession({
requirements: 'authenticated',
roles: ['member', 'admin', 'owner'] // API keys need roles specified
})
// session.organization is available for both user and API key auth
// session.user is null for API key authentication
return { data: session.organization }
}
})Managing API Keys
Retrieve and manage keys for an organization. Remember to include ApiKeyFeatureProcedure in your controller's use array:
// In a controller with ApiKeyFeatureProcedure in use array
const apiKeys = await context.apikey.findManyByOrganization(orgId)
// Update key status
await context.apikey.update({
id: 'key_123',
description: 'Updated description',
enabled: false
})
// Delete a key
await context.apikey.delete({
id: 'key_123',
organizationId: orgId
})Frontend Usage (Client-side)
Listing API Keys
Display organization's API keys with management options:
// Get all API keys for the current organization
const result = await api.apiKey.findManyByOrganization.query()
if (result.error) {
console.error('Failed to load API keys')
} else {
const apiKeys = result.data
// Render keys list with masked values
}Creating New API Keys
Handle API key creation with proper error handling:
const createApiKey = async (description: string, neverExpires: boolean) => {
try {
const result = await api.apiKey.create.mutate({
description,
neverExpires,
enabled: true
})
if (result.error) {
toast.error('Failed to create API key')
return
}
// Show the key only once - copy it immediately
const newKey = result.data
navigator.clipboard.writeText(newKey.key)
toast.success('API key created and copied to clipboard')
// Refresh the list
router.refresh()
} catch (error) {
toast.error('Error creating API key')
}
}Updating API Keys
Modify key properties like description or enabled status:
const updateApiKey = async (id: string, updates: { description?: string, enabled?: boolean }) => {
try {
await api.apiKey.update.mutate({
id,
...updates
})
toast.success('API key updated')
} catch (error) {
toast.error('Failed to update API key')
}
}Deleting API Keys
Permanently remove API keys with confirmation:
const deleteApiKey = async (id: string) => {
if (!confirm('Are you sure you want to delete this API key? This action cannot be undone.')) {
return
}
try {
await api.apiKey.delete.mutate({ id })
toast.success('API key deleted')
router.refresh()
} catch (error) {
toast.error('Failed to delete API key')
}
}API Key Authentication
Using API Keys in Requests
Make authenticated requests using Bearer token authentication:
// Example API request with API key
const response = await fetch('/api/protected-endpoint', {
headers: {
'Authorization': 'Bearer sk_abc123def456...',
'Content-Type': 'application/json'
}
})API Key Session Context
API keys create virtual sessions with organization context:
// API key authenticated session
const session = await context.auth.getSession({
requirements: 'authenticated',
roles: ['member'] // Required for API key auth
})
// Available in API key sessions:
console.log(session.organization) // Full organization object
console.log(session.user) // null (no user for API keys)
console.log(session.organization.billing) // Billing information includedAPI Key Data Structure
ApiKey Interface
Prop
Type
Troubleshooting
Best Practices
See Also
- Authentication & Sessions - How API keys integrate with the authentication system
- Organizations and Tenancy - Organization-scoped API key access
- Notifications - API key creation notifications
- Jobs & Queues - Background processing with API key authentication
API Reference
API Key Endpoints
Prop
Type
Authentication Headers
Prop
Type