Zip code lookup is a powerful feature that improves form usability and data quality. When a user enters their zip code, your app can automatically fill in their city, state, and country. In this tutorial, we'll build a zip code lookup feature using CountryDataAPI's postal codes endpoint, with examples in both JavaScript and Python.
The CountryDataAPI Zip Code Endpoint
CountryDataAPI provides a dedicated endpoint for querying zip codes by country:
GET https://api.countrydataapi.com/v1/zipcode/by-country?country=US&zip=10001
Parameters:
country— ISO 3166-1 alpha-2 country code (e.g.,US,DE,GB)zip— The zip/postal code to look up
Sample response:
{
"zip": "10001",
"city": "New York",
"state": "New York",
"state_code": "NY",
"country": "United States",
"country_code": "US"
}
JavaScript Implementation
Basic Fetch Example
const lookupZipCode = async (countryCode, zipCode) => {
const apiKey = process.env.COUNTRY_API_KEY;
const url = new URL('https://api.countrydataapi.com/v1/zipcode/by-country');
url.searchParams.set('country', countryCode);
url.searchParams.set('zip', zipCode);
const response = await fetch(url.toString(), {
headers: { 'x-api-key': apiKey }
});
if (!response.ok) {
if (response.status === 404) {
throw new Error('Zip code not found');
}
throw new Error('API request failed');
}
return response.json();
};
// Usage
try {
const data = await lookupZipCode('US', '90210');
console.log(data.city); // "Beverly Hills"
console.log(data.state); // "California"
} catch (error) {
console.error(error.message);
}
Auto-fill Address Form
Here's a complete example that auto-fills an address form when the user enters a zip code:
// address-form.js
document.getElementById('zipCode').addEventListener('blur', async function() {
const zip = this.value.trim();
const country = document.getElementById('country').value;
if (!zip || !country) return;
const statusEl = document.getElementById('zip-status');
statusEl.textContent = 'Looking up...';
try {
const data = await lookupZipCode(country, zip);
document.getElementById('city').value = data.city;
document.getElementById('state').value = data.state;
statusEl.textContent = '✓ Address filled';
statusEl.className = 'success';
} catch (error) {
statusEl.textContent = '✗ Zip code not found';
statusEl.className = 'error';
}
});
React Hook for Zip Code Lookup
import { useState, useCallback } from 'react';
export const useZipLookup = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [result, setResult] = useState(null);
const lookup = useCallback(async (country, zip) => {
setLoading(true);
setError(null);
try {
const res = await fetch(
`https://api.countrydataapi.com/v1/zipcode/by-country?country=${country}&zip=${zip}`,
{ headers: { 'x-api-key': process.env.REACT_APP_API_KEY } }
);
if (!res.ok) throw new Error('Not found');
const data = await res.json();
setResult(data);
return data;
} catch (err) {
setError(err.message);
return null;
} finally {
setLoading(false);
}
}, []);
return { lookup, loading, error, result };
};
Python Implementation
Basic requests Example
import os
import requests
API_KEY = os.environ.get('COUNTRY_API_KEY')
BASE_URL = 'https://api.countrydataapi.com/v1'
def lookup_zip_code(country_code: str, zip_code: str) -> dict:
"""
Look up location data for a zip code.
Args:
country_code: ISO 3166-1 alpha-2 code (e.g., 'US', 'DE')
zip_code: The postal/zip code to look up
Returns:
dict with city, state, country data
Raises:
ValueError: If zip code is not found
requests.HTTPError: On API errors
"""
response = requests.get(
f'{BASE_URL}/zipcode/by-country',
params={'country': country_code, 'zip': zip_code},
headers={'x-api-key': API_KEY},
timeout=10
)
if response.status_code == 404:
raise ValueError(f'Zip code {zip_code} not found in {country_code}')
response.raise_for_status()
return response.json()
# Usage
try:
data = lookup_zip_code('DE', '10115')
print(f"City: {data['city']}") # Berlin
print(f"State: {data['state']}") # Berlin
except ValueError as e:
print(f"Error: {e}")
Django Form Field with Validation
from django import forms
from django.core.exceptions import ValidationError
import requests
class AddressForm(forms.Form):
country = forms.ChoiceField(choices=[]) # Populate from API
zip_code = forms.CharField(max_length=20)
city = forms.CharField(max_length=100, required=False)
state = forms.CharField(max_length=100, required=False)
def clean(self):
cleaned_data = super().clean()
country = cleaned_data.get('country')
zip_code = cleaned_data.get('zip_code')
if country and zip_code:
try:
data = lookup_zip_code(country, zip_code)
cleaned_data['city'] = data.get('city', '')
cleaned_data['state'] = data.get('state', '')
except ValueError:
raise ValidationError(
f'Invalid zip code {zip_code} for {country}'
)
return cleaned_data
Handling Edge Cases
Different countries use different postal code formats:
- US: 5 digits (e.g.,
10001) or ZIP+4 (e.g.,10001-1234) - UK: Alphanumeric (e.g.,
SW1A 1AA) - Canada: Alphanumeric (e.g.,
K1A 0A9) - Germany: 5 digits (e.g.,
10115)
CountryDataAPI handles all these formats. Simply pass the raw code and the API normalizes it internally.
Rate Limiting and Caching
To avoid excessive API calls, implement client-side caching:
const zipCache = new Map();
const lookupWithCache = async (country, zip) => {
const key = `${country}:${zip}`;
if (zipCache.has(key)) return zipCache.get(key);
const data = await lookupZipCode(country, zip);
zipCache.set(key, data);
return data;
};
Next Steps
Now that you have zip code lookup working, explore:

