API Documentation

Invoices API

Create and manage invoices for properties. Generate, send, and track invoice status and payments for your tenants and properties.

Invoices

Invoices are automatically generated based on utility bills and share rules. You can retrieve, filter, and update their status.

  • GET /api/v1/invoices: List all invoices with pagination and filtering.
    Query Parameters
    // All fields are optional.
    {
      "propertyId": "string (filter by property ID)",
      "unitId": "string (filter by unit ID)", 
      "month": "string (filter by month, format: YYYY-MM, e.g., '2024-07')",
      "status": "string (one of: DRAFT, SENT, PAID, OVERDUE, CANCELLED)",
      "page": "number (default: 1)",
      "limit": "number (default: 20, max: 100)",
      "details": "boolean (default: false, includes charges and custom charges)"
    }
    Response Structure
    {
      "data": [
        {
          "id": "string",
          "propertyId": "string",
          "unitId": "string", 
          "month": "2024-07-01T00:00:00.000Z",
          "status": "DRAFT | SENT | PAID | OVERDUE | CANCELLED",
          "invoiceNumber": "string (e.g., PROP-UNIT-2024-07)",
          "invoiceDate": "2024-07-15T10:30:00.000Z",
          "totalAmount": "150.50",
          "paidAmount": "0.00",
          "paidAt": null,
          "tenantEmail": "[email protected]",
          "tenantName": "John Doe",
          "paymentStatus": "pending | paid | failed | cancelled",
          "stripePaymentLinkId": "string",
          "stripePaymentLinkUrl": "https://...",
          "emailSentAt": "2024-07-15T10:30:00.000Z",
          "emailStatus": "sent | delivered | bounced",
          "generatedAt": "2024-07-01T00:00:00.000Z",
          "updatedAt": "2024-07-15T10:30:00.000Z",
          "unit": { "id": "...", "label": "Apt 1A", ... },
          "property": { "id": "...", "name": "Building Name", ... }
        }
      ],
      "pagination": {
        "total": 45,
        "page": 1,
        "limit": 20,
        "totalPages": 3
      }
    }
  • GET /api/v1/invoices/:id: Get a single invoice with detailed charges.
    Response Structure
    // Same as list response, plus:
    {
      "charges": [
        {
          "id": "string",
          "utilityType": "ELECTRICITY | GAS | WATER | SEWER | TRASH | INTERNET",
          "amount": "75.25",
          "description": "Electric utility charge",
          "invoiceMonth": "2024-07-01T00:00:00.000Z"
        }
      ],
      "customCharges": [
        {
          "id": "string", 
          "description": "Late fee",
          "amount": "25.00",
          "invoiceMonth": "2024-07-01T00:00:00.000Z"
        }
      ]
    }
  • PATCH /api/v1/invoices/:id: Update an invoice status (includes auto-recalculation).
    Request Body
    {
      "status": "DRAFT | SENT | PAID | OVERDUE | CANCELLED"
    }
    
    // Note: Changing to DRAFT will trigger auto-recalculation 
    // if all invoices for the property-month become DRAFT

Code Examples

Example: Find all unpaid invoices for a property

Retrieve all invoices for a property that are not yet marked as PAID, including detailed charge breakdowns.

import requests
import os
import json

# --- Configuration ---
BASE_URL = "https://app.billgauge.com/api/v1"
# Store your API key in an environment variable for security
API_KEY = os.environ.get("BILLGAUGE_API_KEY", "your_api_key_here")
PROPERTY_ID = "prop_example_123456"  # Replace with your property ID

HEADERS = {
    "Authorization": f"Bearer {'{'}API_KEY{'}'}",
    "Content-Type": "application/json",
}

def get_unpaid_invoices(property_id):
    """Fetches all invoices for a property that are not paid."""
    print(f"Searching for unpaid invoices for property: {'{'}property_id{'}'}")
    
    # Note: status parameter takes a single value, not comma-separated
    # To get multiple statuses, make separate calls or don't filter by status
    params = {
        "propertyId": property_id,
        "status": "OVERDUE",  # or "DRAFT", "SENT", etc.
        "details": "true"  # Include charges breakdown
    }
    
    response = requests.get(f"{'{'}BASE_URL{'}'}/invoices", headers=HEADERS, params=params)
    
    if response.ok:
        data = response.json()
        return data.get("data", []), data.get("pagination", {'{'}{'}'})
    else:
        print(f"Error: {'{'}response.status_code{'}'}")
        print(response.text)
        return None, None

def get_all_unpaid_invoices(property_id):
    """Get all non-paid invoices by fetching different statuses."""
    all_unpaid = []
    statuses = ["DRAFT", "SENT", "OVERDUE"]
    
    for status in statuses:
        params = {
            "propertyId": property_id,
            "status": status,
            "details": "true"
        }
        
        response = requests.get(f"{'{'}BASE_URL{'}'}/invoices", headers=HEADERS, params=params)
        
        if response.ok:
            data = response.json()
            all_unpaid.extend(data.get("data", []))
        else:
            print(f"Error fetching {'{'}status{'}'} invoices: {'{'}response.status_code{'}'}")
    
    return all_unpaid

if __name__ == "__main__":
    unpaid_invoices = get_all_unpaid_invoices(PROPERTY_ID)
    if unpaid_invoices:
        print(f"Found {'{'}len(unpaid_invoices){'}'} unpaid invoices.")
        # Show the first invoice with its charges
        if unpaid_invoices:
            invoice = unpaid_invoices[0]
            print(f"Invoice {'{'}invoice['invoiceNumber']{'}'} - Status: {'{'}invoice['status']{'}'}")
            print(f"Total Amount: {invoice['totalAmount']{'}'}")
            print(f"Charges: {'{'}len(invoice.get('charges', [])){'}'}")
            print(json.dumps(invoice, indent=2, default=str))

Example: Get detailed invoices for a specific month

Retrieve all invoice data for a given month with pagination, including updating invoice status.

import requests
import os
import json
from datetime import datetime

# --- Configuration ---
BASE_URL = "https://app.billgauge.com/api/v1"
API_KEY = os.environ.get("BILLGAUGE_API_KEY", "your_api_key_here")
# Current month in YYYY-MM format
QUERY_MONTH = datetime.now().strftime("%Y-%m") 

HEADERS = {
    "Authorization": f"Bearer {'{'}API_KEY{'}'}",
    "Content-Type": "application/json",
}

def get_detailed_invoices_for_month(month):
    """Fetches detailed invoices for a specific month with pagination."""
    print(f"Fetching detailed invoices for {'{'}month{'}'}")
    
    all_invoices = []
    page = 1
    
    while True:
        params = {
            "month": month,
            "details": "true",  # Include charges and custom charges
            "page": page,
            "limit": 50  # Max items per page
        }
        
        response = requests.get(f"{'{'}BASE_URL{'}'}/invoices", headers=HEADERS, params=params)
        
        if not response.ok:
            print(f"Error: {'{'}response.status_code{'}'}")
            print(response.text)
            break
            
        data = response.json()
        invoices = data.get("data", [])
        pagination = data.get("pagination", {'{'}{'}'})
        
        all_invoices.extend(invoices)
        
        # Check if we have more pages
        if page >= pagination.get("totalPages", 1):
            break
        page += 1
    
    return all_invoices

def update_invoice_status(invoice_id, new_status):
    """Update an invoice status."""
    valid_statuses = ["DRAFT", "SENT", "PAID", "OVERDUE", "CANCELLED"]
    
    if new_status not in valid_statuses:
        print(f"Invalid status. Must be one of: {'{'}valid_statuses{'}'}")
        return None
    
    payload = {"status": new_status}
    
    response = requests.patch(
        f"{'{'}BASE_URL{'}'}/invoices/{'{'}invoice_id{'}'}", 
        headers=HEADERS, 
        json=payload
    )
    
    if response.ok:
        return response.json()
    else:
        print(f"Error updating invoice: {'{'}response.status_code{'}'}")
        print(response.text)
        return None

if __name__ == "__main__":
    invoices = get_detailed_invoices_for_month(QUERY_MONTH)
    if invoices:
        print(f"Found {'{'}len(invoices){'}'} invoices for {'{'}QUERY_MONTH{'}'}")
        
        # Show summary
        for invoice in invoices:
            charges_count = len(invoice.get('charges', []))
            custom_charges_count = len(invoice.get('customCharges', []))
            print(f"Invoice {'{'}invoice['invoiceNumber']{'}'}: {'{'}invoice['status']{'}'} - "
                  f"{invoice['totalAmount']{'}'} ({'{'}charges_count{'}'} charges, "
                  f"{'{'}custom_charges_count{'}'} custom charges)")
        
        # Example: Mark the first draft invoice as sent
        draft_invoice = next((inv for inv in invoices if inv['status'] == 'DRAFT'), None)
        if draft_invoice:
            print(f"Updating invoice {'{'}draft_invoice['invoiceNumber']{'}'} to SENT...")
            updated = update_invoice_status(draft_invoice['id'], 'SENT')
            if updated:
                print(f"Successfully updated to {'{'}updated['status']{'}'}")

Example: Analyze a single invoice in detail

Get comprehensive details about a specific invoice, including utility and custom charges breakdown.

import requests
import os
import json

# --- Configuration ---
BASE_URL = "https://app.billgauge.com/api/v1"
API_KEY = os.environ.get("BILLGAUGE_API_KEY", "your_api_key_here")

HEADERS = {
    "Authorization": f"Bearer {'{'}API_KEY{'}'}",
    "Content-Type": "application/json",
}

def get_invoice_details(invoice_id):
    """Get detailed information about a specific invoice."""
    response = requests.get(f"{'{'}BASE_URL{'}'}/invoices/{'{'}invoice_id{'}'}", headers=HEADERS)
    
    if response.ok:
        return response.json()
    else:
        print(f"Error: {'{'}response.status_code{'}'}")
        print(response.text)
        return None

def analyze_invoice_charges(invoice):
    """Analyze the charges breakdown of an invoice."""
    if not invoice:
        return
    
    print(f"=== Invoice Analysis: {'{'}invoice['invoiceNumber']{'}'} ===")
    print(f"Status: {'{'}invoice['status']{'}'}")
    print(f"Total Amount: {invoice['totalAmount']{'}'}")
    print(f"Property: {'{'}invoice['property']['name']{'}'}")
    print(f"Unit: {'{'}invoice['unit']['label']{'}'}")
    print(f"Month: {'{'}invoice['month'][:7]{'}'}")  # YYYY-MM format
    
    # Utility charges breakdown
    charges = invoice.get('charges', [])
    if charges:
        print(f"--- Utility Charges ({'{'}len(charges){'}'}) ---")
        total_utility = 0
        for charge in charges:
            amount = float(charge['amount'])
            total_utility += amount
            print(f"  {'{'}charge['utilityType']{'}'}: {amount:.2f{'}'}")
        print(f"  Total Utility: {total_utility:.2f{'}'}")
    
    # Custom charges
    custom_charges = invoice.get('customCharges', [])
    if custom_charges:
        print(f"--- Custom Charges ({'{'}len(custom_charges){'}'}) ---")
        total_custom = 0
        for charge in custom_charges:
            amount = float(charge['amount'])
            total_custom += amount
            print(f"  {'{'}charge['description']{'}'}: {amount:.2f{'}'}")
        print(f"  Total Custom: {total_custom:.2f{'}'}")
    
    # Payment information
    if invoice.get('stripePaymentLinkUrl'):
        print(f"--- Payment Info ---")
        print(f"Payment Link: {'{'}invoice['stripePaymentLinkUrl']{'}'}")
        print(f"Payment Status: {'{'}invoice.get('paymentStatus', 'N/A'){'}'}")
        if invoice.get('paidAt'):
            print(f"Paid At: {'{'}invoice['paidAt']{'}'}")

if __name__ == "__main__":
    # Replace with an actual invoice ID from your system
    INVOICE_ID = "cm1234567890abcdef"
    
    invoice = get_invoice_details(INVOICE_ID)
    if invoice:
        analyze_invoice_charges(invoice)