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

skip

integer

No

Number of records to skip for pagination

0

20

take

integer

No

Number of records to return (max 100)

10

50

Pagination Guidelines

  • Maximum per request: 100 invoices

  • Default page size: 10 invoices

  • Offset-based pagination: Use skip and take 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

id

string

Unique invoice identifier (UUID)

"inv_550e8400-e29b-41d4"

invoice_number

string

Sequential invoice number

"INV-000001"

name

string

Invoice name/title

"Website Development Services"

app_id

string

Application identifier

"app_123456789"

email

string

Customer email address

"client@example.com"

status

enum

Current invoice status

"OPEN"

Customer & Contact Information

Field

Type

Description

Example

customer_id

string

Customer identifier

"550e8400-e29b-41d4"

address

string|null

Customer address

"123 Business St"

phone_number

string|null

Customer phone

"+1-555-123-4567"

logo

string|null

Company logo URL

"https://example.com/logo.png"

Dates & Timing

Field

Type

Description

Example

due_date

string

Payment due date (ISO 8601)

"2024-02-15T00:00:00.000Z"

send_date

string|null

Invoice send date

"2024-01-15T00:00:00.000Z"

date_created

string

Creation timestamp

"2024-01-15T10:30:00.000Z"

paidAt

string|null

Payment completion timestamp

"2024-01-30T16:45:00.000Z"

Payment & Processing

Field

Type

Description

Example

payment_methods

array

Accepted payment methods

["CRYPTO", "BANK_TRANSFER"]

payment_link

boolean

Payment link enabled

true

partial_payment

boolean

Partial payments allowed

false

paymentStatus

string|null

Payment processing status

"PAID"

paymentMetadata

object|null

Payment transaction details

{"transactionHash": "0x..."}

Related Objects

Field

Type

Description

customer

object

Customer information

wallet

object|null

Associated wallet details

tax

object|null

Tax configuration

items

array

Invoice line items

transactions

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:

  1. Drill Down: Fetch detailed information for specific invoices

  2. Bulk Operations: Update multiple invoices simultaneously

  3. Generate Reports: Create analytics and business insights

  4. Monitor Changes: Set up real-time notifications for status updates

  5. Export Data: Generate CSV/PDF reports for accounting systems

For more information, see: