🔐 API KEY SYSTEM - COMPLETE EXPLANATION
For Business Integrations Like "WE ARE HYBRID. IN"
Date: October 2, 2025
🎯 HOW IT ALL WORKS
The Complete Flow
1. Business Owner (admin@legacy-integration.cloud) logs into JINBO
2. Creates brand: "WE ARE HYBRID. IN"
3. Gets API key: jinbo_live_abc123...
4. Uses API key to:
✅ Create classes programmatically
✅ Invite/assign coaches
✅ Enroll students
✅ Track progress
✅ Access analytics
🔑 TWO AUTHENTICATION OPTIONS
Option 1: JWT Token (Human Users)
When to use: Interactive sessions, web dashboard, mobile app
// 1. User logs in
POST https://legacy-integration.cloud/auth/login
{
"email": "admin@legacy-integration.cloud",
"password": "secure_password"
}
// 2. Get JWT token
Response: { "token": "eyJhbGc..." }
// 3. Use token in API calls
GET https://jinbo.life/api/classes
Headers: { "Authorization": "Bearer eyJhbGc..." }
Pros:
- ✅ Secure (short-lived, 15 min cache)
- ✅ User identity tracked
- ✅ No key management needed
Cons:
- ❌ Expires (need to re-login)
- ❌ Not ideal for automation
Option 2: API Key (Server-to-Server)
When to use: Automation, integrations, scripts, cron jobs
// 1. Generate API key (one-time)
POST https://jinbo.life/api/auth/api-key
Headers: { "Authorization": "Bearer JWT_TOKEN" }
Body: {
"name": "WE ARE HYBRID Production Key",
"scopes": ["classes:write", "coaches:write", "students:write"]
}
// 2. Get API key (save it!)
Response: { "api_key": "jinbo_live_1234567890abcdef..." }
// 3. Use API key forever
GET https://jinbo.life/api/classes
Headers: { "X-API-Key": "jinbo_live_1234567890abcdef..." }
Pros:
- ✅ Never expires (unless revoked)
- ✅ Perfect for automation
- ✅ Fine-grained permissions (scopes)
Cons:
- ❌ Must store securely
- ❌ Can't be recovered if lost
🏗️ DATABASE STRUCTURE
Brands Table (Your Business)
CREATE TABLE brands (
id UUID PRIMARY KEY,
name VARCHAR(255), -- "WE ARE HYBRID. IN"
domain VARCHAR(255), -- "wearehybrid.in"
owner_email VARCHAR(255), -- "admin@legacy-integration.cloud"
settings JSONB, -- Timezone, currency, etc.
created_at TIMESTAMP
);
API Keys Table (Your Authentication)
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
brand_id UUID, -- Links to brands table
key_hash VARCHAR(64), -- SHA-256 hash (secure!)
name VARCHAR(255), -- "Production API Key"
scopes JSONB, -- ["classes:write", "students:read"]
created_at TIMESTAMP,
last_used_at TIMESTAMP, -- Track usage
expires_at TIMESTAMP, -- Optional expiration
revoked_at TIMESTAMP -- Soft delete
);
Security: We NEVER store the actual API key in plain text, only SHA-256 hash!
Classes Table (Your Coaching Sessions)
CREATE TABLE classes (
id UUID PRIMARY KEY,
brand_id UUID, -- Your brand
name VARCHAR(255), -- "Yoga for Beginners"
description TEXT,
skill_id UUID, -- What skill (yoga, pilates, etc.)
level VARCHAR(50), -- beginner/intermediate/advanced
max_students INTEGER, -- Class capacity
duration_minutes INTEGER, -- Session length
coach_id UUID, -- Assigned coach
location_id UUID, -- Where it happens
schedule JSONB, -- Recurring schedule
pricing JSONB, -- Price info
status VARCHAR(50), -- active/deleted
created_at TIMESTAMP
);
Class Enrollments (Students in Classes)
CREATE TABLE class_enrollments (
id UUID PRIMARY KEY,
class_id UUID, -- Which class
student_id UUID, -- Which student
status VARCHAR(50), -- active/inactive/completed
enrolled_at TIMESTAMP,
UNIQUE(class_id, student_id) -- Can't enroll twice
);
🔐 HOW AUTHENTICATION WORKS
Request Flow
1. Request comes in
↓
2. Check for X-API-Key header
↓
3. If API key present:
- Hash the key (SHA-256)
- Look up in api_keys table
- Verify not revoked/expired
- Get brand_id
- Set req.user and req.brandId
↓
4. If no API key, check Authorization header
- Extract JWT token
- Validate with Legacy Integration
- Get user info
- Set req.user and req.brandId
↓
5. If neither: return 401 Unauthorized
↓
6. Check permissions (role + scopes)
↓
7. Execute request with brand context
Code: Authentication Middleware
// File: src/api/middleware/api-key-auth.js
function apiKeyAuth(db) {
return async (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return next(); // Try JWT auth instead
}
// Hash the provided key
const keyHash = crypto
.createHash('sha256')
.update(apiKey)
.digest('hex');
// Look up in database
const result = await db.query(`
SELECT
ak.id, ak.brand_id, ak.scopes,
b.name as brand_name, b.owner_email
FROM api_keys ak
JOIN brands b ON ak.brand_id = b.id
WHERE ak.key_hash = $1
AND ak.revoked_at IS NULL
AND (ak.expires_at IS NULL OR ak.expires_at > NOW())
`, [keyHash]);
if (result.rows.length === 0) {
return res.status(401).json({
error: 'Invalid API key'
});
}
// Set user context
const keyData = result.rows[0];
req.user = {
id: `api_key_${keyData.id}`,
email: keyData.owner_email,
name: keyData.brand_name,
role: 'admin', // API keys get admin access
brand_id: keyData.brand_id,
api_key_scopes: keyData.scopes,
auth_method: 'api_key'
};
req.brandId = keyData.brand_id;
next();
};
}
🎨 SCOPE-BASED PERMISSIONS
Available Scopes
const AVAILABLE_SCOPES = {
// Classes
'classes:read': 'View classes',
'classes:write': 'Create/update/delete classes',
// Coaches
'coaches:read': 'View coaches',
'coaches:write': 'Invite/assign coaches',
// Students
'students:read': 'View students',
'students:write': 'Add/enroll students',
// Sessions
'sessions:read': 'View sessions',
'sessions:write': 'Create/track sessions',
// Analytics
'analytics:read': 'View reports',
// Wildcard (full access)
'*': 'Full access to everything'
};
Checking Scopes in Routes
// File: src/api/routes/classes.js
router.post('/classes',
requireAuth, // Must be authenticated
requireRole('admin'), // Must have admin role (if JWT)
requireScope('classes:write'), // Must have this scope (if API key)
async (req, res) => {
// Create class logic
}
);
How it works:
- JWT users: Checked by role (admin, coach, student)
- API key users: Checked by scope (classes:write, etc.)
- Super admin: Bypasses all checks
📝 COMPLETE EXAMPLE: WE ARE HYBRID. IN
Step 1: Create the Brand
# 1. Login as admin@legacy-integration.cloud
curl -X POST https://legacy-integration.cloud/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@legacy-integration.cloud",
"password": "your_password"
}'
# Response: { "token": "eyJhbGc..." }
Step 2: Register Business in JINBO
# 2. Create brand
curl -X POST https://jinbo.life/api/brands \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{
"name": "WE ARE HYBRID. IN",
"domain": "wearehybrid.in",
"owner_email": "admin@legacy-integration.cloud",
"settings": {
"timezone": "Asia/Kolkata",
"currency": "INR"
}
}'
# Response:
{
"success": true,
"brand": {
"id": "brand_abc123",
"name": "WE ARE HYBRID. IN",
"domain": "wearehybrid.in"
}
}
Step 3: Generate API Key
# 3. Generate API key
curl -X POST https://jinbo.life/api/auth/api-key \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{
"name": "WE ARE HYBRID Production",
"scopes": ["*"]
}'
# Response:
{
"api_key": "jinbo_live_1234567890abcdef",
"brand_id": "brand_abc123",
"scopes": ["*"],
"created_at": "2025-10-02T02:00:00Z"
}
# ⚠️ SAVE THIS! You'll never see it again.
Step 4: Create Classes via API
# 4. Create yoga class
curl -X POST https://jinbo.life/api/classes \
-H "X-API-Key: jinbo_live_1234567890abcdef" \
-H "Content-Type: application/json" \
-d '{
"name": "Morning Yoga Flow",
"description": "Energizing vinyasa flow for all levels",
"skill_id": "skill_yoga",
"level": "beginner",
"max_students": 15,
"duration_minutes": 60,
"schedule": {
"days": ["monday", "wednesday", "friday"],
"time": "07:00",
"timezone": "Asia/Kolkata"
},
"pricing": {
"amount": 5000,
"currency": "INR",
"billing_cycle": "monthly"
}
}'
# Response:
{
"success": true,
"class": {
"id": "class_xyz789",
"name": "Morning Yoga Flow",
"max_students": 15,
"enrolled_students": 0,
"coach_id": null,
"status": "active"
}
}
Step 5: Invite Coach
# 5. Invite coach
curl -X POST https://jinbo.life/api/users/invite \
-H "X-API-Key: jinbo_live_1234567890abcdef" \
-H "Content-Type: application/json" \
-d '{
"email": "priya@wearehybrid.in",
"name": "Priya Sharma",
"role": "coach",
"skills": ["yoga", "pilates"]
}'
# Response:
{
"success": true,
"user": {
"id": "user_coach1",
"email": "priya@wearehybrid.in",
"name": "Priya Sharma",
"role": "coach"
}
}
Step 6: Assign Coach to Class
# 6. Assign coach
curl -X POST https://jinbo.life/api/classes/class_xyz789/assign-coach \
-H "X-API-Key: jinbo_live_1234567890abcdef" \
-H "Content-Type: application/json" \
-d '{
"coach_id": "user_coach1"
}'
# Response:
{
"success": true,
"class": {
"id": "class_xyz789",
"name": "Morning Yoga Flow",
"coach": {
"id": "user_coach1",
"name": "Priya Sharma",
"email": "priya@wearehybrid.in"
}
},
"assigned_at": "2025-10-02T02:00:00Z"
}
🛡️ SECURITY FEATURES
1. Key Hashing
// We NEVER store plain text keys
const apiKey = 'jinbo_live_1234567890abcdef';
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
// Store keyHash in database, not apiKey!
2. Brand Isolation
-- Every query is scoped to brand
SELECT * FROM classes WHERE brand_id = $1;
-- RLS (Row Level Security) enforces this at database level
CREATE POLICY classes_brand_isolation ON classes
USING (brand_id = current_setting('app.brand_id')::UUID);
3. Scope Checking
// API keys can only do what they're allowed
if (!scopes.includes('*') && !scopes.includes('classes:write')) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
4. Audit Logging
-- Every API key usage is logged
INSERT INTO api_key_logs (
api_key_id, endpoint, method, status_code, ip_address
) VALUES ($1, $2, $3, $4, $5);
5. Revocation
// Instantly disable compromised keys
UPDATE api_keys
SET revoked_at = NOW()
WHERE id = 'compromised_key_id';
📊 API KEY MANAGEMENT
List All Keys
GET https://jinbo.life/api/auth/api-keys
Headers: { "Authorization": "Bearer JWT_TOKEN" }
Response:
{
"keys": [
{
"id": "key1",
"name": "Production Key",
"scopes": ["*"],
"created_at": "2025-10-01",
"last_used_at": "2025-10-02T01:30:00Z"
},
{
"id": "key2",
"name": "Read-Only Key",
"scopes": ["classes:read", "students:read"],
"created_at": "2025-10-01",
"last_used_at": "2025-10-02T00:15:00Z"
}
]
}
Revoke Key
DELETE https://jinbo.life/api/auth/api-keys/key1
Headers: { "Authorization": "Bearer JWT_TOKEN" }
Response:
{
"success": true,
"message": "API key revoked successfully"
}
View Usage Stats
GET https://jinbo.life/api/auth/api-keys/key1/stats
Headers: { "Authorization": "Bearer JWT_TOKEN" }
Response:
{
"total_requests": 1543,
"last_30_days": 892,
"endpoints": {
"/api/classes": 650,
"/api/students": 320,
"/api/sessions": 122
},
"last_used": "2025-10-02T01:30:00Z"
}
🎯 SUMMARY FOR WE ARE HYBRID. IN
What You Get:
- ✅ Your own brand in JINBO system
- ✅ API key for automation (
jinbo_live_...) - ✅ Full API access to:
- Create/manage classes
- Invite/assign coaches
- Enroll students
- Track progress
- Access analytics
How to Use It:
// Simple! Just add X-API-Key header
const response = await fetch('https://jinbo.life/api/classes', {
method: 'POST',
headers: {
'X-API-Key': 'jinbo_live_YOUR_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'New Class',
skill_id: 'yoga',
level: 'beginner'
})
});
Security:
- ✅ Keys are hashed (SHA-256)
- ✅ Brand isolated (can't access other brands)
- ✅ Scope-based permissions
- ✅ Audit logged
- ✅ Can be revoked instantly
Support:
- 📖 Full docs:
/docs/API_INTEGRATION_GUIDE_BUSINESS.md - 🔧 API routes:
/src/api/routes/classes.js - 🗄️ Database:
/database/migrations/025_api_keys_and_classes_update.sql - 🔐 Middleware:
/src/api/middleware/api-key-auth.js
Ready to integrate! 🚀
Last Updated: October 2, 2025 - 02:10 UTC