# OpenGeo Cambodia API - Full LLM & Agent Documentation > Comprehensive developer and AI agent reference manual for the OpenGeo Cambodia REST API. > Standardized 4-tier Cambodian administrative boundary lookups and OpenStreetMap geocoding. --- ## 1. System Overview & Core Capabilities OpenGeo Cambodia is a production-grade geographic division and geocoding platform specifically designed for Cambodian applications (e-commerce checkout, logistics delivery, KYC verification, administrative records, mobile apps, and government forms). ### Primary Architecture 1. **In-Memory Administrative Store (`AdminStore`)**: - Contains all official Cambodian administrative divisions: **25 Provinces**, **210 Districts**, **1,661 Communes**, and **14,576 Villages**. - Sub-millisecond ($< 1\text{ms}$) response times served directly from memory indices without external network dependencies. - Dual-language support: Official Khmer script (`name_km`) and Latin Romanization (`name_en`). 2. **Normalized Smart Geocoding (`GeocodeService`)**: - Reverse geocodes GPS coordinates (`lat`, `lon`) through OpenStreetMap Nominatim. - Normalizes varied OSM tagging conventions (`admin_level`, `suburb`, `quarter`, `municipality`, `county`) into Cambodia's strict 4-tier administrative hierarchy. --- ## 2. Cambodian Administrative Hierarchy Model Cambodia's official administrative structure adheres to four distinct levels: | Tier | Level Name | Cambodian (KM) | Latin Romanization | Count | Code Format | Hierarchy Example | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | **Tier 1** | Province / Capital | រាជធានី / ខេត្ត | Reachdhanie / Khaet | **25** | 2 digits (`12`) | Phnom Penh (`12`) | | **Tier 2** | District / Khan / Krong | ស្រុក / ខណ្ឌ / ក្រុង | Srok / Khan / Krong | **210** | 4 digits (`1202`) | Doun Penh (`1202`) | | **Tier 3** | Commune / Sangkat | ឃុំ / សង្កាត់ | Khum / Sangkat | **1,661** | 6 digits (`120201`) | Phsar Thmei 1 (`120201`) | | **Tier 4** | Village | ភូមិ | Phum | **14,576** | 8 digits (`12020101`) | Phum 1 (`12020101`) | ### Key Domain Rules 1. **ID Hierarchical Prefix Inheritance**: - A District's 4-digit code starts with its parent Province's 2-digit code (e.g., Province `12` -> District `1202`). - A Commune's 6-digit code starts with its parent District's 4-digit code (e.g., District `1202` -> Commune `120201`). - A Village's 8-digit code starts with its parent Commune's 6-digit code (e.g., Commune `120201` -> Village `12020101`). 2. **Administrative Types**: - **Capital (រាជធានី)**: Phnom Penh (single capital). - **Provinces (ខេត្ត)**: 24 provinces (e.g., Siem Reap, Kandal, Battambang, Kampot). - **Khan (ខណ្ឌ)**: Urban municipal subdivisions inside Phnom Penh (14 Khans). - **Krong (ក្រុង)**: Urban municipal cities outside Phnom Penh (32 Krongs, e.g., Krong Siem Reap). - **Srok (ស្រុក)**: Rural districts (164 Sroks). - **Sangkat (សង្កាត់)**: Urban communes inside Khans and Krongs. - **Khum (ឃុំ)**: Rural communes inside Sroks. - **Phum (ភូមិ)**: Villages (the smallest administrative unit). --- ## 3. Network & Connection Details - **Production Base URL**: `https://opengeo-cambodia.pphat.me` - **Local Development URL**: `http://localhost:3000` - **Default Port**: `3000` - **Protocols**: HTTPS (HTTP/1.1 and HTTP/2 supported) - **CORS**: `Access-Control-Allow-Origin: *` enabled for all origins with `GET` and `OPTIONS` - **ETag Support**: Generates standard HTTP `ETag` headers. Clients can pass `If-None-Match` to receive `304 Not Modified` - **Rate Limits**: - Administrative routes (`/api/admin/*`): `120` requests per minute per IP. - Geocoding routes (`/api/geocode/*`): `30` requests per minute per IP. - Response headers include: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`. --- ## 4. Complete REST API Endpoint Reference ### 4.1. List All Provinces Returns all 25 provinces and the capital city of Cambodia. - **Method**: `GET` - **Path**: `/api/admin/provinces` - **Query Parameters**: None - **Response Format**: ```json { "success": true, "data": [ { "id": 12, "code": "12", "name": "Phnom Penh", "name_en": "Phnom Penh", "name_km": "រាជធានីភ្នំពេញ", "typeKm": "រាជធានី", "level": "province", "lat": 11.5564, "lon": 104.9282 } ], "meta": { "total": 25, "timestamp": "2026-10-27T00:00:00.000Z", "source": "local_cache" } } ``` --- ### 4.2. List Districts within a Province Returns all districts, khans, or krongs belonging to a specific province. - **Method**: `GET` - **Path**: `/api/admin/provinces/:id/districts` - **Path Parameters**: - `id` (integer, required): Province ID (e.g. `12` for Phnom Penh, `17` for Siem Reap). - **Validation**: `:id` must be a positive integer ($1-9$ digits). - **Response Format**: ```json { "success": true, "data": [ { "id": 1202, "code": "1202", "parentId": 12, "name": "Doun Penh", "name_en": "Doun Penh", "name_km": "ខណ្ឌដូនពេញ", "typeKm": "ខណ្ឌ", "level": "district" } ], "meta": { "total": 14, "timestamp": "2026-10-27T00:00:00.000Z", "source": "local_cache" } } ``` --- ### 4.3. List Communes within a District Returns all communes or sangkats belonging to a specific district. - **Method**: `GET` - **Path**: `/api/admin/districts/:id/communes` - **Path Parameters**: - `id` (integer, required): District ID (e.g. `1202` for Doun Penh). - **Validation**: `:id` must be a positive integer ($1-9$ digits). - **Response Format**: ```json { "success": true, "data": [ { "id": 120201, "code": "120201", "parentId": 1202, "name": "Phsar Thmei 1", "name_en": "Phsar Thmei 1", "name_km": "សង្កាត់ផ្សារថ្មីទី១", "typeKm": "សង្កាត់", "level": "commune" } ], "meta": { "total": 11, "timestamp": "2026-10-27T00:00:00.000Z", "source": "local_cache" } } ``` --- ### 4.4. List Villages within a Commune Returns all villages (phums) belonging to a specific commune. - **Method**: `GET` - **Path**: `/api/admin/communes/:id/villages` - **Path Parameters**: - `id` (integer, required): Commune ID (e.g. `120201` for Phsar Thmei 1). - **Validation**: `:id` must be a positive integer ($1-9$ digits). - **Response Format**: ```json { "success": true, "data": [ { "id": 12020101, "code": "12020101", "parentId": 120201, "name": "Phum 1", "name_en": "Phum 1", "name_km": "ភូមិ១", "typeKm": "ភូមិ", "level": "village" } ], "meta": { "total": 3, "timestamp": "2026-10-27T00:00:00.000Z", "source": "local_cache" } } ``` --- ### 4.5. Single Item Lookup by ID Retrieves details of any single administrative division by its ID. - **Method**: `GET` - **Path**: `/api/admin/item/:id` - **Path Parameters**: - `id` (integer, required): Administrative division ID. - **Response Format (200 OK)**: ```json { "success": true, "data": { "id": 1202, "code": "1202", "parentId": 12, "name": "Doun Penh", "name_en": "Doun Penh", "name_km": "ខណ្ឌដូនពេញ", "typeKm": "ខណ្ឌ", "level": "district" } } ``` --- ### 4.6. Resolve Ascending Hierarchy Tree Given any division ID (e.g., a village ID or commune ID), returns the complete ascending hierarchy: Province -> District -> Commune -> Village. - **Method**: `GET` - **Path**: `/api/admin/hierarchy/:id` - **Path Parameters**: - `id` (integer, required): Any administrative division ID. - **Response Format**: ```json { "success": true, "data": { "province": { "id": 12, "code": "12", "name": "Phnom Penh", "name_en": "Phnom Penh", "name_km": "រាជធានីភ្នំពេញ", "level": "province" }, "district": { "id": 1202, "code": "1202", "name": "Doun Penh", "name_en": "Doun Penh", "name_km": "ខណ្ឌដូនពេញ", "level": "district" }, "commune": { "id": 120201, "code": "120201", "name": "Phsar Thmei 1", "name_en": "Phsar Thmei 1", "name_km": "សង្កាត់ផ្សារថ្មីទី១", "level": "commune" }, "village": { "id": 12020101, "code": "12020101", "name": "Phum 1", "name_en": "Phum 1", "name_km": "ភូមិ១", "level": "village" } } } ``` --- ### 4.7. Fast In-Memory Administrative Search Performs instant text search across Khmer script, Latin romanizations, and division IDs. - **Method**: `GET` - **Path**: `/api/admin/search` - **Query Parameters**: - `q` (string, required): Search query string (maximum 100 characters). - `level` (string, optional): Filter by tier (`province`, `district`, `commune`, `village`). - `limit` (integer, optional): Maximum results ($1-100$, default: `20`). - **Response Format**: ```json { "success": true, "data": [ { "id": 1202, "name_en": "Doun Penh", "name_km": "ខណ្ឌដូនពេញ", "level": "district", "parentId": 12 } ], "meta": { "total": 1, "timestamp": "2026-10-27T00:00:00.000Z", "source": "local_cache" } } ``` --- ### 4.8. Summary Statistics Returns the record counts of all loaded administrative divisions. - **Method**: `GET` - **Path**: `/api/admin/stats` - **Response Format**: ```json { "success": true, "data": { "provinces": 25, "districts": 210, "communes": 1661, "villages": 14576, "lastSyncAt": "2026-10-27T00:00:00.000Z" } } ``` --- ### 4.9. Reverse Geocode (GPS to Cambodian Address) Converts latitude and longitude into normalized Cambodian administrative components. - **Method**: `GET` - **Path**: `/api/geocode/reverse` - **Query Parameters**: - `lat` (float, required): Latitude between `-90.0` and `90.0` (Cambodia is roughly `10.0` to `14.7`). - `lon` (float, required): Longitude between `-180.0` and `180.0` (Cambodia is roughly `102.3` to `107.6`). - **Response Format**: ```json { "success": true, "data": { "displayName": "Doun Penh, Phnom Penh, 120201, Cambodia", "province": "Phnom Penh", "district": "Doun Penh", "commune": "Voat Phnum", "village": null, "postcode": "120201", "lat": 11.5714, "lon": 104.9250, "osmId": 1234567, "osmType": "relation" }, "meta": { "tookMs": 142.5, "timestamp": "2026-10-27T00:00:00.000Z", "source": "nominatim" } } ``` --- ### 4.10. Free-text Geocoding Search Performs free-text search bounded strictly to Cambodia's geographical boundaries. - **Method**: `GET` - **Path**: `/api/geocode/search` - **Query Parameters**: - `q` (string, required): Place or landmark name (maximum 100 characters). - `limit` (integer, optional): Maximum results ($1-50$, default: `10`). - **Response Format**: ```json { "success": true, "data": [ { "displayName": "Angkor Wat, Siem Reap, Cambodia", "province": "Siem Reap", "district": "Siem Reap", "commune": "Nokor Thum", "village": null, "postcode": "171201", "lat": 13.4125, "lon": 103.8670 } ], "meta": { "total": 1, "tookMs": 180.2, "timestamp": "2026-10-27T00:00:00.000Z", "source": "nominatim" } } ``` --- ### 4.11. Health Check & Diagnostics Used by container orchestrators, load balancers, and uptime monitors. - **Method**: `GET` - **Path**: `/health` - **Response Format (200 OK)**: ```json { "status": "ok", "uptimeSeconds": 86400, "timestamp": "2026-10-27T00:00:00.000Z" } ``` --- ## 5. Standard Error Responses All error payloads follow a uniform structure: ```json { "success": false, "data": null, "error": "Error description message" } ``` | HTTP Code | Description | Typical Cause | | :--- | :--- | :--- | | `400 Bad Request` | Missing or invalid parameters | Invalid integer ID, query length > 100, lat/lon out of range | | `404 Not Found` | Resource not found | Invalid division ID, geocoding returned no results | | `429 Too Many Requests` | Rate limit exceeded | Exceeded 120 req/min for admin or 30 req/min for geocode | | `500 Internal Error` | Server execution error | Sanitized error message without leaking internal stack traces | --- ## 6. Client Integration Code Examples ### 6.1. Complete Cascading Address Selector (TypeScript / Node.js) ```typescript class OpenGeoClient { private baseUrl: string; constructor(baseUrl = 'https://opengeo-cambodia.pphat.me') { this.baseUrl = baseUrl; } async getProvinces() { const res = await fetch(`${this.baseUrl}/api/admin/provinces`); const json = await res.json(); return json.data; } async getDistricts(provinceId: number) { const res = await fetch(`${this.baseUrl}/api/admin/provinces/${provinceId}/districts`); const json = await res.json(); return json.data; } async getCommunes(districtId: number) { const res = await fetch(`${this.baseUrl}/api/admin/districts/${districtId}/communes`); const json = await res.json(); return json.data; } async getVillages(communeId: number) { const res = await fetch(`${this.baseUrl}/api/admin/communes/${communeId}/villages`); const json = await res.json(); return json.data; } async reverseGeocode(lat: number, lon: number) { const res = await fetch(`${this.baseUrl}/api/geocode/reverse?lat=${lat}&lon=${lon}`); const json = await res.json(); return json.data; } } // Example usage: const client = new OpenGeoClient(); const provinces = await client.getProvinces(); console.log(`Loaded ${provinces.length} provinces.`); ``` ### 6.2. Python Integration ```python import requests class OpenGeoCambodia: def __init__(self, base_url="https://opengeo-cambodia.pphat.me"): self.base_url = base_url.rstrip("/") def get_provinces(self): r = requests.get(f"{self.base_url}/api/admin/provinces") r.raise_for_status() return r.json().get("data", []) def get_districts(self, province_id: int): r = requests.get(f"{self.base_url}/api/admin/provinces/{province_id}/districts") r.raise_for_status() return r.json().get("data", []) def reverse_geocode(self, lat: float, lon: float): r = requests.get(f"{self.base_url}/api/geocode/reverse", params={"lat": lat, "lon": lon}) r.raise_for_status() return r.json().get("data") # Example usage: geo = OpenGeoCambodia() provinces = geo.get_provinces() print(f"Total Provinces: {len(provinces)}") ``` --- ## 7. LLM & AI Agent Guidelines (Best Practices) When generating code or addressing user questions about Cambodian geography: 1. **Always Use 4 Tiers**: Never conflate District (Khan/Srok) with Commune (Sangkat/Khum). Cambodian addresses are strictly 4 levels. 2. **Khmer vs Romanized**: When storing user data, preserve both `name_km` and `name_en` for dual-language invoices, KYC, and waybills. 3. **Phnom Penh Special Case**: Remember that Phnom Penh has **Khans** (not Sroks) and **Sangkats** (not Khums). 4. **Sub-millisecond Cascades**: Instruct developers to call `/api/admin/*` directly from front-ends as it has built-in CORS and $<1\text{ms}$ latency. 5. **Rate Limit Handling**: When writing high-throughput scripts, handle `429 Too Many Requests` with exponential backoff using the `Retry-After` header.