The definitive
Tanzania locations API

Complete address hierarchy from regions to streets, with postcodes. REST API on the edge + offline npm package.

31
Regions
168
Districts
4,054
Wards
75,061
Streets

The Problem

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.

The Solution

REST API

Edge-deployed on Cloudflare Workers with D1. 12 endpoints, full-text search, autocomplete, CORS enabled.

  • - Sub-50ms latency from Africa
  • - FTS5 full-text search
  • - HATEOAS navigation links
  • - Rate limiting: 100 req/min

npm Package

Offline-first, fully typed, zero network dependency. All 75K+ locations bundled with typed lookup functions.

  • - TypeScript-first with full types
  • - Lazy Map indexes for O(1) lookups
  • - Zero dependencies
  • - Works offline / edge / mobile

API Reference

Base URL: https://tz-locations-api.mwemanoor.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.

Regions

GET /v1/regions

List all 31 regions.

curl https://tz-locations-api.mwemanoor.workers.dev/v1/regions
Example response
{
  "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 }
}
GET /v1/regions/:slug

Get a single region by slug.

ParamTypeDescription
slugstringRegion slug, e.g. dar-es-salaam
curl https://tz-locations-api.mwemanoor.workers.dev/v1/regions/dar-es-salaam
GET /v1/regions/:slug/districts

List districts in a region.

ParamTypeDescription
slugstringRegion slug
curl https://tz-locations-api.mwemanoor.workers.dev/v1/regions/dar-es-salaam/districts

Districts

GET /v1/districts/:slug

Get a single district by slug.

ParamTypeDescription
slugstringDistrict slug, e.g. ilala
curl https://tz-locations-api.mwemanoor.workers.dev/v1/districts/ilala
GET /v1/districts/:slug/wards

List wards in a district.

ParamTypeDescription
slugstringDistrict slug
curl https://tz-locations-api.mwemanoor.workers.dev/v1/districts/ilala/wards

Wards

GET /v1/wards/:slug

Get a single ward with its postcode.

ParamTypeDescription
slugstringWard slug, e.g. buguruni
curl https://tz-locations-api.mwemanoor.workers.dev/v1/wards/buguruni
GET /v1/wards/:slug/streets

List streets in a ward.

ParamTypeDescription
slugstringWard slug
curl https://tz-locations-api.mwemanoor.workers.dev/v1/wards/buguruni/streets

Streets

GET /v1/streets/:id

Get a single street by ID.

ParamTypeDescription
idnumberStreet ID
curl https://tz-locations-api.mwemanoor.workers.dev/v1/streets/1

Postcodes

GET /v1/postcodes/:postcode

Lookup a location by 5-digit postcode. Returns the ward and all streets with that postcode.

ParamTypeDescription
postcodestring5-digit postcode, e.g. 12102
curl https://tz-locations-api.mwemanoor.workers.dev/v1/postcodes/12102
Example response
{
  "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" }
    ]
  }
}

Search

GET /v1/search

Full-text search across all locations using FTS5.

ParamTypeDescription
qstringSearch query (required)
typestringFilter by type: region, district, ward, street
limitnumberMax results (default: 20, max: 100)
curl "https://tz-locations-api.mwemanoor.workers.dev/v1/search?q=buguruni&type=ward"
GET /v1/autocomplete

Prefix-based autocomplete. Optimized for search-as-you-type UIs.

ParamTypeDescription
qstringSearch prefix (required, min 2 chars)
limitnumberMax results (default: 10)
curl "https://tz-locations-api.mwemanoor.workers.dev/v1/autocomplete?q=bug"

Stats

GET /v1/stats

Get counts per location level.

curl https://tz-locations-api.mwemanoor.workers.dev/v1/stats
Example response
{
  "data": {
    "regions": 31,
    "districts": 168,
    "wards": 4054,
    "streets": 75061
  }
}

npm Package

Offline-first, fully typed. All 75,061 locations bundled — no network needed.

npm install tz-locations

Functions

Function Returns Description
getRegions()Region[]All 31 regions
getRegion(slug)Region | undefinedSingle region by slug
getDistricts(regionSlug?)District[]All districts or filtered by region
getDistrictsByRegion(slug)District[]Districts in a region
getDistrict(slug)District | undefinedSingle district by slug
getWards(region?, district?)Ward[]All wards or filtered
getWardsByDistrict(region, district)Ward[]Wards in a district
getWard(slug)Ward | undefinedSingle ward by slug
getStreets(region, district, ward)Street[]Streets in a ward
getByPostcode(postcode)PostcodeLookupResultLookup by 5-digit postcode
searchLocations(query, opts?)Location[]Search by name
getStats()objectCount of each entity type

TypeScript Types

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[];
}

Examples

Cascading Address Select

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');

Postcode Validation

import { getByPostcode } from 'tz-locations';

const result = getByPostcode('12102');
if (result.ward) {
  console.log(`${result.ward.name}, ${result.ward.district_slug}`);
  // "Buguruni, ilala"
}

Search-as-you-type

import { searchLocations } from 'tz-locations';

function onSearchInput(query: string) {
  if (query.length < 2) return [];
  return searchLocations(query, { limit: 10 });
}

Use Cases

E-commerce Checkout

Cascading address dropdowns for shipping. Auto-fill postcode from ward selection.

Delivery & Logistics

Structured delivery zones. Map streets to wards for route planning.

Government & NGO

Location-based reporting and aggregation at region, district, or ward level.

Fintech KYC

Address verification for customer onboarding. Validate postcodes against known locations.

Mobile Offline-First

Use the npm package for offline address selection — no network required.

Data Analysis

Normalize and enrich address data. Join with external datasets using postcodes.