API Key Security
Complete guide to securing and managing API keys for the SCORM API.
Table of Contents
API Key Overview
What are API Keys?
API keys are long-lived credentials that provide programmatic access to the SCORM API. They are:
- Tenant-specific (one key per tenant)
- Scope-based (
public_api,internal_product, oradmin) - Hashed in storage (SHA-256, never plain text)
- Revocable at any time
Key Format
API keys follow this format:
ac_live_550e8400e29b41d4a716446655440000
- Prefix:
ac_live_(production) orac_test_(test) - Length: 40 characters (8-character prefix + 32-character UUID hex without hyphens)
- Format: Hex-encoded UUID (without hyphens)
Creating API Keys
Via Dashboard
- Sign in to your account
- Navigate to Dashboard → Integrations → API Keys
- Click "Create API Key"
- Configure:
- Name: Descriptive name (e.g., "Production API Key")
- Scope: Select the appropriate scope (
public_api,internal_product, oradmin) - Expires In (optional): Days until expiration
- Click "Create"
- Important: Copy key immediately - it won't be shown again!
Via Admin Script
Use the Convex CLI to mint a key for a tenant directly. The scope must be one of public_api, internal_product, or admin:
npx convex run connectApiKeys:createApiKey \
--tenantExternalId "550e8400-e29b-41d4-a716-446655440000" \
--label "Production API Key" \
--scope "public_api"
Output:
✅ API Key created successfully!
Key: ac_live_550e8400e29b41d4a716446655440000
Tenant: 550e8400-e29b-41d4-a716-446655440000
Scope: public_api
⚠️ IMPORTANT: Save this key securely - it will not be shown again!
Via API
curl -X POST https://app.allureconnect.com/api/admin/api-keys \
-H "X-API-Key: ac_live_your-admin-key" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production API Key",
"scope": "public_api",
"expires_in_days": 90
}'
Securing API Keys
Storage
❌ Never Do This:
// ❌ Hardcoded in code
const apiKey = 'ac_live_abc123...';
// ❌ In version control
// .env (committed to git)
CONNECT_API_KEY=ac_live_abc123...
✅ Always Do This:
// ✅ Environment variable
const apiKey = process.env.CONNECT_API_KEY;
if (!apiKey) {
throw new Error('API key not configured');
}
Environment Variables
Development:
# .env.local (gitignored)
CONNECT_API_KEY=ac_test_abc123...
Production:
# Use secret management
# AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id connect-api-key
# Vercel
vercel env add CONNECT_API_KEY production
# Docker
docker run -e CONNECT_API_KEY="ac_live_..." your-image
Secret Management Services
Recommended:
- AWS Secrets Manager
- Google Cloud Secret Manager
- Azure Key Vault
- HashiCorp Vault
- 1Password Secrets Automation
Example (AWS Secrets Manager):
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' });
const secret = await client.getSecretValue({
SecretId: 'connect-api-key'
});
const apiKey = JSON.parse(secret.SecretString).apiKey;
Key Rotation
Why Rotate?
- Security: Limits exposure if key is compromised
- Compliance: Meets security requirements
- Best Practice: Regular rotation reduces risk
Rotation Schedule
Recommended:
- Production Keys: Every 90 days
- Development Keys: Every 180 days
- After Compromise: Immediately
Rotation Process
Step 1: Create New Key
Create a replacement key via Dashboard → Integrations → API Keys or the admin API (see Creating API Keys). Use the same scope as the key being replaced (e.g., public_api).
Step 2: Update Integrations
Update all systems using the old key:
// Update environment variable
process.env.CONNECT_API_KEY = newApiKey;
// Test new key
const test = await fetch('/api/health', {
headers: { 'X-API-Key': newApiKey }
});
Step 3: Revoke Old Key
# Via dashboard or API
DELETE /api/admin/api-keys/{old_key_id}
Step 4: Monitor
- Check for errors from old key
- Verify all systems using new key
- Monitor for unauthorized access
Automated Rotation
async function rotateApiKey(tenantId: string, oldKey: { id: string; scope: 'public_api' | 'internal_product' | 'admin' }) {
// 1. Create new key with the same scope as the key being replaced
const newKey = await createApiKey(tenantId, {
name: `Rotated Key - ${new Date().toISOString()}`,
scope: oldKey.scope
});
// 2. Update all integrations (your system)
await updateIntegrations(newKey.id);
// 3. Wait for propagation (e.g., 24 hours)
await delay(24 * 60 * 60 * 1000);
// 4. Revoke old key
await revokeApiKey(oldKey.id);
}
Key Scopes
Available Scopes
| Scope | Description | Use Case |
|---|---|---|
public_api |
Standard partner access — upload, launch, sessions, dispatches, webhooks | Most integrations |
internal_product |
Internal product/service access (elevated operations) | Platform services |
admin |
System administration | Admin-only tooling |
Fine-grained scopes may be added in future releases (CALE).
Principle of Least Privilege
Best Practice:
- Use
public_apifor standard integrations and partner access - Use
internal_productonly for internal platform services - Use
adminonly for administrative tooling
Example:
// Standard partner integration
const integrationKey = {
scope: 'public_api'
};
// Admin operations
const adminKey = {
scope: 'admin'
};
Best Practices
1. Use Different Keys Per Environment
# Development
CONNECT_API_KEY_DEV=ac_test_abc123...
# Staging
CONNECT_API_KEY_STAGING=ac_test_def456...
# Production
CONNECT_API_KEY_PROD=ac_live_ghi789...
2. Name Keys Descriptively
# Good names
"Production API Key - Integration Service"
"Development API Key - Testing"
"Monitoring API Key - Read Only"
# Bad names
"Key 1"
"API Key"
"Test"
3. Monitor Key Usage
// Track API key usage
async function trackApiKeyUsage(apiKey: string) {
const usage = await getApiKeyUsage(apiKey);
// Alert on unusual activity
if (usage.requestsPerMinute > 100) {
alert('Unusual API key activity detected');
}
}
4. Set Expiration Dates
// Create key with expiration
const key = await createApiKey({
tenant_id: tenantId,
name: 'Temporary Integration Key',
scope: 'public_api',
expires_in_days: 30 // Expires in 30 days
});
5. Revoke Unused Keys
// Regularly audit and revoke unused keys
async function auditApiKeys(tenantId: string) {
const keys = await listApiKeys(tenantId);
for (const key of keys) {
const lastUsed = await getLastUsed(key.id);
const daysSinceUse = (Date.now() - lastUsed) / (1000 * 60 * 60 * 24);
if (daysSinceUse > 90) {
await revokeApiKey(key.id);
console.log(`Revoked unused key: ${key.name}`);
}
}
}
6. Implement Key Validation
// Validate API key before use
function validateApiKey(apiKey: string): boolean {
// Check format
if (!apiKey.startsWith('ac_live_') && !apiKey.startsWith('ac_test_')) {
return false;
}
// Check minimum length (8-char prefix + 32-char UUID hex = 40 total)
if (apiKey.length < 40) {
return false;
}
return true;
}
Security Checklist
- API keys stored in environment variables
- Keys never committed to version control
- Different keys for each environment
- Keys rotated every 90 days
- Minimum required scopes used
- Unused keys revoked
- Key usage monitored
- Expiration dates set
- Keys named descriptively
- Secret management service used
Related Documentation
- Security Overview - Complete security guide
- Data Isolation - Tenant isolation
- Authentication Guide - Auth details
Last Updated: 2025-01-15