domainly-sdk
v1.0.1
Published
Official TypeScript SDK for Domainly External SaaS API - Manage multi-tenant custom domains with ease
Maintainers
Readme
Domainly SDK
Official TypeScript/JavaScript SDK for the Domainly External SaaS API. Easily manage multi-tenant custom domains, subdomains, and SSL certificates for your SaaS application.
🆕 What's New in v1.0.1
- ✅ Enhanced Webhook Testing: Test webhook endpoints with custom URLs
- ✅ Webhook Retry Functionality: Retry failed webhook deliveries
- ✅ Webhook Signature Verification: Built-in utilities for secure webhook handling
- ✅ Improved Error Handling: Better error messages and retry logic
- ✅ Enhanced Validation: Comprehensive input validation utilities
- ✅ Updated Type Definitions: Types now match the latest API implementation
- ✅ Webhook Event Monitoring: Tools for monitoring webhook delivery health
🚀 Features
- Complete TypeScript Support - Full type definitions for all API operations
- Multi-tenant Management - Create and manage tenants with ease
- Custom Domain Handling - Add, verify, and manage custom domains
- Subdomain Operations - Create and manage subdomains for your tenants
- Webhook Integration - Configure webhooks for real-time notifications
- Automatic Retries - Built-in retry logic for failed requests
- Error Handling - Comprehensive error handling with detailed error types
- Validation Utilities - Input validation helpers for common operations
- Multiple Output Formats - CommonJS, ES Modules, and UMD builds
📦 Installation
npm install domainly-sdkyarn add domainly-sdkpnpm add domainly-sdk🏃 Quick Start
import { DomainlyClient } from 'domainly-sdk';
// Initialize the client
const client = new DomainlyClient({
apiKey: 'dom_your_api_key_here'
});
// Create a tenant with subdomain
const tenant = await client.createTenant({
name: 'Acme Corporation',
createSubdomain: true,
subdomainName: 'acme',
metadata: {
plan: 'premium',
industry: 'technology'
}
});
// Add a custom domain
const domain = await client.createDomain({
customDomain: 'app.acme.com',
subdomain: 'acme',
tenantId: tenant.data.id,
verificationMethod: 'cname'
});
console.log('Setup complete!', { tenant, domain });🔧 Configuration
Basic Configuration
import { DomainlyClient } from 'domainly-sdk';
const client = new DomainlyClient({
apiKey: 'dom_your_api_key_here',
// Optional configuration
baseURL: 'https://api.domainly.dev/external/api', // Default
timeout: 30000, // Default: 30 seconds
retries: 3, // Default: 3 retries
retryDelay: 1000, // Default: 1 second
});Using Environment Variables
// Set environment variable
process.env.DOMAINLY_API_KEY = 'dom_your_api_key_here';
const client = new DomainlyClient({
apiKey: process.env.DOMAINLY_API_KEY!
});📚 API Reference
Tenant Operations
Create Tenant
const tenant = await client.createTenant({
name: 'Customer Company',
createSubdomain: true, // Optional
subdomainName: 'customer', // Optional
metadata: { // Optional
plan: 'enterprise',
industry: 'finance'
}
});Get All Tenants
const tenants = await client.getTenants();
console.log(tenants.data); // Array of tenantsGet Specific Tenant
const tenant = await client.getTenant('tenant-id');
console.log(tenant.data);Update Tenant
const updatedTenant = await client.updateTenant('tenant-id', {
name: 'Updated Company Name',
status: 'ACTIVE',
metadata: { plan: 'premium' }
});Delete Tenant
await client.deleteTenant('tenant-id');Domain Operations
Create Custom Domain
const domain = await client.createDomain({
customDomain: 'app.customer.com',
subdomain: 'customer-subdomain', // Optional
tenantId: 'tenant-id', // Optional
verificationMethod: 'cname', // 'cname' | 'txt' | 'http'
autoRenewSSL: true // Optional
});Verify Domain
const verification = await client.verifyDomain('domain-id');
console.log('Verified:', verification.data.verified);Get CNAME Records
const records = await client.getCNAMERecords('domain-id');
console.log('CNAME records:', records.data);List All Domains
const domains = await client.getDomains();
console.log(domains.data);Subdomain Operations
Create Subdomain
const subdomain = await client.createSubdomain({
subdomain: 'store',
tenantId: 'tenant-id', // Optional
description: 'E-commerce store subdomain'
});Check Subdomain Availability
const availability = await client.checkSubdomainAvailability('desired-subdomain');
console.log('Available:', availability.data.available);Webhook Management
Configure Webhooks
await client.configureWebhook({
webhookUrl: 'https://your-app.com/webhooks/domainly',
secret: 'your-webhook-secret',
events: [
'tenant.created',
'domain.verified',
'ssl.provisioned'
]
});Get Webhook Configuration
const config = await client.getWebhookConfig();
console.log(config.data);Test Webhook
const testResult = await client.testWebhook({
webhookUrl: 'https://your-app.com/webhooks/domainly'
});
console.log('Webhook test:', testResult.data.success);Get Webhook Logs
const logs = await client.getWebhookLogs({ limit: 20, offset: 0 });
console.log('Recent webhook deliveries:', logs.data.logs);
console.log('Total events:', logs.data.total);Retry Failed Webhook
const result = await client.retryWebhook('webhook-event-id');
console.log('Retry successful:', result.success);🛠 Utility Functions
The SDK includes utility functions for common operations:
Domain Setup Workflow
import { DomainlyUtils } from 'domainly-sdk';
const utils = new DomainlyUtils(client);
// Complete setup with tenant, subdomain, and domain
const setup = await utils.createCompleteSetup({
tenantName: 'Acme Corporation',
subdomainName: 'acme',
customDomain: 'app.acme.com',
verificationMethod: 'cname',
metadata: { plan: 'enterprise' }
});
console.log('Setup result:', setup);Wait for Domain Verification
// Wait for domain to be verified with polling
const verifiedDomain = await utils.waitForDomainVerification('domain-id', {
maxAttempts: 30,
pollInterval: 5000, // 5 seconds
timeout: 300000 // 5 minutes
});
console.log('Domain verified!', verifiedDomain);Batch Operations
// Create multiple tenants at once
const batchResult = await utils.batchCreateTenants([
{ name: 'Company A', createSubdomain: true, subdomainName: 'company-a' },
{ name: 'Company B', createSubdomain: true, subdomainName: 'company-b' },
{ name: 'Company C', createSubdomain: true, subdomainName: 'company-c' }
], {
concurrency: 3,
continueOnError: true
});
console.log(`Created ${batchResult.successful} tenants`);✅ Validation Utilities
Input Validation
import { DomainUtils, SubdomainUtils, ValidationUtils } from 'domainly-sdk';
// Validate domain format
const domainValidation = DomainUtils.validateDomain('app.example.com');
if (!domainValidation.valid) {
console.error('Invalid domain:', domainValidation.error);
}
// Validate subdomain format
const subdomainValidation = SubdomainUtils.validateSubdomain('my-app');
if (!subdomainValidation.valid) {
console.error('Invalid subdomain:', subdomainValidation.error);
}
// Validate complete requests
const tenantRequestValidation = ValidationUtils.validateCreateTenantRequest({
name: 'Test Company',
subdomainName: 'test-company'
});
if (!tenantRequestValidation.valid) {
console.error('Validation errors:', tenantRequestValidation.errors);
}🎯 Helper Functions
import { SubdomainUtils, DomainUtils, ErrorUtils } from 'domainly-sdk';
// Generate slug from company name
const slug = SubdomainUtils.generateSlug('My Company Inc!');
console.log(slug); // 'my-company-inc'
// Check domain status
const isVerified = DomainUtils.isDomainVerified(domain);
const hasSSL = DomainUtils.isSSLActive(domain);
// Get verification instructions
const instructions = DomainUtils.getVerificationInstructions(domain);
console.log(instructions);
// Handle errors gracefully
if (ErrorUtils.isRetryableError(error)) {
// Implement retry logic
}🔄 Error Handling
The SDK provides comprehensive error handling:
import { DomainlyClient, ErrorUtils } from 'domainly-sdk';
try {
const tenant = await client.createTenant({
name: 'Test Company'
});
} catch (error) {
if (error.name === 'DomainlyError') {
console.error('API Error:', {
message: error.message,
status: error.status,
code: error.code,
details: error.details
});
// Check if error is retryable
if (ErrorUtils.isRetryableError(error)) {
console.log('This error can be retried');
}
// Get user-friendly error message
const readableError = ErrorUtils.getReadableError(error);
console.log('User message:', readableError);
} else {
console.error('Unexpected error:', error);
}
}🔗 Webhook Events
Configure webhooks to receive real-time notifications:
Available Events
- Tenant Events:
tenant.created,tenant.updated,tenant.deleted - Subdomain Events:
subdomain.created,subdomain.updated,subdomain.deleted - Domain Events:
domain.created,domain.updated,domain.deleted,domain.verified,domain.failed - SSL Events:
ssl.provisioned,ssl.expired,ssl.renewed
Webhook Payload Example
{
"event": "domain.verified",
"timestamp": "2024-01-15T12:00:00Z",
"projectId": "project-789",
"domainId": "domain-123",
"data": {
"customDomain": "app.customer.com",
"status": "verified",
"message": "Domain verification completed successfully",
"timestamp": "2024-01-15T12:00:00Z"
}
}For webhook tests, the payload will include:
{
"event": "webhook.test",
"projectId": "your-project-id",
"domainId": "test-domain-id",
"data": {
"customDomain": "test.example.com",
"status": "verified",
"message": "This is a test webhook from Domainly",
"timestamp": "2024-01-15T12:00:00Z"
},
"timestamp": "2024-01-15T12:00:00Z"
}Webhook Event Monitoring
import { DomainlyClient, WebhookUtils } from 'domainly-sdk';
class WebhookMonitor {
private client: DomainlyClient;
constructor(apiKey: string) {
this.client = new DomainlyClient({ apiKey });
}
async checkWebhookHealth() {
try {
// Test webhook connectivity
const testResult = await this.client.testWebhook({
webhookUrl: 'https://your-app.com/webhooks/domainly'
});
if (testResult.data.success) {
console.log('✅ Webhook endpoint is healthy');
}
} catch (error) {
console.error('❌ Webhook test failed:', error.message);
}
}
async getFailedWebhooks() {
const logs = await this.client.getWebhookLogs({ limit: 100 });
const failedEvents = logs.data.logs.filter(log => log.status === 'FAILED');
console.log(`Found ${failedEvents.length} failed webhook deliveries`);
return failedEvents;
}
async retryFailedWebhooks() {
const failedEvents = await this.getFailedWebhooks();
for (const event of failedEvents) {
try {
await this.client.retryWebhook(event.id);
console.log(`✅ Retried webhook ${event.id}`);
} catch (error) {
console.error(`❌ Failed to retry webhook ${event.id}:`, error.message);
}
}
}
}
// Usage
const monitor = new WebhookMonitor('dom_your_api_key');
await monitor.checkWebhookHealth();
await monitor.retryFailedWebhooks();Webhook Security
Verify webhook signatures using the built-in utility:
import { WebhookUtils } from 'domainly-sdk';
// In your webhook endpoint
app.post('/webhooks/domainly', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString();
const signature = req.headers['x-webhook-signature'];
const secret = process.env.DOMAINLY_WEBHOOK_SECRET;
// Verify the webhook signature
if (!WebhookUtils.verifySignature(payload, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse the webhook payload
const webhookData = WebhookUtils.parsePayload(payload);
if (!webhookData) {
return res.status(400).json({ error: 'Invalid payload' });
}
// Process the webhook
console.log('Received webhook:', webhookData.event);
res.json({ success: true });
});🌍 Real-world Examples
SaaS Multi-tenant Setup
import { DomainlyClient, DomainlyUtils } from 'domainly-sdk';
class SaaSIntegration {
private domainly: DomainlyClient;
private utils: DomainlyUtils;
constructor(apiKey: string) {
this.domainly = new DomainlyClient({ apiKey });
this.utils = new DomainlyUtils(this.domainly);
}
async onboardCustomer(customerData: {
name: string;
subdomain: string;
customDomain?: string;
}) {
try {
// Create tenant with subdomain
const tenant = await this.domainly.createTenant({
name: customerData.name,
createSubdomain: true,
subdomainName: customerData.subdomain,
metadata: {
onboardedAt: new Date().toISOString(),
plan: 'starter'
}
});
let domain = null;
// Add custom domain if provided
if (customerData.customDomain) {
domain = await this.domainly.createDomain({
customDomain: customerData.customDomain,
subdomain: customerData.subdomain,
tenantId: tenant.data.id,
verificationMethod: 'cname'
});
// Wait for verification
const verifiedDomain = await this.utils.waitForDomainVerification(
domain.data.id,
{ timeout: 300000 }
);
console.log('Domain verified:', verifiedDomain.customDomain);
}
return {
success: true,
tenant: tenant.data,
domain: domain?.data,
accessUrl: domain?.data?.customDomain || `${customerData.subdomain}.your-app.com`
};
} catch (error) {
console.error('Customer onboarding failed:', error);
return { success: false, error: error.message };
}
}
async getCustomerSummary() {
const summary = await this.utils.getResourcesSummary();
return summary;
}
}
// Usage
const saas = new SaaSIntegration('dom_your_api_key');
const result = await saas.onboardCustomer({
name: 'Acme Corporation',
subdomain: 'acme',
customDomain: 'app.acme.com'
});
console.log('Onboarding result:', result);Domain Verification Flow
async function setupCustomDomain(customDomain: string, subdomain: string) {
const client = new DomainlyClient({
apiKey: process.env.DOMAINLY_API_KEY!
});
// 1. Create domain
const domain = await client.createDomain({
customDomain,
subdomain,
verificationMethod: 'cname'
});
// 2. Get verification records
const records = await client.getCNAMERecords(domain.data.id);
console.log('Add these DNS records:');
records.data.forEach(record => {
console.log(`${record.name} CNAME ${record.value}`);
});
// 3. Poll for verification
console.log('Waiting for DNS propagation...');
const utils = new DomainlyUtils(client);
try {
const verifiedDomain = await utils.waitForDomainVerification(domain.data.id, {
maxAttempts: 60, // 5 minutes with 5-second intervals
pollInterval: 5000
});
console.log('✅ Domain verified successfully!');
console.log('🔒 SSL certificate will be provisioned automatically');
return verifiedDomain;
} catch (error) {
console.error('❌ Domain verification failed:', error.message);
throw error;
}
}📈 Rate Limiting
The SDK automatically handles rate limiting with exponential backoff:
- General API calls: 1000 per hour
- Tenant creation: 100 per hour
- Domain creation: 20 per hour
- DNS verification: 50 per hour
Rate limit exceeded responses (429) are automatically retried.
🔒 Security Best Practices
- Store API keys securely - Never commit API keys to version control
- Use environment variables - Store keys in environment variables or secure vaults
- Verify webhook signatures - Always verify webhook payloads using HMAC-SHA256
- Use HTTPS - Only configure HTTPS webhook endpoints
- Rotate API keys - Regularly rotate your API keys
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🆘 Support
- Documentation: https://domainly.dev/docs
- API Reference: https://domainly.dev/api
- Support Email: [email protected]
- GitHub Issues: https://github.com/domainly-sdk/sdk/issues
🔗 Links
Made with ❤️ by the Domainly Team
