v1.0

Migrating Deals

How to bulk-import your existing deal portfolio into SmartMCA

This guide covers importing an existing deal portfolio from a spreadsheet, legacy system, or another LMS into SmartMCA via the API.

Migration Strategy

For bulk imports, follow this order to satisfy foreign key dependencies:

  1. Merchants β€” Create merchant records first
  2. Contacts β€” Create contacts linked to merchants
  3. Deals β€” Create deals linked to merchants
  4. Payments β€” Import historical payment records

Step 1: Prepare Your Data

Map your existing fields to SmartMCA’s schema. Key fields:

Your FieldSmartMCA FieldNotes
Company NamelegalNameRequired
DBAdbaOptional
EIN/Tax IDeinUsed for dedup
Funded AmountfundedAmountDecimal
Purchase AmountpurchaseAmountDecimal (funded Γ— factor rate)
Total CollectedtotalCollectedSum of all payments
Funded DatefundedDateISO date
StatusstatusMust match: active, paidOff, defaulted, etc.
Payment FrequencypaymentFrequencydaily, weekly, monthly

Step 2: Import Merchants

async function importMerchant(row) {
  const res = await fetch('https://api.smartmca.com/api/public/v1/merchants', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `import-merchant-${row.ein}`,
    },
    body: JSON.stringify({
      legalName: row.companyName,
      dba: row.dba,
      ein: row.ein,
      industry: row.industry,
      address: row.address,
      city: row.city,
      state: row.state,
      zip: row.zip,
      phone: row.phone,
    }),
  });

  if (!res.ok) {
    const err = await res.json();
    console.error(`Failed to import merchant ${row.companyName}:`, err);
    return null;
  }

  return (await res.json()).data;
}

Step 3: Import Deals

async function importDeal(row, merchantId) {
  const res = await fetch('https://api.smartmca.com/api/public/v1/deals', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `import-deal-${row.legacyDealId}`,
    },
    body: JSON.stringify({
      merchantId,
      fundedAmount: row.fundedAmount,
      purchaseAmount: row.purchaseAmount,
      fundedDate: row.fundedDate,
      paymentFrequency: row.paymentFrequency,
      paymentAmount: row.dailyPayment,
      status: mapStatus(row.status),
    }),
  });

  if (!res.ok) {
    const err = await res.json();
    console.error(`Failed to import deal ${row.legacyDealId}:`, err);
    return null;
  }

  return (await res.json()).data;
}

function mapStatus(legacyStatus) {
  const mapping = {
    'Active': 'active',
    'Paid Off': 'paidOff',
    'Paid-Off': 'paidOff',
    'Default': 'defaulted',
    'Collections': 'inCollections',
    'Legal': 'inLegal',
    'Settled': 'settled',
    'Refinanced': 'refinanced',
  };
  return mapping[legacyStatus] || 'active';
}

Step 4: Import Payments

For each deal, import the historical payment records:

async function importPayments(dealId, payments) {
  for (const payment of payments) {
    await fetch(`https://api.smartmca.com/api/public/v1/deals/${dealId}/payments`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': `import-payment-${payment.legacyId}`,
      },
      body: JSON.stringify({
        amount: payment.amount,
        type: payment.type, // "collection", "fee", "adjustment"
        effectiveDate: payment.date,
        description: payment.memo,
      }),
    });

    // Respect rate limits β€” small delay between requests
    await new Promise(r => setTimeout(r, 100));
  }
}

Running the Full Import

const csv = require('csv-parse/sync');
const fs = require('fs');

async function runImport() {
  const merchants = csv.parse(fs.readFileSync('merchants.csv'), { columns: true });
  const deals = csv.parse(fs.readFileSync('deals.csv'), { columns: true });
  const payments = csv.parse(fs.readFileSync('payments.csv'), { columns: true });

  // Phase 1: Merchants
  const merchantMap = {}; // legacyId β†’ smartMcaId
  for (const row of merchants) {
    const result = await importMerchant(row);
    if (result) merchantMap[row.legacyId] = result.id;
  }
  console.log(`Imported ${Object.keys(merchantMap).length} merchants`);

  // Phase 2: Deals
  const dealMap = {}; // legacyDealId β†’ smartMcaDealId
  for (const row of deals) {
    const merchantId = merchantMap[row.legacyMerchantId];
    if (!merchantId) {
      console.warn(`Skipping deal ${row.legacyDealId} β€” merchant not found`);
      continue;
    }
    const result = await importDeal(row, merchantId);
    if (result) dealMap[row.legacyDealId] = result.id;
  }
  console.log(`Imported ${Object.keys(dealMap).length} deals`);

  // Phase 3: Payments
  let paymentCount = 0;
  for (const row of payments) {
    const dealId = dealMap[row.legacyDealId];
    if (!dealId) continue;
    await importPayments(dealId, [row]);
    paymentCount++;
  }
  console.log(`Imported ${paymentCount} payments`);
}

runImport().catch(console.error);

Rate Limit Considerations

For large imports (thousands of records):

  • The API allows 60 requests/minute per key by default
  • Use Idempotency-Key headers so you can safely retry on failures
  • Add delays between batches: await new Promise(r => setTimeout(r, 1000))
  • Consider running the import during off-peak hours
  • Contact support for temporary rate limit increases for large migrations

Validation Checklist

After import, verify:

  • Total deal count matches your source
  • totalCollected on each deal matches sum of imported payments
  • outstandingBalance = purchaseAmount - totalCollected
  • Deal statuses are correct (paid-off deals should have zero balance)
  • Merchant deduplication β€” no duplicate merchants by EIN