Developer Documentation

Stripe API Migrate Subscriptions

Technical deep dive into programmatic Stripe subscription migration. Learn API endpoints, rate limits, error handling, and best practices for automated migrations.

# Quick example: Migrate a subscription
curl -X POST https://api.submigrations.com/v1/migrations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d source_account="acct_source" \
  -d destination_account="acct_destination" \
  -d subscription_id="sub_123"

Authentication & Security

All API requests must be authenticated using OAuth 2.0 or API keys. We recommend OAuth for production applications.

API Key Authentication

// Node.js example
const submigrations = require('@submigrations/node');

const client = submigrations.Client({
  apiKey: process.env.SUBMIGRATIONS_API_KEY,
  stripeSecretKey: process.env.STRIPE_SECRET_KEY
});

OAuth 2.0 Flow

// OAuth authorization URL
https://api.submigrations.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=YOUR_CALLBACK_URL&
  response_type=code&
  scope=read:migrations+write:migrations

API Reference

Create Migration

POST /v1/migrations

Request Body

{
  "source_account": "acct_1234567890",
  "destination_account": "acct_0987654321",
  "resources": {
    "products": true,
    "prices": true,
    "customers": true,
    "subscriptions": true,
    "coupons": true
  },
  "options": {
    "preserve_ids": false,
    "preserve_billing_cycles": true,
    "test_mode": false
  }
}

Response

{
  "id": "mig_1234567890",
  "status": "pending",
  "created": 1234567890,
  "estimated_duration": 300,
  "resource_counts": {
    "products": 45,
    "subscriptions": 1250,
    "customers": 1100
  }
}

Get Migration Status

GET /v1/migrations/{migration_id}

Response

{
  "id": "mig_1234567890",
  "status": "in_progress",
  "progress": {
    "percentage": 45,
    "current_step": "migrating_subscriptions",
    "completed_resources": {
      "products": 45,
      "prices": 120,
      "customers": 500
    },
    "remaining_resources": {
      "subscriptions": 750
    }
  },
  "logs": [
    {
      "timestamp": 1234567890,
      "level": "info",
      "message": "Successfully migrated 500 customers"
    }
  ]
}

List Migrations

GET /v1/migrations

Query Parameters

  • status - Filter by status (pending, in_progress, completed, failed)
  • limit - Number of results (1-100, default: 10)
  • starting_after - Cursor for pagination

Rate Limits & Performance

Rate limits are enforced to ensure fair usage and platform stability. Plan accordingly for large migrations.

Endpoint Rate Limit Burst
Create Migration 10 req/hour 20 req
Get Status 100 req/min 200 req
List Migrations 60 req/min 100 req

Handling Rate Limits

// Exponential backoff example
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error.statusCode === 429) {
        const delay = Math.pow(2, i) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

Error Handling & Recovery

Error Response Format

{
  "error": {
    "type": "invalid_request_error",
    "message": "Source account not found",
    "code": "account_not_found",
    "param": "source_account",
    "request_id": "req_1234567890"
  }
}

Common Error Codes

Code Description Recovery Action
invalid_api_key API key is invalid or expired Check API key configuration
insufficient_permissions API key lacks required permissions Update Stripe API key permissions
migration_in_progress Another migration is already running Wait for completion or cancel existing
resource_limit_exceeded Too many resources to migrate Use batched migration approach

Idempotency

Use idempotency keys to safely retry requests without creating duplicates:

curl -X POST https://api.submigrations.com/v1/migrations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique-key-123" \
  -d source_account="acct_source" \
  -d destination_account="acct_destination"

Webhooks & Events

Webhook Configuration

// Configure webhook endpoint
POST /v1/webhook_endpoints
{
  "url": "https://your-app.com/webhooks/submigrations",
  "enabled_events": [
    "migration.started",
    "migration.progress",
    "migration.completed",
    "migration.failed"
  ]
}

Event Types

migration.started

Fired when migration begins processing

migration.progress

Periodic updates during migration (every 10%)

migration.completed

Migration finished successfully

migration.failed

Migration encountered unrecoverable error

Webhook Security

// Verify webhook signature
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Code Examples

Node.js Full Migration Example

const Submigrations = require('@submigrations/node');
const client = new Submigrations(process.env.SUBMIGRATIONS_API_KEY);

async function migrateStripeAccounts() {
  try {
    // Create migration
    const migration = await client.migrations.create({
      source_account: 'acct_source',
      destination_account: 'acct_destination',
      resources: {
        products: true,
        prices: true,
        customers: true,
        subscriptions: true,
        coupons: true
      },
      options: {
        preserve_billing_cycles: true,
        test_mode: true // Run in test mode first
      }
    });
    
    console.log(`Migration started: ${migration.id}`);
    
    // Poll for status
    let status = migration.status;
    while (status === 'pending' || status === 'in_progress') {
      await new Promise(resolve => setTimeout(resolve, 5000));
      
      const update = await client.migrations.retrieve(migration.id);
      status = update.status;
      
      console.log(`Progress: ${update.progress.percentage}%`);
    }
    
    if (status === 'completed') {
      console.log('Migration completed successfully!');
      
      // Download detailed report
      const report = await client.migrations.downloadReport(migration.id);
      await fs.writeFileSync('migration-report.json', report);
    } else {
      console.error('Migration failed:', migration.error);
    }
    
  } catch (error) {
    console.error('Error:', error.message);
  }
}

migrateStripeAccounts();

Python Batched Migration

import submigrations
import time

client = submigrations.Client(api_key=os.environ['SUBMIGRATIONS_API_KEY'])

def migrate_in_batches(source, destination, batch_size=100):
    """Migrate subscriptions in batches for large accounts"""
    
    # Get total count
    subscriptions = client.analyze_account(source)
    total = subscriptions['counts']['subscriptions']
    
    # Process in batches
    for offset in range(0, total, batch_size):
        migration = client.migrations.create(
            source_account=source,
            destination_account=destination,
            resources={
                'subscriptions': {
                    'limit': batch_size,
                    'offset': offset
                }
            }
        )
        
        # Wait for batch completion
        while migration.status in ['pending', 'in_progress']:
            time.sleep(5)
            migration = client.migrations.retrieve(migration.id)
        
        if migration.status == 'failed':
            raise Exception(f"Batch failed: {migration.error}")
        
        print(f"Completed batch {offset}-{offset+batch_size}")
    
    print("All batches completed successfully")

# Run migration
migrate_in_batches('acct_source', 'acct_destination')

CLI Usage

# Install CLI
npm install -g @submigrations/cli

# Configure authentication
submigrations auth login

# Run migration with progress bar
submigrations migrate \
  --source acct_source \
  --destination acct_destination \
  --resources all \
  --preserve-billing-cycles \
  --output migration-report.json

# Check status of running migration
submigrations status mig_1234567890

# List recent migrations
submigrations list --limit 10

Best Practices

✓ Do's

  • • Always test in Stripe test mode first
  • • Use idempotency keys for all requests
  • • Implement proper error handling and retries
  • • Monitor webhook events for real-time updates
  • • Batch large migrations to avoid timeouts
  • • Verify data integrity post-migration
  • • Keep audit logs for compliance

✗ Don'ts

  • • Don't hardcode API keys in your code
  • • Don't ignore rate limits
  • • Don't skip test migrations
  • • Don't migrate during peak traffic
  • • Don't forget to update webhook endpoints
  • • Don't assume immediate completion
  • • Don't delete source data immediately

Open Source Examples

Explore our GitHub repository for complete examples, SDKs, and community contributions.