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.
Unified Payment Search
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.
Basic Search
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
Parameter Description Format Example created_from
Start of creation date range YYYY-MM-DD 2024-10-01
created_to
End of creation date range YYYY-MM-DD 2024-10-31
updated_from
Start of update date range YYYY-MM-DD 2024-10-01
updated_to
End of update date range YYYY-MM-DD 2024-10-31
payment_date_from
Start of payment date range YYYY-MM-DD 2024-10-01
payment_date_to
End of payment date range YYYY-MM-DD 2024-10-31
Payment Filters
Parameter Description Values Example payment_type
Filter by type charge
, payout
charge
status
Filter by status created
, scheduled
, pending
, paid
, failed
, reversed
, cancelled
, on_hold
paid
status_reason
Filter by failure reason insufficient_funds
, closed_bank_account
, etc.insufficient_funds
amount_min
Minimum amount in cents Integer 10000
amount_max
Maximum amount in cents Integer 500000
Reference Filters
Parameter Description Example paykey
Filter by paykey pk_abc123xyz
external_id
Your reference ID inv_12345
customer_id
Customer identifier cust_789
payment_id
Specific payment ID ch_def456
trace_number
ACH trace number 071000301234567
Parameter Description Default Values page
Page number 1
Integer limit
Results per page 100
1-500 sort_by
Sort field created_at
created_at
, updated_at
, amount
, payment_date
sort_order
Sort direction desc
asc
, 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
Multi-Status Search
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 ;
}
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 ;
}
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:
Check date ranges - Ensure dates include the payment creation time
Verify filters - Remove filters one by one to identify issues
Check status - Some statuses like unknown
may not appear by default
Review pagination - Check if results are on subsequent pages
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