Service API Module
The Service API module (sso.serviceApi) provides write operations for backend service-to-service communication. All methods require API key authentication via the X-Api-Key header and are scoped to the service associated with the API key.
Authentication Setup
To use the Service API, you must first create an API key through the SDK:
// Create an API key (as an admin)
const apiKey = await sso.services.apiKeys.create('acme-corp', 'main-app', {
name: 'Production Backend',
permissions: ['read:users', 'write:users', 'write:subscriptions'],
expires_in_days: 90
});
// IMPORTANT: Store this key securely - it's only shown once!
console.log('API Key:', apiKey.key);
Then initialize the SDK with the API key:
import { SsoClient } from '@drmhse/sso-sdk';
const sso = new SsoClient({
baseURL: 'https://sso.example.com',
apiKey: 'sk_live_abcd1234...' // Your API key
});
// Now you can use sso.serviceApi methods
const user = await sso.serviceApi.createUser({ email: 'user@example.com' });
User Management
sso.serviceApi.createUser()
Signature:
createUser(request: CreateUserRequest): Promise<ServiceApiUser>
Description: Create a new user in AuthOS for your service. This is useful for creating users programmatically from your backend, such as during migration or bulk import.
Required Permission: write:users
Parameters:
| Name | Type | Description |
|---|---|---|
| request | CreateUserRequest | User creation request |
| request.email | string | User email address (must be unique) |
Returns: Promise<ServiceApiUser> - The created user object with:
id(string) - Unique user IDemail(string) - User email addresscreated_at(string) - ISO 8601 timestamp when user was created
Example:
// Create a single user
const user = await sso.serviceApi.createUser({
email: 'customer@example.com'
});
console.log(`Created user: ${user.email}`);
console.log(`User ID: ${user.id}`);
// Bulk user import
const emailList = [
'user1@example.com',
'user2@example.com',
'user3@example.com'
];
for (const email of emailList) {
try {
const user = await sso.serviceApi.createUser({ email });
console.log(` Imported ${email}`);
} catch (error) {
console.error(` Failed to import ${email}:`, error.message);
}
}
Throws:
403 Forbidden- API key doesn’t havewrite:userspermission400 Bad Request- Invalid email format or email already exists401 Unauthorized- Invalid or expired API key
Related:
sso.serviceApi.updateUser()
Signature:
updateUser(userId: string, request: UpdateUserRequest): Promise<ServiceApiUser>
Description: Update user details such as email address.
Required Permission: write:users
Parameters:
| Name | Type | Description |
|---|---|---|
| userId | string | User ID to update |
| request | UpdateUserRequest | User update request |
| request.email | string (optional) | New email address |
Returns: Promise<ServiceApiUser> - The updated user object with:
id(string) - User IDemail(string) - Updated email addresscreated_at(string) - ISO 8601 timestamp when user was created
Example:
// Update user email
const updated = await sso.serviceApi.updateUser('user-id-123', {
email: 'newemail@example.com'
});
console.log(`Updated user email to: ${updated.email}`);
// Example: Migrate users to new email domain
const users = await sso.serviceApi.listUsers(); // Note: You'd need read:users permission
for (const user of users) {
if (user.email.endsWith('@olddomain.com')) {
const newEmail = user.email.replace('@olddomain.com', '@newdomain.com');
await sso.serviceApi.updateUser(user.id, { email: newEmail });
console.log(`Migrated: ${user.email} -> ${newEmail}`);
}
}
Throws:
403 Forbidden- API key doesn’t havewrite:userspermission404 Not Found- User doesn’t exist400 Bad Request- Invalid email format or email already in use401 Unauthorized- Invalid or expired API key
Related:
sso.serviceApi.deleteUser()
Signature:
deleteUser(userId: string): Promise<void>
Description: Permanently delete a user from your service. This will also delete all associated data including subscriptions and sessions. This operation cannot be undone.
Required Permission: delete:users
Parameters:
| Name | Type | Description |
|---|---|---|
| userId | string | User ID to delete |
Returns: Promise<void> - No return value on success
Example:
// Delete a user
await sso.serviceApi.deleteUser('user-id-123');
console.log(' User deleted successfully');
// Best practice: Confirm before deleting
async function deleteUserWithConfirmation(userId: string, userEmail: string) {
const confirmed = confirm(
`Are you sure you want to permanently delete ${userEmail}? This cannot be undone.`
);
if (confirmed) {
await sso.serviceApi.deleteUser(userId);
console.log(` Deleted user: ${userEmail}`);
}
}
// Bulk delete inactive users
const inactiveUsers = []; // Users who haven't logged in for 2 years
for (const user of inactiveUsers) {
try {
await sso.serviceApi.deleteUser(user.id);
console.log(` Deleted inactive user: ${user.email}`);
} catch (error) {
console.error(` Failed to delete ${user.email}:`, error.message);
}
}
Throws:
403 Forbidden- API key doesn’t havedelete:userspermission404 Not Found- User doesn’t exist401 Unauthorized- Invalid or expired API key
Related:
Subscription Management
sso.serviceApi.createSubscription()
Signature:
createSubscription(request: CreateSubscriptionRequest): Promise<ServiceApiSubscription>
Description: Create a new subscription for a user. This allows you to programmatically assign users to subscription plans from your backend, useful for payment processor webhooks or administrative actions.
Required Permission: write:subscriptions
Parameters:
| Name | Type | Description |
|---|---|---|
| request | CreateSubscriptionRequest | Subscription creation request |
| request.user_id | string | User ID to create subscription for |
| request.plan_id | string | Subscription plan ID |
| request.status | string (optional) | Initial status (default: ‘active’) |
| request.current_period_end | string (optional) | ISO 8601 date when current period ends |
Returns: Promise<ServiceApiSubscription> - The created subscription object with:
id(string) - Unique subscription IDuser_id(string) - User IDplan_id(string) - Plan IDplan_name(string) - Plan namestatus(string) - Subscription status (‘active’, ‘cancelled’, ’expired’)current_period_end(string) - ISO 8601 timestamp when period ends
Example:
// Create a subscription after payment
const subscription = await sso.serviceApi.createSubscription({
user_id: 'user-id-123',
plan_id: 'plan-pro-monthly',
status: 'active',
current_period_end: '2024-02-15T00:00:00Z'
});
console.log(` Created ${subscription.plan_name} subscription`);
console.log(` Renews: ${new Date(subscription.current_period_end).toLocaleDateString()}`);
// Stripe webhook handler example
async function handleStripePayment(stripeEvent: any) {
if (stripeEvent.type === 'checkout.session.completed') {
const session = stripeEvent.data.object;
// Create SSO subscription after successful payment
const subscription = await sso.serviceApi.createSubscription({
user_id: session.metadata.user_id,
plan_id: session.metadata.plan_id,
status: 'active',
current_period_end: new Date(session.expires_at * 1000).toISOString()
});
console.log(` Subscription created for user ${session.metadata.user_id}`);
}
}
Throws:
403 Forbidden- API key doesn’t havewrite:subscriptionspermission404 Not Found- User or plan doesn’t exist400 Bad Request- Invalid request (user already has subscription, invalid status, etc.)401 Unauthorized- Invalid or expired API key
Related:
sso.serviceApi.updateSubscription()
Signature:
updateSubscription(userId: string, request: UpdateSubscriptionRequest): Promise<ServiceApiSubscription>
Description: Update a user’s subscription status or renewal date. Useful for handling payment renewals, cancellations, or plan changes.
Required Permission: write:subscriptions
Parameters:
| Name | Type | Description |
|---|---|---|
| userId | string | User ID whose subscription to update |
| request | UpdateSubscriptionRequest | Subscription update request |
| request.status | string (optional) | New status (‘active’, ‘cancelled’, ’expired’) |
| request.current_period_end | string (optional) | New period end date (ISO 8601) |
Returns: Promise<ServiceApiSubscription> - The updated subscription object with:
id(string) - Subscription IDuser_id(string) - User IDplan_id(string) - Plan IDplan_name(string) - Plan namestatus(string) - Updated subscription statuscurrent_period_end(string) - Updated period end timestamp
Example:
// Cancel a subscription
const cancelled = await sso.serviceApi.updateSubscription('user-id-123', {
status: 'cancelled'
});
console.log(` Subscription cancelled: ${cancelled.plan_name}`);
// Renew subscription (extend period)
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
const renewed = await sso.serviceApi.updateSubscription('user-id-123', {
status: 'active',
current_period_end: nextMonth.toISOString()
});
console.log(` Subscription renewed until ${nextMonth.toLocaleDateString()}`);
// Stripe webhook handler for renewals
async function handleStripeRenewal(stripeEvent: any) {
if (stripeEvent.type === 'invoice.payment_succeeded') {
const invoice = stripeEvent.data.object;
await sso.serviceApi.updateSubscription(invoice.metadata.user_id, {
status: 'active',
current_period_end: new Date(invoice.period_end * 1000).toISOString()
});
console.log(` Subscription renewed for ${invoice.metadata.user_id}`);
}
}
// Handle failed payment
async function handleStripePaymentFailed(stripeEvent: any) {
if (stripeEvent.type === 'invoice.payment_failed') {
const invoice = stripeEvent.data.object;
await sso.serviceApi.updateSubscription(invoice.metadata.user_id, {
status: 'expired'
});
console.log(`� Subscription expired for ${invoice.metadata.user_id}`);
}
}
Throws:
403 Forbidden- API key doesn’t havewrite:subscriptionspermission404 Not Found- User or subscription doesn’t exist400 Bad Request- Invalid status or date format401 Unauthorized- Invalid or expired API key
Related:
sso.serviceApi.deleteSubscription()
Signature:
deleteSubscription(userId: string): Promise<void>
Description: Permanently delete a user’s subscription. This is a destructive operation and cannot be undone. Consider using updateSubscription() with status ‘cancelled’ instead for better audit trails.
Required Permission: delete:subscriptions
Parameters:
| Name | Type | Description |
|---|---|---|
| userId | string | User ID whose subscription to delete |
Returns: Promise<void> - No return value on success
Example:
// Delete a subscription
await sso.serviceApi.deleteSubscription('user-id-123');
console.log(' Subscription deleted');
// Best practice: Prefer cancelling over deleting
// Instead of deleting:
await sso.serviceApi.deleteSubscription(userId);
// Consider cancelling (maintains history):
await sso.serviceApi.updateSubscription(userId, {
status: 'cancelled'
});
Throws:
403 Forbidden- API key doesn’t havedelete:subscriptionspermission404 Not Found- User or subscription doesn’t exist401 Unauthorized- Invalid or expired API key
Related:
Service Configuration
sso.serviceApi.updateServiceInfo()
Signature:
updateServiceInfo(request: UpdateServiceInfoRequest): Promise<ServiceApiInfo>
Description: Update service configuration details such as the service name. The service is scoped to the API key being used.
Required Permission: write:service
Parameters:
| Name | Type | Description |
|---|---|---|
| request | UpdateServiceInfoRequest | Service update request |
| request.name | string (optional) | New service name |
Returns: Promise<ServiceApiInfo> - The updated service object with:
id(string) - Service IDname(string) - Updated service nameslug(string) - Service slug (read-only)service_type(string) - Service type (‘web’, ‘mobile’, ‘cli’)created_at(string) - ISO 8601 timestamp when service was created
Example:
// Update service name
const updated = await sso.serviceApi.updateServiceInfo({
name: 'My Awesome App v2'
});
console.log(` Service name updated to: ${updated.name}`);
console.log(` Service type: ${updated.service_type}`);
console.log(` Created: ${new Date(updated.created_at).toLocaleDateString()}`);
// Example: Update service name based on environment
const environment = process.env.NODE_ENV;
const serviceName = environment === 'production'
? 'Production App'
: `Development App (${environment})`;
await sso.serviceApi.updateServiceInfo({ name: serviceName });
Throws:
403 Forbidden- API key doesn’t havewrite:servicepermission400 Bad Request- Invalid service name401 Unauthorized- Invalid or expired API key
Related:
Complete Example: Backend Service Integration
Here’s a complete example showing how to integrate the Service API into a backend service:
import { SsoClient } from '@drmhse/sso-sdk';
// Backend service class
class UserService {
private sso: SsoClient;
constructor(apiKey: string) {
this.sso = new SsoClient({
baseURL: process.env.SSO_BASE_URL || 'https://sso.example.com',
apiKey: apiKey
});
}
// Create a new user account
async createAccount(email: string): Promise<string> {
try {
const user = await this.sso.serviceApi.createUser({ email });
console.log(` Created account for ${email}`);
return user.id;
} catch (error) {
console.error(`Failed to create account for ${email}:`, error);
throw new Error('Account creation failed');
}
}
// Update user email
async updateEmail(userId: string, newEmail: string): Promise<void> {
try {
await this.sso.serviceApi.updateUser(userId, { email: newEmail });
console.log(` Updated email for user ${userId}`);
} catch (error) {
console.error(`Failed to update email:`, error);
throw new Error('Email update failed');
}
}
// Delete user account
async deleteAccount(userId: string): Promise<void> {
try {
// First, cancel/delete subscription
await this.sso.serviceApi.deleteSubscription(userId);
// Then delete the user
await this.sso.serviceApi.deleteUser(userId);
console.log(` Deleted account for user ${userId}`);
} catch (error) {
console.error(`Failed to delete account:`, error);
throw new Error('Account deletion failed');
}
}
}
// Subscription service class
class SubscriptionService {
private sso: SsoClient;
constructor(apiKey: string) {
this.sso = new SsoClient({
baseURL: process.env.SSO_BASE_URL || 'https://sso.example.com',
apiKey: apiKey
});
}
// Create subscription after successful payment
async createSubscription(
userId: string,
planId: string,
periodEndDate: Date
): Promise<void> {
try {
const subscription = await this.sso.serviceApi.createSubscription({
user_id: userId,
plan_id: planId,
status: 'active',
current_period_end: periodEndDate.toISOString()
});
console.log(` Created subscription: ${subscription.plan_name}`);
console.log(` Renews: ${new Date(subscription.current_period_end).toLocaleDateString()}`);
} catch (error) {
console.error(`Failed to create subscription:`, error);
throw new Error('Subscription creation failed');
}
}
// Renew subscription after payment
async renewSubscription(userId: string, periodEndDate: Date): Promise<void> {
try {
await this.sso.serviceApi.updateSubscription(userId, {
status: 'active',
current_period_end: periodEndDate.toISOString()
});
console.log(` Renewed subscription for user ${userId}`);
} catch (error) {
console.error(`Failed to renew subscription:`, error);
throw new Error('Subscription renewal failed');
}
}
// Cancel subscription
async cancelSubscription(userId: string): Promise<void> {
try {
await this.sso.serviceApi.updateSubscription(userId, {
status: 'cancelled'
});
console.log(` Cancelled subscription for user ${userId}`);
} catch (error) {
console.error(`Failed to cancel subscription:`, error);
throw new Error('Subscription cancellation failed');
}
}
// Mark subscription as expired (failed payment)
async expireSubscription(userId: string): Promise<void> {
try {
await this.sso.serviceApi.updateSubscription(userId, {
status: 'expired'
});
console.log(`� Expired subscription for user ${userId}`);
} catch (error) {
console.error(`Failed to expire subscription:`, error);
throw new Error('Subscription expiration failed');
}
}
}
// Stripe webhook handler
class StripeWebhookHandler {
private userService: UserService;
private subscriptionService: SubscriptionService;
constructor(apiKey: string) {
this.userService = new UserService(apiKey);
this.subscriptionService = new SubscriptionService(apiKey);
}
async handleEvent(event: any): Promise<void> {
console.log(`Processing Stripe event: ${event.type}`);
switch (event.type) {
case 'checkout.session.completed':
await this.handleCheckoutCompleted(event.data.object);
break;
case 'invoice.payment_succeeded':
await this.handlePaymentSucceeded(event.data.object);
break;
case 'invoice.payment_failed':
await this.handlePaymentFailed(event.data.object);
break;
case 'customer.subscription.deleted':
await this.handleSubscriptionDeleted(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
private async handleCheckoutCompleted(session: any): Promise<void> {
const userId = session.metadata.user_id;
const planId = session.metadata.plan_id;
const periodEnd = new Date(session.expires_at * 1000);
// Create initial subscription
await this.subscriptionService.createSubscription(userId, planId, periodEnd);
}
private async handlePaymentSucceeded(invoice: any): Promise<void> {
const userId = invoice.metadata.user_id;
const periodEnd = new Date(invoice.period_end * 1000);
// Renew subscription
await this.subscriptionService.renewSubscription(userId, periodEnd);
}
private async handlePaymentFailed(invoice: any): Promise<void> {
const userId = invoice.metadata.user_id;
// Mark subscription as expired
await this.subscriptionService.expireSubscription(userId);
}
private async handleSubscriptionDeleted(subscription: any): Promise<void> {
const userId = subscription.metadata.user_id;
// Cancel subscription in SSO
await this.subscriptionService.cancelSubscription(userId);
}
}
// Usage in Express.js
import express from 'express';
const app = express();
const apiKey = process.env.SSO_API_KEY!;
const userService = new UserService(apiKey);
const subscriptionService = new SubscriptionService(apiKey);
const webhookHandler = new StripeWebhookHandler(apiKey);
// Create user endpoint
app.post('/api/users', async (req, res) => {
try {
const userId = await userService.createAccount(req.body.email);
res.json({ success: true, userId });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Stripe webhook endpoint
app.post('/webhooks/stripe', async (req, res) => {
try {
await webhookHandler.handleEvent(req.body);
res.json({ success: true });
} catch (error) {
console.error('Webhook processing failed:', error);
res.status(500).json({ success: false });
}
});
app.listen(3000, () => {
console.log('Backend service running on port 3000');
});
Read Operations
The Service API also supports read operations for accessing user data, subscriptions, and analytics. These require different permissions:
Available Read Endpoints
While the SDK’s serviceApi module focuses on write operations, you can access read operations directly through the HTTP client:
import { HttpClient } from '@drmhse/sso-sdk';
const client = new HttpClient({
baseURL: 'https://sso.example.com',
headers: {
'X-Api-Key': 'sk_live_abcd1234...'
}
});
// List all users (requires read:users)
const usersResponse = await client.get('/api/service/users?limit=50');
console.log(`Total users: ${usersResponse.data.total}`);
// Get specific user (requires read:users)
const userResponse = await client.get(`/api/service/users/${userId}`);
console.log(`User: ${userResponse.data.email}`);
// List subscriptions (requires read:subscriptions)
const subsResponse = await client.get('/api/service/subscriptions');
console.log(`Total subscriptions: ${subsResponse.data.total}`);
// Get user's subscription (requires read:subscriptions)
const subResponse = await client.get(`/api/service/subscriptions/${userId}`);
console.log(`Subscription: ${subResponse.data.plan_name} (${subResponse.data.status})`);
// Get service analytics (requires read:analytics)
const analyticsResponse = await client.get('/api/service/analytics');
const stats = analyticsResponse.data;
console.log(`
Total Users: ${stats.total_users}
Active Subscriptions: ${stats.active_subscriptions}
30-Day Logins: ${stats.total_logins_30d}
Active Users (30d): ${stats.unique_users_30d}
`);
// Get service info (requires read:service)
const infoResponse = await client.get('/api/service/info');
console.log(`Service: ${infoResponse.data.name} (${infoResponse.data.service_type})`);
API Key Permissions
When creating an API key, you can specify which operations it’s allowed to perform:
Available Permissions:
read:users- List and view userswrite:users- Create and update usersdelete:users- Delete usersread:subscriptions- List and view subscriptionswrite:subscriptions- Create and update subscriptionsdelete:subscriptions- Delete subscriptionsread:analytics- View service analyticsread:service- View service configurationwrite:service- Update service configuration
Example:
// Create API key with minimal permissions (read-only)
const readOnlyKey = await sso.services.apiKeys.create('acme-corp', 'main-app', {
name: 'Analytics Dashboard',
permissions: ['read:users', 'read:subscriptions', 'read:analytics']
});
// Create API key with full write permissions
const fullAccessKey = await sso.services.apiKeys.create('acme-corp', 'main-app', {
name: 'Backend Service',
permissions: [
'read:users',
'write:users',
'delete:users',
'read:subscriptions',
'write:subscriptions',
'delete:subscriptions',
'write:service'
],
expires_in_days: 90
});
Best Practices
-
Principle of Least Privilege: Only grant the permissions your service actually needs. Don’t request
deletepermissions if you only need to read data. -
API Key Security:
- Never expose API keys in client-side code
- Store keys in environment variables
- Rotate keys regularly (set
expires_in_days) - Use different keys for different environments
-
Error Handling: Always handle API errors gracefully:
try { await sso.serviceApi.createUser({ email }); } catch (error) { if (error.statusCode === 400 && error.message.includes('already exists')) { console.log('User already exists, skipping...'); } else { throw error; // Re-throw unexpected errors } } -
Idempotency: Design your integration to be idempotent. For example, check if a user exists before creating:
// Use read:users to check first const existingUsers = await client.get(`/api/service/users?email=${email}`); if (existingUsers.data.users.length === 0) { await sso.serviceApi.createUser({ email }); } -
Audit Trails: Prefer updating subscription status to ‘cancelled’ rather than deleting subscriptions. This maintains audit history.
-
Batch Operations: When performing bulk operations, implement proper rate limiting and error handling:
async function bulkCreateUsers(emails: string[]) { const results = { success: 0, failed: 0 }; for (const email of emails) { try { await sso.serviceApi.createUser({ email }); results.success++; } catch (error) { results.failed++; console.error(`Failed to create ${email}:`, error.message); } // Rate limit: wait 100ms between requests await new Promise(resolve => setTimeout(resolve, 100)); } return results; } -
Monitoring: Track API key usage through the
last_used_atfield to detect unused or potentially compromised keys. -
Testing: Use separate API keys for development/staging/production environments. Never use production keys in development.
Type Definitions
CreateUserRequest
interface CreateUserRequest {
email: string;
}
UpdateUserRequest
interface UpdateUserRequest {
email?: string;
}
CreateSubscriptionRequest
interface CreateSubscriptionRequest {
user_id: string;
plan_id: string;
status?: string;
current_period_end?: string;
}
UpdateSubscriptionRequest
interface UpdateSubscriptionRequest {
status?: string;
current_period_end?: string;
}
UpdateServiceInfoRequest
interface UpdateServiceInfoRequest {
name?: string;
}
ServiceApiUser
interface ServiceApiUser {
id: string;
email: string;
created_at: string;
}
ServiceApiSubscription
interface ServiceApiSubscription {
id: string;
user_id: string;
plan_id: string;
plan_name: string;
status: string;
current_period_end: string;
}
ServiceApiInfo
interface ServiceApiInfo {
id: string;
name: string;
slug: string;
service_type: string;
created_at: string;
}