List Invoices
The List Invoices endpoint retrieves all invoices from your application with comprehensive pagination support. You can fetch invoices with detailed information including customer data, items, payment status, and transaction history. This endpoint is essential for invoice management dashboards, reporting, and customer service operations
Endpoint Details
-
Method:
GET
-
URL:
/api/v0/invoices
-
Authentication: Required (API Key & Secret)
-
Rate Limiting: 1000 requests per minute
Query ParametersCopied!
Pagination Parameters
Parameter |
Type |
Required |
Description |
Default |
Example |
---|---|---|---|---|---|
|
integer |
No |
Number of records to skip for pagination |
|
|
|
integer |
No |
Number of records to return (max 100) |
|
|
Pagination Guidelines
-
Maximum per request: 100 invoices
-
Default page size: 10 invoices
-
Offset-based pagination: Use
skip
andtake
parameters -
Recommended page size: 25-50 for optimal performance
-
Total count: Use separate count endpoint if needed
Request ExamplesCopied!
List All Invoices (Default Pagination)
curl -X GET "https://api.devdraft.com/api/v0/invoices" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET"
List Invoices with Pagination
curl -X GET "https://api.devdraft.com/api/v0/invoices?skip=20&take=50" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET"
JavaScript/Node.js Examples
// Basic invoice listing
const listInvoices = async (skip = 0, take = 10) => {
try {
const response = await fetch(`https://api.devdraft.com/api/v0/invoices?skip=${skip}&take=${take}`, {
method: 'GET',
headers: {
'x-client-key': 'YOUR_CLIENT_KEY',
'x-client-secret': 'YOUR_CLIENT_SECRET'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const invoices = await response.json();
return invoices;
} catch (error) {
console.error('Error listing invoices:', error);
throw error;
}
};
// Advanced pagination helper
class InvoicePaginator {
constructor(apiClient) {
this.apiClient = apiClient;
this.pageSize = 25;
}
async getPage(page = 1) {
const skip = (page - 1) * this.pageSize;
return await this.apiClient.listInvoices(skip, this.pageSize);
}
async getAllInvoices() {
let skip = 0;
let allInvoices = [];
let hasMore = true;
while (hasMore) {
const invoices = await this.apiClient.listInvoices(skip, this.pageSize);
allInvoices = allInvoices.concat(invoices);
hasMore = invoices.length === this.pageSize;
skip += this.pageSize;
// Safety limit to prevent infinite loops
if (skip > 10000) break;
}
return allInvoices;
}
async *invoiceIterator() {
let skip = 0;
let hasMore = true;
while (hasMore) {
const invoices = await this.apiClient.listInvoices(skip, this.pageSize);
for (const invoice of invoices) {
yield invoice;
}
hasMore = invoices.length === this.pageSize;
skip += this.pageSize;
}
}
}
// Usage examples
try {
// Simple listing
const recentInvoices = await listInvoices(0, 10);
console.log(`Found ${recentInvoices.length} recent invoices`);
// Paginated access
const paginator = new InvoicePaginator({ listInvoices });
const firstPage = await paginator.getPage(1);
const secondPage = await paginator.getPage(2);
// Iterator for processing all invoices
for await (const invoice of paginator.invoiceIterator()) {
console.log(`Processing invoice: ${invoice.invoice_number}`);
// Process each invoice individually
}
// Get all invoices (use with caution for large datasets)
const allInvoices = await paginator.getAllInvoices();
console.log(`Total invoices: ${allInvoices.length}`);
} catch (error) {
console.error('Invoice listing failed:', error);
}
React Component Example
import React, { useState, useEffect } from 'react';
const InvoiceListComponent = () => {
const [invoices, setInvoices] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const pageSize = 25;
const loadInvoices = async (page = 1, append = false) => {
setLoading(true);
try {
const skip = (page - 1) * pageSize;
const newInvoices = await listInvoices(skip, pageSize);
if (append) {
setInvoices(prev => [...prev, ...newInvoices]);
} else {
setInvoices(newInvoices);
}
setHasMore(newInvoices.length === pageSize);
setCurrentPage(page);
} catch (error) {
console.error('Failed to load invoices:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadInvoices(1);
}, []);
const loadMore = () => {
if (!loading && hasMore) {
loadInvoices(currentPage + 1, true);
}
};
return (
<div className="invoice-list">
<h2>Invoices</h2>
<div className="invoice-grid">
{invoices.map(invoice => (
<div key={invoice.id} className="invoice-card">
<div className="invoice-header">
<h3>{invoice.invoice_number}</h3>
<span className={`status ${invoice.status.toLowerCase()}`}>
{invoice.status}
</span>
</div>
<div className="invoice-details">
<p><strong>Customer:</strong> {invoice.customer?.name || invoice.email}</p>
<p><strong>Amount:</strong> ${invoice.total_amount}</p>
<p><strong>Due Date:</strong> {new Date(invoice.due_date).toLocaleDateString()}</p>
<p><strong>Created:</strong> {new Date(invoice.date_created).toLocaleDateString()}</p>
</div>
<div className="invoice-actions">
<button onClick={() => viewInvoice(invoice.id)}>View</button>
<button onClick={() => editInvoice(invoice.id)}>Edit</button>
</div>
</div>
))}
</div>
{loading && <div className="loading">Loading invoices...</div>}
{!loading && hasMore && (
<button onClick={loadMore} className="load-more">
Load More Invoices
</button>
)}
{!loading && !hasMore && invoices.length > 0 && (
<div className="end-message">No more invoices to load</div>
)}
</div>
);
};
export default InvoiceListComponent;
Python Example
import requests
from typing import List, Dict, Optional, Iterator
from datetime import datetime
class InvoiceClient:
def __init__(self, client_key: str, client_secret: str, base_url: str = "https://api.devdraft.com"):
self.client_key = client_key
self.client_secret = client_secret
self.base_url = base_url
def _get_headers(self) -> Dict[str, str]:
return {
'x-client-key': self.client_key,
'x-client-secret': self.client_secret
}
def list_invoices(self, skip: int = 0, take: int = 10) -> List[Dict]:
"""List invoices with pagination"""
url = f"{self.base_url}/api/v0/invoices"
params = {'skip': skip, 'take': take}
try:
response = requests.get(url, headers=self._get_headers(), params=params)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to list invoices: {e}")
def get_all_invoices(self, page_size: int = 50) -> List[Dict]:
"""Get all invoices with automatic pagination"""
all_invoices = []
skip = 0
while True:
invoices = self.list_invoices(skip, page_size)
all_invoices.extend(invoices)
if len(invoices) < page_size:
break
skip += page_size
# Safety limit
if skip > 10000:
break
return all_invoices
def invoice_iterator(self, page_size: int = 50) -> Iterator[Dict]:
"""Iterator for processing invoices one by one"""
skip = 0
while True:
invoices = self.list_invoices(skip, page_size)
for invoice in invoices:
yield invoice
if len(invoices) < page_size:
break
skip += page_size
def get_invoices_by_status(self, status: str) -> List[Dict]:
"""Get invoices filtered by status (client-side filtering)"""
all_invoices = self.get_all_invoices()
return [inv for inv in all_invoices if inv['status'] == status]
def get_overdue_invoices(self) -> List[Dict]:
"""Get overdue invoices"""
all_invoices = self.get_all_invoices()
today = datetime.now().date()
overdue = []
for invoice in all_invoices:
due_date = datetime.fromisoformat(invoice['due_date'].replace('Z', '+00:00')).date()
if due_date < today and invoice['status'] in ['OPEN', 'PARTIALLYPAID']:
overdue.append(invoice)
return overdue
# Usage examples
def main():
client = InvoiceClient("YOUR_CLIENT_KEY", "YOUR_CLIENT_SECRET")
try:
# Basic listing
recent_invoices = client.list_invoices(0, 20)
print(f"Found {len(recent_invoices)} recent invoices")
# Get all invoices (use carefully with large datasets)
all_invoices = client.get_all_invoices()
print(f"Total invoices: {len(all_invoices)}")
# Process invoices with iterator
for invoice in client.invoice_iterator():
print(f"Processing: {invoice['invoice_number']} - {invoice['status']}")
# Process each invoice
# Filter by status
open_invoices = client.get_invoices_by_status('OPEN')
print(f"Open invoices: {len(open_invoices)}")
# Get overdue invoices
overdue_invoices = client.get_overdue_invoices()
print(f"Overdue invoices: {len(overdue_invoices)}")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Response FormatCopied!
Success Response (200 OK)
[
{
"id": "inv_550e8400-e29b-41d4-a716-446655440000",
"invoice_number": "INV-000001",
"name": "Website Development Services",
"app_id": "app_123456789",
"email": "client@example.com",
"address": "123 Business St, City, State",
"phone_number": "+1-555-123-4567",
"logo": "https://mycompany.com/assets/logo.png",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"walletId": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"due_date": "2024-02-15T00:00:00.000Z",
"send_date": "2024-01-15T00:00:00.000Z",
"date_created": "2024-01-15T10:30:00.000Z",
"status": "OPEN",
"payment_methods": ["CRYPTO", "BANK_TRANSFER"],
"delivery": "EMAIL",
"payment_link": true,
"partial_payment": false,
"reminder_sent": false,
"paidAt": null,
"paymentMetadata": null,
"paymentStatus": null,
"taxId": "tax_550e8400-e29b-41d4-a716-446655440123",
"currency": "USDC",
"app": {
"id": "app_123456789",
"name": "My Business App"
},
"customer": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "client@example.com",
"name": "John Doe",
"status": "ACTIVE"
},
"wallet": {
"id": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"address": "0x742d35Cc6635C0532925a3b8d",
"blockchain": "ETHEREUM",
"type": "APP"
},
"tax": {
"id": "tax_550e8400-e29b-41d4-a716-446655440123",
"name": "Standard Sales Tax",
"rate": 8.5,
"type": "PERCENTAGE"
},
"items": [
{
"invoice_id": "inv_550e8400-e29b-41d4-a716-446655440000",
"productId": "prod_123456789",
"quantity": 1,
"product": {
"id": "prod_123456789",
"name": "Website Development",
"price": 2500.00,
"currency": "USD"
}
}
],
"transactions": [
{
"id": "txn_987654321",
"amount": 1250.00,
"currency": "USDC",
"status": "PAYMENT_PROCESSED",
"type": "PAYMENT",
"dateCreated": "2024-01-20T14:30:00.000Z"
}
]
},
{
"id": "inv_550e8400-e29b-41d4-a716-446655440001",
"invoice_number": "INV-000002",
"name": "Monthly Subscription - February 2024",
"app_id": "app_123456789",
"email": "subscriber@example.com",
"address": null,
"phone_number": null,
"logo": "https://mycompany.com/assets/logo.png",
"customer_id": "550e8400-e29b-41d4-a716-446655440001",
"walletId": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"due_date": "2024-02-01T00:00:00.000Z",
"send_date": "2024-01-25T00:00:00.000Z",
"date_created": "2024-01-20T09:15:00.000Z",
"status": "PAID",
"payment_methods": ["CRYPTO"],
"delivery": "EMAIL",
"payment_link": true,
"partial_payment": false,
"reminder_sent": false,
"paidAt": "2024-01-30T16:45:00.000Z",
"paymentMetadata": {
"transactionHash": "0x1234567890abcdef",
"blockNumber": 12345678
},
"paymentStatus": "PAID",
"taxId": null,
"currency": "USDC",
"customer": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"email": "subscriber@example.com",
"name": "Jane Smith",
"status": "ACTIVE"
},
"items": [
{
"invoice_id": "inv_550e8400-e29b-41d4-a716-446655440001",
"productId": "prod_subscription_001",
"quantity": 1,
"product": {
"id": "prod_subscription_001",
"name": "Premium Subscription",
"price": 99.99,
"currency": "USD"
}
}
],
"transactions": [
{
"id": "txn_987654322",
"amount": 99.99,
"currency": "USDC",
"status": "PAYMENT_PROCESSED",
"type": "PAYMENT",
"dateCreated": "2024-01-30T16:45:00.000Z"
}
]
}
]
Empty Response (200 OK)
[]
Response Fields ReferenceCopied!
Core Invoice Fields
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Unique invoice identifier (UUID) |
|
|
string |
Sequential invoice number |
|
|
string |
Invoice name/title |
|
|
string |
Application identifier |
|
|
string |
Customer email address |
|
|
enum |
Current invoice status |
|
Customer & Contact Information
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Customer identifier |
|
|
string|null |
Customer address |
|
|
string|null |
Customer phone |
|
|
string|null |
Company logo URL |
|
Dates & Timing
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Payment due date (ISO 8601) |
|
|
string|null |
Invoice send date |
|
|
string |
Creation timestamp |
|
|
string|null |
Payment completion timestamp |
|
Payment & Processing
Field |
Type |
Description |
Example |
---|---|---|---|
|
array |
Accepted payment methods |
|
|
boolean |
Payment link enabled |
|
|
boolean |
Partial payments allowed |
|
|
string|null |
Payment processing status |
|
|
object|null |
Payment transaction details |
|
Related Objects
Field |
Type |
Description |
---|---|---|
|
object |
Customer information |
|
object|null |
Associated wallet details |
|
object|null |
Tax configuration |
|
array |
Invoice line items |
|
array |
Related payment transactions |
Error ResponsesCopied!
Authentication Error (401 Unauthorized)
{
"statusCode": 401,
"message": "Invalid or missing API credentials",
"error": "Unauthorized"
}
Invalid Pagination Parameters (400 Bad Request)
{
"statusCode": 400,
"message": "Take parameter cannot exceed 100",
"error": "Bad Request"
}
Rate Limit Error (429 Too Many Requests)
{
"statusCode": 429,
"message": "Rate limit exceeded. Maximum 1000 requests per minute.",
"error": "Too Many Requests",
"retryAfter": 60
}
Server Error (500 Internal Server Error)
{
"statusCode": 500,
"message": "Internal server error occurred while fetching invoices",
"error": "Internal Server Error"
}
Advanced Use CasesCopied!
Invoice Filtering & Search
// Client-side filtering utilities
const filterInvoices = (invoices, filters) => {
return invoices.filter(invoice => {
// Filter by status
if (filters.status && invoice.status !== filters.status) {
return false;
}
// Filter by date range
if (filters.startDate) {
const startDate = new Date(filters.startDate);
const invoiceDate = new Date(invoice.date_created);
if (invoiceDate < startDate) return false;
}
if (filters.endDate) {
const endDate = new Date(filters.endDate);
const invoiceDate = new Date(invoice.date_created);
if (invoiceDate > endDate) return false;
}
// Filter by amount range
if (filters.minAmount && invoice.total_amount < filters.minAmount) {
return false;
}
if (filters.maxAmount && invoice.total_amount > filters.maxAmount) {
return false;
}
// Search by customer email or name
if (filters.search) {
const searchTerm = filters.search.toLowerCase();
const emailMatch = invoice.email.toLowerCase().includes(searchTerm);
const nameMatch = invoice.customer?.name?.toLowerCase().includes(searchTerm) || false;
const invoiceNumberMatch = invoice.invoice_number.toLowerCase().includes(searchTerm);
if (!emailMatch && !nameMatch && !invoiceNumberMatch) {
return false;
}
}
// Filter by payment method
if (filters.paymentMethod) {
if (!invoice.payment_methods.includes(filters.paymentMethod)) {
return false;
}
}
return true;
});
};
// Advanced search implementation
const searchInvoices = async (searchCriteria) => {
const allInvoices = await getAllInvoices();
const filtered = filterInvoices(allInvoices, searchCriteria);
// Sort results
if (searchCriteria.sortBy) {
filtered.sort((a, b) => {
const aValue = a[searchCriteria.sortBy];
const bValue = b[searchCriteria.sortBy];
if (searchCriteria.sortOrder === 'desc') {
return bValue > aValue ? 1 : -1;
}
return aValue > bValue ? 1 : -1;
});
}
return filtered;
};
// Usage
const searchResults = await searchInvoices({
status: 'OPEN',
startDate: '2024-01-01',
endDate: '2024-01-31',
search: 'example.com',
sortBy: 'due_date',
sortOrder: 'asc'
});
Invoice Analytics
// Calculate invoice statistics
const calculateInvoiceStats = (invoices) => {
const stats = {
total: invoices.length,
byStatus: {},
totalAmount: 0,
averageAmount: 0,
overdue: 0,
paid: 0,
pending: 0,
byMonth: {},
paymentMethods: {}
};
const today = new Date();
invoices.forEach(invoice => {
// Status breakdown
stats.byStatus[invoice.status] = (stats.byStatus[invoice.status] || 0) + 1;
// Calculate total amount (would need to sum line items in real implementation)
const amount = invoice.items.reduce((sum, item) =>
sum + (item.product.price * item.quantity), 0);
stats.totalAmount += amount;
// Overdue check
const dueDate = new Date(invoice.due_date);
if (dueDate < today && ['OPEN', 'PARTIALLYPAID'].includes(invoice.status)) {
stats.overdue++;
}
// Payment status
if (invoice.status === 'PAID') {
stats.paid++;
} else if (['OPEN', 'PARTIALLYPAID'].includes(invoice.status)) {
stats.pending++;
}
// Monthly breakdown
const month = new Date(invoice.date_created).toISOString().slice(0, 7);
stats.byMonth[month] = (stats.byMonth[month] || 0) + 1;
// Payment methods
invoice.payment_methods.forEach(method => {
stats.paymentMethods[method] = (stats.paymentMethods[method] || 0) + 1;
});
});
stats.averageAmount = stats.total > 0 ? stats.totalAmount / stats.total : 0;
return stats;
};
// Generate invoice report
const generateInvoiceReport = async (dateRange) => {
const invoices = await getAllInvoices();
// Filter by date range if provided
let filteredInvoices = invoices;
if (dateRange) {
filteredInvoices = invoices.filter(invoice => {
const invoiceDate = new Date(invoice.date_created);
return invoiceDate >= new Date(dateRange.start) &&
invoiceDate <= new Date(dateRange.end);
});
}
const stats = calculateInvoiceStats(filteredInvoices);
return {
summary: stats,
invoices: filteredInvoices,
generatedAt: new Date().toISOString(),
period: dateRange || 'All time'
};
};
Real-time Invoice Dashboard
// Dashboard component with real-time updates
class InvoiceDashboard {
constructor(apiClient, updateInterval = 30000) {
this.apiClient = apiClient;
this.updateInterval = updateInterval;
this.invoices = [];
this.stats = {};
this.listeners = new Set();
this.isRunning = false;
}
async start() {
if (this.isRunning) return;
this.isRunning = true;
await this.refresh();
this.intervalId = setInterval(() => {
this.refresh();
}, this.updateInterval);
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.isRunning = false;
}
async refresh() {
try {
const invoices = await this.apiClient.getAllInvoices();
const previousTotal = this.invoices.length;
this.invoices = invoices;
this.stats = calculateInvoiceStats(invoices);
// Notify listeners of updates
this.notifyListeners({
type: 'refresh',
invoices: this.invoices,
stats: this.stats,
newInvoices: invoices.length - previousTotal
});
} catch (error) {
this.notifyListeners({
type: 'error',
error: error.message
});
}
}
addListener(callback) {
this.listeners.add(callback);
}
removeListener(callback) {
this.listeners.delete(callback);
}
notifyListeners(data) {
this.listeners.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error('Dashboard listener error:', error);
}
});
}
getOverdueInvoices() {
const today = new Date();
return this.invoices.filter(invoice => {
const dueDate = new Date(invoice.due_date);
return dueDate < today && ['OPEN', 'PARTIALLYPAID'].includes(invoice.status);
});
}
getRecentInvoices(days = 7) {
const cutoff = new Date(Date.now() - (days * 24 * 60 * 60 * 1000));
return this.invoices.filter(invoice => {
const createdDate = new Date(invoice.date_created);
return createdDate >= cutoff;
});
}
}
// Usage
const dashboard = new InvoiceDashboard(apiClient);
dashboard.addListener((data) => {
if (data.type === 'refresh') {
console.log(`Dashboard updated: ${data.invoices.length} total invoices`);
if (data.newInvoices > 0) {
console.log(`${data.newInvoices} new invoices since last update`);
}
// Update UI components
updateStatsDisplay(data.stats);
updateInvoiceList(data.invoices);
} else if (data.type === 'error') {
console.error('Dashboard error:', data.error);
}
});
await dashboard.start();
Bulk Operations
// Bulk invoice operations
const bulkUpdateInvoices = async (invoiceIds, updates) => {
const results = [];
const batchSize = 10;
for (let i = 0; i < invoiceIds.length; i += batchSize) {
const batch = invoiceIds.slice(i, i + batchSize);
const batchPromises = batch.map(async (invoiceId) => {
try {
const updatedInvoice = await updateInvoice(invoiceId, updates);
return { success: true, invoiceId, invoice: updatedInvoice };
} catch (error) {
return { success: false, invoiceId, error: error.message };
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
// Rate limiting delay
if (i + batchSize < invoiceIds.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return {
successful: results.filter(r => r.success),
failed: results.filter(r => !r.success),
summary: `Updated ${results.filter(r => r.success).length} of ${invoiceIds.length} invoices`
};
};
// Export invoices to CSV
const exportInvoicesToCSV = (invoices) => {
const headers = [
'Invoice Number',
'Customer Email',
'Customer Name',
'Status',
'Amount',
'Due Date',
'Created Date',
'Paid Date'
];
const rows = invoices.map(invoice => [
invoice.invoice_number,
invoice.email,
invoice.customer?.name || '',
invoice.status,
invoice.items.reduce((sum, item) => sum + (item.product.price * item.quantity), 0),
new Date(invoice.due_date).toLocaleDateString(),
new Date(invoice.date_created).toLocaleDateString(),
invoice.paidAt ? new Date(invoice.paidAt).toLocaleDateString() : ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(field => `"${field}"`).join(','))
.join('\n');
return csvContent;
};
Performance OptimizationCopied!
Caching Strategy
// Invoice cache with TTL
class InvoiceCache {
constructor(ttl = 300000) { // 5 minutes default TTL
this.cache = new Map();
this.ttl = ttl;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key, data) {
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
invalidate(pattern) {
if (pattern) {
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key);
}
}
} else {
this.cache.clear();
}
}
}
// Cached invoice client
class CachedInvoiceClient {
constructor(apiClient) {
this.apiClient = apiClient;
this.cache = new InvoiceCache();
}
async listInvoices(skip = 0, take = 10, useCache = true) {
const cacheKey = `invoices_${skip}_${take}`;
if (useCache) {
const cached = this.cache.get(cacheKey);
if (cached) return cached;
}
const invoices = await this.apiClient.listInvoices(skip, take);
this.cache.set(cacheKey, invoices);
return invoices;
}
invalidateCache() {
this.cache.invalidate();
}
}
Integration PatternsCopied!
Webhook Integration
// Handle invoice-related webhooks
const handleInvoiceWebhooks = (webhook) => {
const { event, data } = webhook;
switch (event) {
case 'invoice.created':
// Refresh invoice list cache
invoiceCache.invalidate();
console.log(`New invoice created: ${data.invoice_number}`);
break;
case 'invoice.paid':
// Update specific invoice in cache
invoiceCache.invalidate(`invoice_${data.id}`);
console.log(`Invoice paid: ${data.invoice_number}`);
break;
case 'invoice.overdue':
// Send notification
sendOverdueNotification(data);
break;
}
};
Database Synchronization
// Sync invoices with local database
const syncInvoicesWithLocalDB = async (localDB, apiClient) => {
try {
// Get last sync timestamp
const lastSync = await localDB.getLastSyncTimestamp('invoices');
// Fetch all invoices (in production, you'd want to filter by lastSync)
const remoteInvoices = await apiClient.getAllInvoices();
// Update local database
for (const invoice of remoteInvoices) {
await localDB.upsertInvoice(invoice);
}
// Update sync timestamp
await localDB.setLastSyncTimestamp('invoices', new Date());
console.log(`Synced ${remoteInvoices.length} invoices`);
} catch (error) {
console.error('Sync failed:', error);
throw error;
}
};
Best PracticesCopied!
Efficient Data Loading
-
Use appropriate page sizes (25-50 for UI, larger for processing)
-
Implement lazy loading for large invoice lists
-
Cache frequently accessed data with reasonable TTL
-
Use incremental loading for real-time dashboards
Error Handling
-
Implement retry logic for network failures
-
Handle rate limits gracefully with backoff
-
Provide fallback data when fresh data is unavailable
-
Log errors for monitoring and debugging
User Experience
-
Show loading states during data fetching
-
Implement infinite scroll for large datasets
-
Provide search and filter capabilities
-
Display meaningful error messages to users
Performance Monitoring
-
Track API response times and success rates
-
Monitor cache hit ratios and adjust TTL as needed
-
Analyze pagination patterns to optimize page sizes
-
Set up alerts for unusual error rates or slow responses
Next StepsCopied!
After listing invoices, you can:
-
Drill Down: Fetch detailed information for specific invoices
-
Bulk Operations: Update multiple invoices simultaneously
-
Generate Reports: Create analytics and business insights
-
Monitor Changes: Set up real-time notifications for status updates
-
Export Data: Generate CSV/PDF reports for accounting systems
For more information, see: