Current Issue: JWT tokens are designed for the Word add-in client, not for third-party API consumers.
Needed:
Implementation Priority: HIGH
Use Cases:
Needed:
Implementation Priority: MEDIUM
Benefits:
Implementation Priority: LOW (REST is sufficient for now)
Needed:
Implementation Priority: MEDIUM
Examples:
Implementation Priority: MEDIUM
---
Database Schema:
CREATE TABLE ApiKeys (
Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
UserId UNIQUEIDENTIFIER NOT NULL,
KeyName NVARCHAR(255) NOT NULL,
KeyHash NVARCHAR(255) NOT NULL, -- bcrypt hash
KeyPrefix NVARCHAR(20) NOT NULL, -- First 8 chars for identification
Status NVARCHAR(50) DEFAULT 'active', -- active, revoked, expired
Scopes NVARCHAR(MAX), -- JSON array of permissions
RateLimitTier NVARCHAR(50) DEFAULT 'standard',
LastUsedAt DATETIME2,
CreatedAt DATETIME2 DEFAULT GETDATE(),
ExpiresAt DATETIME2,
FOREIGN KEY (UserId) REFERENCES Users(Id)
);
CREATE TABLE ApiKeyUsage (
Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
ApiKeyId UNIQUEIDENTIFIER NOT NULL,
Endpoint NVARCHAR(255),
RequestCount INT DEFAULT 1,
Date DATE DEFAULT CAST(GETDATE() AS DATE),
FOREIGN KEY (ApiKeyId) REFERENCES ApiKeys(Id)
);
API Key Format:
dp_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
dp_ (Data Publisher)
live_ or test_
Endpoints to Add:
POST /api/keys - Create new API key
GET /api/keys - List user's API keys
GET /api/keys/:id - Get key details (not the key itself)
PUT /api/keys/:id - Update key (name, scopes)
DELETE /api/keys/:id - Revoke key
POST /api/keys/:id/rotate - Rotate key (invalidate & create new)
GET /api/keys/:id/usage - Get usage statistics
Authentication Middleware Update:
// Accept JWT OR API Key
export const authenticate = async (req, res, next) => {
const authHeader = req.headers.authorization;
if (authHeader.startsWith('Bearer dp_')) {
// API Key authentication
const apiKey = authHeader.substring(7);
const user = await validateApiKey(apiKey);
if (!user) return res.status(401).json({ error: 'Invalid API key' });
req.user = user;
req.authMethod = 'apikey';
} else {
// JWT authentication (existing)
passport.authenticate('jwt', { session: false })(req, res, next);
req.authMethod = 'jwt';
}
next();
};
Permission Scopes:
{
"scopes": [
"data:read",
"data:write",
"email:send",
"campaigns:read",
"campaigns:write",
"templates:read",
"templates:write",
"analytics:read",
"users:read",
"users:write"
]
}
Example Usage:
// Middleware to check scopes
export const requireScope = (scope: string) => {
return (req, res, next) => {
if (req.authMethod === 'jwt') {
// JWT has full access
return next();
}
if (!req.user.scopes.includes(scope)) {
return res.status(403).json({
error: 'Insufficient permissions',
required: scope
});
}
next();
};
};
// Usage in routes
router.post('/email-campaigns',
authenticate,
requireScope('campaigns:write'),
createCampaign
);
Tiers:
const RATE_LIMITS = {
free: {
requests: 1000, // per day
campaigns: 5, // per month
dataFiles: 10 // total
},
standard: {
requests: 10000,
campaigns: 50,
dataFiles: 100
},
premium: {
requests: 100000,
campaigns: 500,
dataFiles: 1000
},
enterprise: {
requests: 'unlimited',
campaigns: 'unlimited',
dataFiles: 'unlimited'
}
};
Features Needed:
Tech Stack:
---
enum WebhookEvent {
// Campaign Events
CAMPAIGN_STARTED = 'campaign.started',
CAMPAIGN_COMPLETED = 'campaign.completed',
CAMPAIGN_FAILED = 'campaign.failed',
// Email Events
EMAIL_SENT = 'email.sent',
EMAIL_DELIVERED = 'email.delivered',
EMAIL_OPENED = 'email.opened',
EMAIL_CLICKED = 'email.clicked',
EMAIL_BOUNCED = 'email.bounced',
EMAIL_UNSUBSCRIBED = 'email.unsubscribed',
// Data sync Events
SYNC_COMPLETED = 'sync.completed',
SYNC_FAILED = 'sync.failed',
// Document Events
DOCUMENT_GENERATED = 'document.generated',
DOCUMENT_FAILED = 'document.failed'
}
{
"event": "campaign.completed",
"timestamp": "2026-02-12T10:30:00Z",
"id": "event-uuid",
"data": {
"campaignId": "campaign-uuid",
"campaignName": "Welcome Campaign",
"sentCount": 150,
"failedCount": 2,
"completedAt": "2026-02-12T10:30:00Z"
},
"signature": "sha256-hmac-signature"
}
POST /api/webhooks - Create webhook
GET /api/webhooks - List webhooks
GET /api/webhooks/:id - Get webhook
PUT /api/webhooks/:id - Update webhook
DELETE /api/webhooks/:id - Delete webhook
POST /api/webhooks/:id/test - Test webhook
GET /api/webhooks/:id/deliveries - Delivery history
POST /api/webhooks/:id/retry - Retry failed delivery
HMAC Signature:
// Server generates signature
const signature = crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(payload))
.digest('hex');
// Client verifies
const receivedSignature = req.headers['x-webhook-signature'];
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(receivedSignature)
);
---
Stick with REST for now, add GraphQL later if needed. REST is:
---
/api/v2/email-campaigns
Header Versioning:
Accept: application/vnd.datapublisher.v2+json
Query Parameter:
/api/email-campaigns?version=2
Continue with URL versioning. Only version endpoints that change incompatibly. Most changes should be additive (new optional fields, new endpoints).
---
X-API-Deprecation: true
---
// Cache frequently accessed data
app.get('/api/email-templates', async (req, res) => {
const cached = await redis.get(templates:${userId});
if (cached) return res.json(JSON.parse(cached));
const templates = await getTemplates(userId);
await redis.setex(templates:${userId}, 3600, JSON.stringify(templates));
res.json(templates);
});
-- Add indexes for common queries
CREATE INDEX IX_EmailCampaigns_UserId ON EmailCampaigns(UserId);
CREATE INDEX IX_EmailCampaigns_Status ON EmailCampaigns(Status);
CREATE INDEX IX_EmailTracking_CampaignId ON EmailTracking(CampaignId);
CREATE INDEX IX_DataFiles_UserId_CreatedAt ON DataFiles(UserId, CreatedAt DESC);
Use Bull Queue for long-running operations:
import Queue from 'bull';
const emailQueue = new Queue('email-sending', redisConfig);
emailQueue.process(async (job) => {
const { campaignId } = job.data;
await sendCampaign(campaignId);
});
// In API
router.post('/campaigns/:id/send', async (req, res) => {
const job = await emailQueue.add({ campaignId: req.params.id });
res.json({ jobId: job.id });
});
---
---
---
---
- Per request?
- Per email sent?
- Monthly subscription tiers?
- Generous enough for trials
- Limited enough to encourage upgrades
- Dedicated IP ranges?
- Custom rate limits?
- On-premise deployment?
- Revenue sharing?
- Co-marketing?
---
For API development questions: