# Water Billing System - API Documentation

## Overview

The Water Billing System provides a comprehensive REST API for mobile applications to integrate with the water meter reading and billing system.

**Base URL**: `https://billing.happyimart.com/api/v1`

---

## Authentication

### Header Authentication
All API requests must include:

```
X-Tenant: property-slug
```

Where `property-slug` is the tenant's unique identifier (e.g., `barangay-ws`).

### API Key
Each request must include an `api_key` parameter in the request body or query string.

**Example Header**:
```
X-Tenant: barangay-ws
Content-Type: application/json
```

---

## Error Responses

All error responses follow this format:

```json
{
  "message": "Error description",
  "errors": {
    "field": ["Error message"]
  }
}
```

### Common HTTP Status Codes

- `200` - Success
- `201` - Created
- `400` - Bad Request
- `404` - Not Found
- `409` - Conflict
- `422` - Unprocessable Entity
- `500` - Server Error

---

## API Endpoints

### 1. Get Client Details

Get client account information and current balance.

**Endpoint**
```
POST /api/v1/meter-reading/client-details
```

**Request Body**
```json
{
  "api_key": "your-secret-api-key",
  "account_number": "ACC-1-000001"
}
```

**Success Response** (200)
```json
{
  "client": {
    "id": 1,
    "account_number": "ACC-1-000001",
    "name": "Juan Dela Cruz",
    "meter_number": "WM-12345",
    "status": "active",
    "outstanding_balance": 1500.00
  }
}
```

**Error Responses**
```json
{
  "message": "Tenant not found"
}
```

```json
{
  "message": "Client not found"
}
```

---

### 2. Submit Meter Reading

Submit a new water meter reading. Automatically generates a billing statement.

**Endpoint**
```
POST /api/v1/meter-reading/submit
```

**Request Body**
```json
{
  "api_key": "your-secret-api-key",
  "account_number": "ACC-1-000001",
  "reading_value": 1234.56,
  "recorded_by": "John Doe",
  "notes": "Optional reading notes"
}
```

**Required Fields**
- `api_key` (string) - API authentication key
- `account_number` (string) - Client's account number
- `reading_value` (number) - Current meter reading in cubic meters

**Optional Fields**
- `recorded_by` (string) - Name of person recording reading
- `notes` (string) - Additional notes or comments

**Success Response** (201)
```json
{
  "message": "Meter reading submitted successfully",
  "reading": {
    "id": 5,
    "client_id": 1,
    "reading_value": 1234.56,
    "reading_date": "2026-01-03T14:30:00Z",
    "status": "draft",
    "units_consumed": 5.25
  },
  "billing": {
    "billing_number": "BIL-1-000001",
    "units_consumed": 5.25,
    "total_amount_due": 262.50,
    "due_date": "2026-01-18T23:59:59Z"
  }
}
```

**Error Responses**

Duplicate reading today (409):
```json
{
  "message": "Reading already submitted today",
  "reading": {
    "id": 5,
    "reading_value": 1234.56,
    "reading_date": "2026-01-03T10:00:00Z"
  }
}
```

Validation error (422):
```json
{
  "message": "The reading value field is required.",
  "errors": {
    "reading_value": ["The reading value field is required."]
  }
}
```

---

### 3. Get Readings History

Retrieve the meter reading history for a client.

**Endpoint**
```
GET /api/v1/meter-reading/history?api_key=xxx&account_number=ACC-1-000001
```

**Query Parameters**
- `api_key` (required) - API authentication key
- `account_number` (required) - Client's account number

**Success Response** (200)
```json
{
  "readings": [
    {
      "id": 5,
      "reading_value": 1234.56,
      "previous_reading": 1229.31,
      "units_consumed": 5.25,
      "reading_date": "2026-01-03T14:30:00Z",
      "recorded_by": "John Doe",
      "status": "approved",
      "notes": "Normal reading"
    },
    {
      "id": 4,
      "reading_value": 1229.31,
      "previous_reading": 1223.40,
      "units_consumed": 5.91,
      "reading_date": "2025-12-03T10:15:00Z",
      "recorded_by": "Mobile App",
      "status": "approved",
      "notes": null
    },
    {
      "id": 3,
      "reading_value": 1223.40,
      "previous_reading": 1217.15,
      "units_consumed": 6.25,
      "reading_date": "2025-11-03T09:45:00Z",
      "recorded_by": "John Doe",
      "status": "approved",
      "notes": null
    }
  ]
}
```

**Note**: Returns last 12 months of readings, ordered by most recent first.

---

### 4. Get Outstanding Billings

Retrieve all outstanding billing statements for a client.

**Endpoint**
```
GET /api/v1/billings?api_key=xxx&account_number=ACC-1-000001
```

**Query Parameters**
- `api_key` (required) - API authentication key
- `account_number` (required) - Client's account number

**Success Response** (200)
```json
{
  "billings": [
    {
      "id": 1,
      "billing_number": "BIL-1-000001",
      "previous_reading": 1229.31,
      "current_reading": 1234.56,
      "units_consumed": 5.25,
      "subtotal": 262.50,
      "tax": 0.00,
      "other_charges": 0.00,
      "penalties": 0.00,
      "total_amount_due": 262.50,
      "amount_paid": 0.00,
      "balance": 262.50,
      "billing_date": "2026-01-03T14:30:00Z",
      "due_date": "2026-01-18T23:59:59Z",
      "status": "sent",
      "remarks": null
    },
    {
      "id": 2,
      "billing_number": "BIL-1-000002",
      "previous_reading": 1223.40,
      "current_reading": 1229.31,
      "units_consumed": 5.91,
      "subtotal": 295.50,
      "tax": 0.00,
      "other_charges": 0.00,
      "penalties": 0.00,
      "total_amount_due": 295.50,
      "amount_paid": 100.00,
      "balance": 195.50,
      "billing_date": "2025-12-03T10:15:00Z",
      "due_date": "2025-12-18T23:59:59Z",
      "status": "overdue",
      "remarks": "Includes previous month balance"
    }
  ]
}
```

---

## Data Types

### Number Formatting
- Prices and amounts: 2 decimal places (e.g., `1234.56`)
- Meter readings: 2 decimal places (e.g., `1234.56`)
- Consumption: 2 decimal places (e.g., `5.25` cubic meters)

### Date Format
All dates are in ISO 8601 format with UTC timezone:
```
2026-01-03T14:30:00Z
```

For date operations, parse as:
```javascript
new Date("2026-01-03T14:30:00Z")
```

### Status Values

**Reading Statuses**:
- `draft` - Pending approval
- `approved` - Approved and billing generated
- `rejected` - Rejected by operator

**Billing Statuses**:
- `draft` - Created but not sent
- `sent` - Sent to customer
- `overdue` - Past due date but not paid
- `paid` - Fully paid
- `cancelled` - Billing cancelled

---

## Rate Limiting

Currently no rate limiting is implemented. For production, implement:
- Maximum 100 requests per minute per API key
- IP-based throttling if needed

---

## Code Examples

### cURL

**Get Client Details**
```bash
curl -X POST https://billing.happyimart.com/api/v1/meter-reading/client-details \
  -H "X-Tenant: barangay-ws" \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "sk_live_xxxxxxxx",
    "account_number": "ACC-1-000001"
  }'
```

**Submit Meter Reading**
```bash
curl -X POST https://billing.happyimart.com/api/v1/meter-reading/submit \
  -H "X-Tenant: barangay-ws" \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "sk_live_xxxxxxxx",
    "account_number": "ACC-1-000001",
    "reading_value": 1234.56,
    "recorded_by": "Field Officer"
  }'
```

### JavaScript/Node.js

```javascript
const API_BASE = 'https://billing.happyimart.com/api/v1';
const TENANT = 'barangay-ws';
const API_KEY = 'sk_live_xxxxxxxx';

// Get Client Details
async function getClientDetails(accountNumber) {
  const response = await fetch(`${API_BASE}/meter-reading/client-details`, {
    method: 'POST',
    headers: {
      'X-Tenant': TENANT,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      api_key: API_KEY,
      account_number: accountNumber
    })
  });
  
  return await response.json();
}

// Submit Meter Reading
async function submitMeterReading(accountNumber, readingValue) {
  const response = await fetch(`${API_BASE}/meter-reading/submit`, {
    method: 'POST',
    headers: {
      'X-Tenant': TENANT,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      api_key: API_KEY,
      account_number: accountNumber,
      reading_value: readingValue,
      recorded_by: 'Mobile App User'
    })
  });
  
  return await response.json();
}

// Get Readings History
async function getReadingsHistory(accountNumber) {
  const params = new URLSearchParams({
    api_key: API_KEY,
    account_number: accountNumber
  });
  
  const response = await fetch(
    `${API_BASE}/meter-reading/history?${params}`,
    {
      headers: {
        'X-Tenant': TENANT,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return await response.json();
}

// Get Billings
async function getBillings(accountNumber) {
  const params = new URLSearchParams({
    api_key: API_KEY,
    account_number: accountNumber
  });
  
  const response = await fetch(
    `${API_BASE}/billings?${params}`,
    {
      headers: {
        'X-Tenant': TENANT,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return await response.json();
}

// Usage
getClientDetails('ACC-1-000001').then(data => {
  console.log('Client:', data.client);
});
```

### Python

```python
import requests
import json

API_BASE = 'https://billing.happyimart.com/api/v1'
TENANT = 'barangay-ws'
API_KEY = 'sk_live_xxxxxxxx'

headers = {
    'X-Tenant': TENANT,
    'Content-Type': 'application/json'
}

def get_client_details(account_number):
    """Get client account details and balance"""
    payload = {
        'api_key': API_KEY,
        'account_number': account_number
    }
    
    response = requests.post(
        f'{API_BASE}/meter-reading/client-details',
        headers=headers,
        json=payload
    )
    
    return response.json()

def submit_meter_reading(account_number, reading_value, recorded_by='Mobile'):
    """Submit new meter reading"""
    payload = {
        'api_key': API_KEY,
        'account_number': account_number,
        'reading_value': reading_value,
        'recorded_by': recorded_by
    }
    
    response = requests.post(
        f'{API_BASE}/meter-reading/submit',
        headers=headers,
        json=payload
    )
    
    return response.json()

def get_readings_history(account_number):
    """Get last 12 months of readings"""
    params = {
        'api_key': API_KEY,
        'account_number': account_number
    }
    
    response = requests.get(
        f'{API_BASE}/meter-reading/history',
        headers=headers,
        params=params
    )
    
    return response.json()

def get_billings(account_number):
    """Get outstanding billings"""
    params = {
        'api_key': API_KEY,
        'account_number': account_number
    }
    
    response = requests.get(
        f'{API_BASE}/billings',
        headers=headers,
        params=params
    )
    
    return response.json()

# Usage
if __name__ == '__main__':
    client = get_client_details('ACC-1-000001')
    print('Client:', client)
    
    reading = submit_meter_reading('ACC-1-000001', 1234.56)
    print('Reading:', reading)
```

### Kotlin/Android

```kotlin
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import kotlinx.coroutines.*

data class ClientDetailsRequest(
    val api_key: String,
    val account_number: String
)

data class ClientDetailsResponse(
    val client: ClientInfo
)

data class ClientInfo(
    val id: Int,
    val account_number: String,
    val name: String,
    val meter_number: String?,
    val status: String,
    val outstanding_balance: Double
)

interface WaterBillingAPI {
    @POST("meter-reading/client-details")
    suspend fun getClientDetails(
        @Header("X-Tenant") tenant: String,
        @Body request: ClientDetailsRequest
    ): ClientDetailsResponse
}

class WaterBillingService {
    private val retrofit = Retrofit.Builder()
        .baseUrl("https://billing.happyimart.com/api/v1/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    
    private val api = retrofit.create(WaterBillingAPI::class.java)
    
    suspend fun getClientDetails(
        tenant: String,
        accountNumber: String,
        apiKey: String
    ) = withContext(Dispatchers.IO) {
        api.getClientDetails(
            tenant,
            ClientDetailsRequest(apiKey, accountNumber)
        )
    }
}
```

---

## Best Practices

1. **API Key Security**
   - Never expose API keys in client-side code
   - Use server-side proxy for mobile applications
   - Rotate keys regularly
   - Use separate keys per environment

2. **Error Handling**
   - Always check HTTP status codes
   - Parse error messages for user feedback
   - Log errors for debugging
   - Implement retry logic for network failures

3. **Performance**
   - Cache client information locally
   - Batch requests when possible
   - Implement pagination for large datasets
   - Use conditional requests with ETags

4. **Data Validation**
   - Validate readings on client before submission
   - Check for duplicate submissions
   - Validate account numbers format
   - Handle decimal precision correctly

---

## Testing

### Postman Collection
A Postman collection is available for testing all endpoints.

### Sample Requests

```json
{
  "info": {
    "name": "Water Billing System API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Get Client Details",
      "request": {
        "method": "POST",
        "url": "{{base_url}}/meter-reading/client-details"
      }
    }
  ]
}
```

---

## Changelog

### Version 1.0.0 (Jan 2026)
- Initial API release
- 4 core endpoints
- Multi-tenant support
- API key authentication

---

## Support

For API support:
- Check endpoint documentation above
- Review code examples
- Check error messages
- Implement proper error handling

---

**API Documentation Version**: 1.0.0  
**Last Updated**: January 2026
