The payments search API provides powerful capabilities to query, filter, and analyze both charges and payouts in a unified interface. With over 20 search parameters and support for complex filtering, you can build everything from real-time dashboards to detailed reconciliation reports that match your exact business requirements.

What You’ll Build With This Guide

This guide equips you with patterns and code examples for:
  • Unified payment searches: Query charges and payouts together or separately
  • Advanced filtering: Combine date ranges, amounts, statuses, and metadata for precise results
  • Reconciliation workflows: Match payments to bank statements using trace numbers
  • Return tracking: Analyze reversed payments and identify high-risk patterns
  • Export capabilities: Generate CSV and JSON reports for accounting systems
  • Performance monitoring: Build real-time metrics and anomaly detection
  • Pagination strategies: Efficiently process large datasets with parallel requests
Whether you need daily settlement reports, customer payment histories, or failure analysis, this guide provides production-ready patterns for every reporting scenario. The payments search endpoint allows you to query both charges and payouts together or separately, providing a complete view of money movement through your platform. Search for all payments in a date range:
curl -X GET "https://sandbox.straddle.io/v1/payments/search?\
  created_from=2024-10-01&\
  created_to=2024-10-31" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response includes both charges and payouts:
{
  "data": [
    {
      "id": "ch_abc123",
      "payment_type": "charge",
      "amount": 10000,
      "status": "paid",
      "created_at": "2024-10-15T10:00:00Z"
    },
    {
      "id": "po_def456",
      "payment_type": "payout",
      "amount": 25000,
      "status": "paid",
      "created_at": "2024-10-16T14:00:00Z"
    }
  ],
  "pagination": {
    "current_page": 1,
    "total_pages": 5,
    "total_count": 247
  }
}

Search Parameters

Date Filters

ParameterDescriptionFormatExample
created_fromStart of creation date rangeYYYY-MM-DD2024-10-01
created_toEnd of creation date rangeYYYY-MM-DD2024-10-31
updated_fromStart of update date rangeYYYY-MM-DD2024-10-01
updated_toEnd of update date rangeYYYY-MM-DD2024-10-31
payment_date_fromStart of payment date rangeYYYY-MM-DD2024-10-01
payment_date_toEnd of payment date rangeYYYY-MM-DD2024-10-31

Payment Filters

ParameterDescriptionValuesExample
payment_typeFilter by typecharge, payoutcharge
statusFilter by statuscreated, scheduled, pending, paid, failed, reversed, cancelled, on_holdpaid
status_reasonFilter by failure reasoninsufficient_funds, closed_bank_account, etc.insufficient_funds
amount_minMinimum amount in centsInteger10000
amount_maxMaximum amount in centsInteger500000

Reference Filters

ParameterDescriptionExample
paykeyFilter by paykeypk_abc123xyz
external_idYour reference IDinv_12345
customer_idCustomer identifiercust_789
payment_idSpecific payment IDch_def456
trace_numberACH trace number071000301234567

Pagination & Sorting

ParameterDescriptionDefaultValues
pagePage number1Integer
limitResults per page1001-500
sort_bySort fieldcreated_atcreated_at, updated_at, amount, payment_date
sort_orderSort directiondescasc, desc

Common Search Patterns

Daily Settlement Report

Get all successful payments for settlement reconciliation:
async function getDailySettlement(date) {
  const payments = await straddle.payments.search({
    payment_date_from: date,
    payment_date_to: date,
    status: 'paid',
    sort_by: 'payment_type'
  });
  
  const summary = {
    date,
    charges: {
      count: 0,
      amount: 0
    },
    payouts: {
      count: 0,
      amount: 0
    },
    net: 0
  };
  
  for (const payment of payments.data) {
    if (payment.payment_type === 'charge') {
      summary.charges.count++;
      summary.charges.amount += payment.amount;
    } else {
      summary.payouts.count++;
      summary.payouts.amount += payment.amount;
    }
  }
  
  summary.net = summary.charges.amount - summary.payouts.amount;
  
  return summary;
}

Failed Payment Analysis

Identify failure patterns for risk optimization:
async function analyzeFailures(startDate, endDate) {
  const failures = await straddle.payments.search({
    created_from: startDate,
    created_to: endDate,
    status: 'failed',
    limit: 500
  });
  
  // Group by failure reason
  const reasonAnalysis = {};
  
  for (const payment of failures.data) {
    const reason = payment.status_details.reason;
    
    if (!reasonAnalysis[reason]) {
      reasonAnalysis[reason] = {
        count: 0,
        total_amount: 0,
        payment_types: { charge: 0, payout: 0 }
      };
    }
    
    reasonAnalysis[reason].count++;
    reasonAnalysis[reason].total_amount += payment.amount;
    reasonAnalysis[reason].payment_types[payment.payment_type]++;
  }
  
  return {
    period: { from: startDate, to: endDate },
    total_failures: failures.pagination.total_count,
    by_reason: reasonAnalysis
  };
}

Customer Payment History

View all payments for a specific customer:
async function getCustomerPaymentHistory(customerId, paykey) {
  const payments = await straddle.payments.search({
    paykey: paykey,
    sort_by: 'created_at',
    sort_order: 'desc',
    limit: 100
  });
  
  return {
    customer_id: customerId,
    paykey: paykey,
    total_payments: payments.pagination.total_count,
    recent_payments: payments.data.map(p => ({
      id: p.id,
      type: p.payment_type,
      amount: p.amount,
      status: p.status,
      date: p.created_at,
      description: p.description
    }))
  };
}

Advanced Filtering

Find payments in specific states:
curl -X GET "https://api.straddle.io/v1/payments/search?\
  status=pending&\
  status=scheduled&\
  payment_type=charge" \
  -H "Authorization: Bearer YOUR_API_KEY"

Amount Range Filtering

Find high-value transactions:
curl -X GET "https://api.straddle.io/v1/payments/search?\
  amount_min=100000&\
  amount_max=1000000&\
  created_from=2024-10-01" \
  -H "Authorization: Bearer YOUR_API_KEY"
Search using your custom metadata:
async function searchByMetadata(key, value) {
  const payments = await straddle.payments.search({
    [`metadata.${key}`]: value
  });
  
  return payments.data;
}

// Example: Find all payments for a specific order
const orderPayments = await searchByMetadata('order_id', 'ord_789');

Reconciliation Workflows

Bank Statement Reconciliation

Match payments with bank transactions:
async function reconcileBankStatement(statementDate, bankTransactions) {
  // Get all payments for the date
  const payments = await straddle.payments.search({
    payment_date: statementDate,
    status: 'paid'
  });
  
  const reconciliation = {
    date: statementDate,
    matched: [],
    unmatched_payments: [],
    unmatched_bank: [...bankTransactions]
  };
  
  // Match by amount and trace number
  for (const payment of payments.data) {
    const bankIndex = reconciliation.unmatched_bank.findIndex(
      t => t.amount === payment.amount && 
           t.trace === payment.trace_number
    );
    
    if (bankIndex !== -1) {
      reconciliation.matched.push({
        payment_id: payment.id,
        bank_transaction: reconciliation.unmatched_bank[bankIndex],
        amount: payment.amount
      });
      reconciliation.unmatched_bank.splice(bankIndex, 1);
    } else {
      reconciliation.unmatched_payments.push(payment);
    }
  }
  
  return reconciliation;
}

Return Tracking

Monitor and analyze returned payments:
async function trackReturns(startDate, endDate) {
  // Get all reversed payments
  const reversed = await straddle.payments.search({
    created_from: startDate,
    created_to: endDate,
    status: 'reversed'
  });
  
  const returnAnalysis = {
    period: { from: startDate, to: endDate },
    total_returns: reversed.pagination.total_count,
    by_code: {},
    by_day: {},
    high_risk_paykeys: []
  };
  
  // Analyze return patterns
  for (const payment of reversed.data) {
    const code = payment.status_details.code;
    const date = payment.updated_at.split('T')[0];
    
    // Track by return code
    if (!returnAnalysis.by_code[code]) {
      returnAnalysis.by_code[code] = { count: 0, amount: 0 };
    }
    returnAnalysis.by_code[code].count++;
    returnAnalysis.by_code[code].amount += payment.amount;
    
    // Track by day
    if (!returnAnalysis.by_day[date]) {
      returnAnalysis.by_day[date] = { count: 0, amount: 0 };
    }
    returnAnalysis.by_day[date].count++;
    returnAnalysis.by_day[date].amount += payment.amount;
  }
  
  // Identify high-risk paykeys
  const paykeyReturns = {};
  for (const payment of reversed.data) {
    if (!paykeyReturns[payment.paykey]) {
      paykeyReturns[payment.paykey] = 0;
    }
    paykeyReturns[payment.paykey]++;
  }
  
  returnAnalysis.high_risk_paykeys = Object.entries(paykeyReturns)
    .filter(([_, count]) => count > 2)
    .map(([paykey, count]) => ({ paykey, return_count: count }))
    .sort((a, b) => b.return_count - a.return_count);
  
  return returnAnalysis;
}

Export Patterns

CSV Export

Generate CSV reports for accounting:
async function exportPaymentsCSV(startDate, endDate) {
  const payments = await straddle.payments.search({
    created_from: startDate,
    created_to: endDate,
    limit: 500  // Process in batches for large exports
  });
  
  const csv = [
    // Headers
    'ID,Type,Amount,Status,Created,Updated,Description,External ID,Trace Number'
  ];
  
  for (const payment of payments.data) {
    csv.push([
      payment.id,
      payment.payment_type,
      payment.amount / 100,  // Convert to dollars
      payment.status,
      payment.created_at,
      payment.updated_at,
      `"${payment.description}"`,  // Quote for commas
      payment.external_id || '',
      payment.trace_number || ''
    ].join(','));
  }
  
  return csv.join('\n');
}

JSON Export with Details

Export complete payment data:
async function exportDetailedPayments(filters) {
  const allPayments = [];
  let page = 1;
  let hasMore = true;
  
  while (hasMore) {
    const result = await straddle.payments.search({
      ...filters,
      page,
      limit: 100
    });
    
    // Enrich with full details
    for (const payment of result.data) {
      const detailed = payment.payment_type === 'charge'
        ? await straddle.charges.retrieve(payment.id)
        : await straddle.payouts.retrieve(payment.id);
      
      allPayments.push(detailed);
    }
    
    hasMore = page < result.pagination.total_pages;
    page++;
  }
  
  return {
    export_date: new Date().toISOString(),
    filters,
    total_count: allPayments.length,
    payments: allPayments
  };
}

Monitoring & Alerts

Real-Time Monitoring

Track payment metrics in real-time:
async function getPaymentMetrics() {
  const today = new Date().toISOString().split('T')[0];
  
  // Today's activity
  const todayPayments = await straddle.payments.search({
    created_from: today,
    created_to: today
  });
  
  // Pending payments
  const pending = await straddle.payments.search({
    status: 'pending'
  });
  
  // Recent failures
  const failures = await straddle.payments.search({
    status: 'failed',
    created_from: today,
    created_to: today
  });
  
  return {
    timestamp: new Date().toISOString(),
    today: {
      total_count: todayPayments.pagination.total_count,
      total_volume: todayPayments.data.reduce((sum, p) => sum + p.amount, 0)
    },
    pending: {
      count: pending.pagination.total_count,
      volume: pending.data.reduce((sum, p) => sum + p.amount, 0)
    },
    failures: {
      count: failures.pagination.total_count,
      primary_reason: getMostCommonReason(failures.data)
    }
  };
}

Anomaly Detection

Identify unusual payment patterns:
async function detectAnomalies(lookbackDays = 7) {
  const endDate = new Date();
  const startDate = new Date();
  startDate.setDate(startDate.getDate() - lookbackDays);
  
  const payments = await straddle.payments.search({
    created_from: startDate.toISOString().split('T')[0],
    created_to: endDate.toISOString().split('T')[0]
  });
  
  const anomalies = [];
  
  // Large transaction detection
  const avgAmount = payments.data.reduce((sum, p) => sum + p.amount, 0) / payments.data.length;
  const largeTransactions = payments.data.filter(p => p.amount > avgAmount * 5);
  
  if (largeTransactions.length > 0) {
    anomalies.push({
      type: 'large_transactions',
      count: largeTransactions.length,
      payments: largeTransactions.map(p => p.id)
    });
  }
  
  // Velocity check - too many payments from same paykey
  const paykeyVelocity = {};
  for (const payment of payments.data) {
    if (!paykeyVelocity[payment.paykey]) {
      paykeyVelocity[payment.paykey] = [];
    }
    paykeyVelocity[payment.paykey].push(payment);
  }
  
  for (const [paykey, payments] of Object.entries(paykeyVelocity)) {
    if (payments.length > 10) {  // More than 10 payments in period
      anomalies.push({
        type: 'high_velocity',
        paykey,
        count: payments.length,
        total_amount: payments.reduce((sum, p) => sum + p.amount, 0)
      });
    }
  }
  
  // Unusual failure rate
  const failureRate = payments.data.filter(p => p.status === 'failed').length / payments.data.length;
  if (failureRate > 0.15) {  // More than 15% failure rate
    anomalies.push({
      type: 'high_failure_rate',
      rate: failureRate,
      message: `${(failureRate * 100).toFixed(1)}% failure rate detected`
    });
  }
  
  return anomalies;
}

Pagination Strategies

Cursor-Based Iteration

Process large result sets efficiently:
async function* iterateAllPayments(filters) {
  let page = 1;
  let hasMore = true;
  
  while (hasMore) {
    const result = await straddle.payments.search({
      ...filters,
      page,
      limit: 100
    });
    
    for (const payment of result.data) {
      yield payment;
    }
    
    hasMore = page < result.pagination.total_pages;
    page++;
    
    // Rate limiting
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

// Usage
async function processAllPayments() {
  for await (const payment of iterateAllPayments({ status: 'paid' })) {
    await processPayment(payment);
  }
}

Parallel Processing

Speed up large exports with parallel requests:
async function parallelExport(filters) {
  // First, get total count
  const initial = await straddle.payments.search({
    ...filters,
    limit: 1
  });
  
  const totalPages = initial.pagination.total_pages;
  const batchSize = 5;  // Process 5 pages at a time
  
  const allPayments = [];
  
  for (let i = 1; i <= totalPages; i += batchSize) {
    const pagePromises = [];
    
    for (let page = i; page < Math.min(i + batchSize, totalPages + 1); page++) {
      pagePromises.push(
        straddle.payments.search({
          ...filters,
          page,
          limit: 100
        })
      );
    }
    
    const results = await Promise.all(pagePromises);
    
    for (const result of results) {
      allPayments.push(...result.data);
    }
  }
  
  return allPayments;
}

Performance Optimization

Efficient Date Ranges

For better performance, use specific date ranges rather than open-ended queries. The API performs best with date ranges under 90 days.
// Good - Specific date range
const efficient = await straddle.payments.search({
  created_from: '2024-10-01',
  created_to: '2024-10-31'
});

// Less efficient - Open-ended
const lessEfficient = await straddle.payments.search({
  created_from: '2024-01-01'
  // No end date
});

Index-Friendly Filters

Use indexed fields for faster queries:
// Fast - Uses indexed fields
const fast = await straddle.payments.search({
  payment_id: 'ch_abc123',  // Direct ID lookup
  paykey: 'pk_xyz789',       // Indexed field
  status: 'paid'             // Indexed enum
});

// Slower - Requires scanning
const slower = await straddle.payments.search({
  'metadata.custom_field': 'value'  // Not indexed
});

Common Use Cases

Monthly Billing Report

async function generateMonthlyReport(year, month) {
  const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
  const endDate = new Date(year, month, 0).toISOString().split('T')[0];
  
  const report = {
    period: { year, month },
    summary: {
      total_charges: 0,
      total_payouts: 0,
      net_revenue: 0,
      success_rate: 0
    },
    daily_breakdown: {},
    top_customers: [],
    failure_analysis: {}
  };
  
  // Get all payments for the month
  const payments = await straddle.payments.search({
    created_from: startDate,
    created_to: endDate,
    limit: 500
  });
  
  // Process metrics
  let successful = 0;
  const customerTotals = {};
  
  for (const payment of payments.data) {
    // Summary totals
    if (payment.payment_type === 'charge') {
      report.summary.total_charges += payment.amount;
    } else {
      report.summary.total_payouts += payment.amount;
    }
    
    // Success tracking
    if (payment.status === 'paid') {
      successful++;
    }
    
    // Daily breakdown
    const day = payment.created_at.split('T')[0];
    if (!report.daily_breakdown[day]) {
      report.daily_breakdown[day] = {
        charges: 0,
        payouts: 0,
        count: 0
      };
    }
    report.daily_breakdown[day].count++;
    if (payment.payment_type === 'charge') {
      report.daily_breakdown[day].charges += payment.amount;
    } else {
      report.daily_breakdown[day].payouts += payment.amount;
    }
    
    // Track by customer
    if (!customerTotals[payment.paykey]) {
      customerTotals[payment.paykey] = 0;
    }
    customerTotals[payment.paykey] += payment.amount;
  }
  
  // Calculate final metrics
  report.summary.net_revenue = report.summary.total_charges - report.summary.total_payouts;
  report.summary.success_rate = (successful / payments.data.length) * 100;
  
  // Top customers
  report.top_customers = Object.entries(customerTotals)
    .sort(([,a], [,b]) => b - a)
    .slice(0, 10)
    .map(([paykey, total]) => ({ paykey, total }));
  
  return report;
}

Best Practices

Use Date Ranges

Always specify date ranges to improve query performance

Paginate Large Results

Use pagination for exports exceeding 100 records

Cache Frequently Used

Cache search results that don’t change frequently

Monitor Rate Limits

Implement rate limiting for bulk operations

Troubleshooting

Missing Payments

If payments aren’t appearing in search results:
  1. Check date ranges - Ensure dates include the payment creation time
  2. Verify filters - Remove filters one by one to identify issues
  3. Check status - Some statuses like unknown may not appear by default
  4. Review pagination - Check if results are on subsequent pages

Performance Issues

For slow queries:
  • Narrow date ranges to 30 days or less
  • Use specific IDs when possible
  • Avoid complex metadata queries
  • Implement caching for repeated searches

Next Steps