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.
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)
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))