Complete address hierarchy from regions to streets, with postcodes. REST API on the edge + offline npm package.
There's no structured, developer-friendly address data for Tanzania. Every team building for TZ scrapes the same postal website, maintains brittle spreadsheets, or hardcodes a handful of locations. The data exists — but not in a format developers can use.
Edge-deployed on Cloudflare Workers with D1. 12 endpoints, full-text search, autocomplete, CORS enabled.
Offline-first, fully typed, zero network dependency. All 75K+ locations bundled with typed lookup functions.
Base URL: https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev
Response format: All responses use { data, meta } envelope.
Errors: { error: { code, message } }
Rate limiting: 100 requests/minute per IP.
Navigation: Responses include _links for HATEOAS navigation.
/v1/regions
List all 31 regions.
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/regions
{
"data": [
{ "name": "Arusha", "slug": "arusha", "_links": { "self": "/v1/regions/arusha", "districts": "/v1/regions/arusha/districts" } },
{ "name": "Dar es Salaam", "slug": "dar-es-salaam", "_links": { "self": "/v1/regions/dar-es-salaam", "districts": "/v1/regions/dar-es-salaam/districts" } }
],
"meta": { "total": 31 }
}
/v1/regions/:slug
Get a single region by slug.
| Param | Type | Description |
|---|---|---|
slug | string | Region slug, e.g. dar-es-salaam |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/regions/dar-es-salaam
/v1/regions/:slug/districts
List districts in a region.
| Param | Type | Description |
|---|---|---|
slug | string | Region slug |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/regions/dar-es-salaam/districts
/v1/districts/:slug
Get a single district by slug.
| Param | Type | Description |
|---|---|---|
slug | string | District slug, e.g. ilala |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/districts/ilala
/v1/districts/:slug/wards
List wards in a district.
| Param | Type | Description |
|---|---|---|
slug | string | District slug |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/districts/ilala/wards
/v1/wards/:slug
Get a single ward with its postcode.
| Param | Type | Description |
|---|---|---|
slug | string | Ward slug, e.g. buguruni |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/wards/buguruni
/v1/wards/:slug/streets
List streets in a ward.
| Param | Type | Description |
|---|---|---|
slug | string | Ward slug |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/wards/buguruni/streets
/v1/streets/:id
Get a single street by ID.
| Param | Type | Description |
|---|---|---|
id | number | Street ID |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/streets/1
/v1/postcodes/:postcode
Lookup a location by 5-digit postcode. Returns the ward and all streets with that postcode.
| Param | Type | Description |
|---|---|---|
postcode | string | 5-digit postcode, e.g. 12102 |
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/postcodes/12102
{
"data": {
"ward": { "name": "Buguruni", "slug": "buguruni", "postcode": "12102", "district_slug": "ilala", "region_slug": "dar-es-salaam" },
"streets": [
{ "name": "Malapa", "postcode": "12102" },
{ "name": "Mnyamani", "postcode": "12102" }
]
}
}
/v1/search
Full-text search across all locations using FTS5.
| Param | Type | Description |
|---|---|---|
q | string | Search query (required) |
type | string | Filter by type: region, district, ward, street |
limit | number | Max results (default: 20, max: 100) |
curl "https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/search?q=buguruni&type=ward"
/v1/autocomplete
Prefix-based autocomplete. Optimized for search-as-you-type UIs.
| Param | Type | Description |
|---|---|---|
q | string | Search prefix (required, min 2 chars) |
limit | number | Max results (default: 10) |
curl "https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/autocomplete?q=bug"
/v1/stats
Get counts per location level.
curl https://tz-locations-api.YOUR-SUBDOMAIN.workers.dev/v1/stats
{
"data": {
"regions": 31,
"districts": 168,
"wards": 4054,
"streets": 75061
}
}
Offline-first, fully typed. All 75,061 locations bundled — no network needed.
npm install tz-locations
| Function | Returns | Description |
|---|---|---|
getRegions() | Region[] | All 31 regions |
getRegion(slug) | Region | undefined | Single region by slug |
getDistricts(regionSlug?) | District[] | All districts or filtered by region |
getDistrictsByRegion(slug) | District[] | Districts in a region |
getDistrict(slug) | District | undefined | Single district by slug |
getWards(region?, district?) | Ward[] | All wards or filtered |
getWardsByDistrict(region, district) | Ward[] | Wards in a district |
getWard(slug) | Ward | undefined | Single ward by slug |
getStreets(region, district, ward) | Street[] | Streets in a ward |
getByPostcode(postcode) | PostcodeLookupResult | Lookup by 5-digit postcode |
searchLocations(query, opts?) | Location[] | Search by name |
getStats() | object | Count of each entity type |
interface Region {
name: string;
slug: string;
}
interface District {
name: string;
slug: string;
region_slug: string;
}
interface Ward {
name: string;
slug: string;
district_slug: string;
region_slug: string;
}
interface Street {
name: string;
postcode: string;
ward_slug: string;
district_slug: string;
region_slug: string;
}
interface PostcodeLookupResult {
ward: Ward | null;
streets: Street[];
}
import { getRegions, getDistrictsByRegion, getWardsByDistrict, getStreets } from 'tz-locations';
// Step 1: User picks a region
const regions = getRegions();
// Step 2: Load districts for selected region
const districts = getDistrictsByRegion('dar-es-salaam');
// Step 3: Load wards for selected district
const wards = getWardsByDistrict('dar-es-salaam', 'ilala');
// Step 4: Load streets for selected ward
const streets = getStreets('dar-es-salaam', 'ilala', 'buguruni');
import { getByPostcode } from 'tz-locations';
const result = getByPostcode('12102');
if (result.ward) {
console.log(`${result.ward.name}, ${result.ward.district_slug}`);
// "Buguruni, ilala"
}
import { searchLocations } from 'tz-locations';
function onSearchInput(query: string) {
if (query.length < 2) return [];
return searchLocations(query, { limit: 10 });
}
Cascading address dropdowns for shipping. Auto-fill postcode from ward selection.
Structured delivery zones. Map streets to wards for route planning.
Location-based reporting and aggregation at region, district, or ward level.
Address verification for customer onboarding. Validate postcodes against known locations.
Use the npm package for offline address selection — no network required.
Normalize and enrich address data. Join with external datasets using postcodes.