Service API Module

Complete API reference for sso.serviceApi module - API key authenticated backend operations for user and subscription management

Updated Dec 16, 2025
Edit on GitHub

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 ID
  • email (string) - User email address
  • created_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 have write:users permission
  • 400 Bad Request - Invalid email format or email already exists
  • 401 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 ID
  • email (string) - Updated email address
  • created_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 have write:users permission
  • 404 Not Found - User doesn’t exist
  • 400 Bad Request - Invalid email format or email already in use
  • 401 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 have delete:users permission
  • 404 Not Found - User doesn’t exist
  • 401 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 ID
  • user_id (string) - User ID
  • plan_id (string) - Plan ID
  • plan_name (string) - Plan name
  • status (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 have write:subscriptions permission
  • 404 Not Found - User or plan doesn’t exist
  • 400 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 ID
  • user_id (string) - User ID
  • plan_id (string) - Plan ID
  • plan_name (string) - Plan name
  • status (string) - Updated subscription status
  • current_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 have write:subscriptions permission
  • 404 Not Found - User or subscription doesn’t exist
  • 400 Bad Request - Invalid status or date format
  • 401 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 have delete:subscriptions permission
  • 404 Not Found - User or subscription doesn’t exist
  • 401 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 ID
  • name (string) - Updated service name
  • slug (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 have write:service permission
  • 400 Bad Request - Invalid service name
  • 401 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 users
  • write:users - Create and update users
  • delete:users - Delete users
  • read:subscriptions - List and view subscriptions
  • write:subscriptions - Create and update subscriptions
  • delete:subscriptions - Delete subscriptions
  • read:analytics - View service analytics
  • read:service - View service configuration
  • write: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

  1. Principle of Least Privilege: Only grant the permissions your service actually needs. Don’t request delete permissions if you only need to read data.

  2. 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
  3. 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
      }
    }
    
  4. 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 });
    }
    
  5. Audit Trails: Prefer updating subscription status to ‘cancelled’ rather than deleting subscriptions. This maintains audit history.

  6. 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;
    }
    
  7. Monitoring: Track API key usage through the last_used_at field to detect unused or potentially compromised keys.

  8. 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;
}