API Documentation - Endpoints & Examples

Search Country by Postal Code

Overview

The Search Country by Postal Code endpoint allows you to find the country that contains a specific postal/zip code. This is essential for address validation, e-commerce shipping, and location-based services.

Country Data API provides detailed information about +200 different countries

Authentication

All API requests require authentication using an API key passed as a query parameter.

https://api.countrydataapi.com/v1/countries/zipCode?apikey=YOUR_API_KEY&postalCode=10001

Request

HTTP GET

https://api.countrydataapi.com/v1/countries/zipCode

Return the country that contains the postal code

There are 4 types of operations:

  • BASIC: Returns the fields id, lang, country_name, country_short_iso, country_phone_code, country_cca2, country_ccn3, country_cca3, country_cioc.

  • NORMAL: Returns the previous fields and adds country_independent, country_status, country_unMember, country_flag, country_map_googleMaps, country_map_openStreetMaps, country_fifa, country_flag_png, country_flag_svg, country_flag_alt, country_coatOfArms_png, country_coatOfArms_svg, country_startofWeek, country_continent_code, country_current_currency, country_GDP, country_location, country_land, country_terrain, country_climate, country_natural_hazards, country_note, country_history, country_GDP_per_capita_PPP, country_life_expectancy, country_median_age, country_birth_rate, country_death_rate, country_sex_ratio, country_literacy, country_roadways, country_airports, country_railways, country_waterways, country_heliports, country_airports_paved, country_wikipedia_url.

  • ADVANCED: Returns the previous fields and adds country_car_info, _country_idd_info.

  • ALL: Returns the previous fields and adds country_tld, country_capital, country_altSpellings, country_latLng, country_borders, country_timezones, country_continents, country_currencies, country_languages, country_translations, country_capital_info, country_demonyms, country_name.nativeName.

Each country returned with the BASIC method will cost 1 tokens.

Each country returned with the NORMAL method will cost 2 tokens.

Each country returned with the ADVANCED method will cost 3 tokens.

Each country returned with the ALL method will cost 4 tokens.

Query Params


Parameter Type Description
apikey required, token Authentication key of the account
postalCode required, string Postal code you want to search for
limitToken optional, number 1000 (default). Maximum number of countries to be returned
lang optional, lang en (default). Expected language of the response
fields optional, string id,lang,country_name (default). Expected fields in the response

Response

Example Response

[
  {
    "id": "33be30c5-80fc-429d-bf10-bd11f2e3e84c",
    "lang": "en",
    "country_name": "United States",
    "country_short_iso": "US",
    "country_phone_code": "1",
    "country_cca2": "US",
    "country_ccn3": "840",
    "country_cca3": "USA",
    "country_cioc": "USA",
    "country_independent": true,
    "country_status": "officially-assigned",
    "country_unMember": true,
    "country_capital": ["Washington, D.C."],
    "country_region": "Americas",
    "country_subregion": "Northern America",
    "country_flag": "flag-emoji",
    "country_flag_png": "https://flagcdn.com/w320/us.png",
    "country_flag_svg": "https://flagcdn.com/us.svg"
  }
]

Code Examples

cURL

# Search country by postal code (US ZIP)
curl -X GET "https://api.countrydataapi.com/v1/countries/zipCode?apikey=YOUR_API_KEY&postalCode=10001"

# Search by UK postal code
curl -X GET "https://api.countrydataapi.com/v1/countries/zipCode?apikey=YOUR_API_KEY&postalCode=SW1A"

# Search by German postal code
curl -X GET "https://api.countrydataapi.com/v1/countries/zipCode?apikey=YOUR_API_KEY&postalCode=10115"

# With specific fields (NORMAL level)
curl -X GET "https://api.countrydataapi.com/v1/countries/zipCode?apikey=YOUR_API_KEY&postalCode=75001&fields=id,lang,country_name,country_flag_png,country_capital"

# Search with different language response
curl -X GET "https://api.countrydataapi.com/v1/countries/zipCode?apikey=YOUR_API_KEY&postalCode=28001&lang=es"

JavaScript (Fetch)

const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://api.countrydataapi.com/v1/countries';

// Search country by postal code
async function searchCountryByPostalCode(postalCode, options = {}) {
  const params = new URLSearchParams({
    apikey: API_KEY,
    postalCode: postalCode,
    ...options
  });

  try {
    const response = await fetch(`${BASE_URL}/zipCode?${params}`);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error searching country by postal code:', error);
    throw error;
  }
}

// Usage examples
// US ZIP code
searchCountryByPostalCode('10001')
  .then(countries => {
    console.log('Country for ZIP 10001:', countries[0]?.country_name);
  });

// UK postal code
searchCountryByPostalCode('SW1A', {
  fields: 'id,lang,country_name,country_flag_png,country_capital'
})
  .then(countries => {
    console.log('UK postal code result:', countries);
  });

// French postal code with French response
searchCountryByPostalCode('75001', { lang: 'fr' })
  .then(countries => {
    console.log('French result:', countries);
  });

// Validate if postal code belongs to expected country
async function validatePostalCode(postalCode, expectedCountryCode) {
  const countries = await searchCountryByPostalCode(postalCode);
  if (countries.length === 0) {
    return { valid: false, message: 'Invalid postal code' };
  }

  const matchesExpected = countries.some(
    c => c.country_cca2 === expectedCountryCode ||
         c.country_cca3 === expectedCountryCode
  );

  return {
    valid: matchesExpected,
    country: countries[0]?.country_name,
    message: matchesExpected
      ? 'Postal code matches expected country'
      : `Postal code belongs to ${countries[0]?.country_name}`
  };
}

JavaScript (Axios)

import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.countrydataapi.com/v1/countries',
  params: {
    apikey: 'YOUR_API_KEY'
  }
});

// Search country by postal code
async function searchCountryByPostalCode(postalCode, options = {}) {
  try {
    const response = await apiClient.get('/zipCode', {
      params: {
        postalCode: postalCode,
        ...options
      }
    });
    return response.data;
  } catch (error) {
    if (error.response) {
      console.error('API Error:', error.response.status, error.response.data);
    } else {
      console.error('Network Error:', error.message);
    }
    throw error;
  }
}

// Address validation service
const addressValidationService = {
  async validatePostalCode(postalCode) {
    const countries = await searchCountryByPostalCode(postalCode, {
      fields: 'id,lang,country_name,country_cca2,country_flag_png'
    });

    return {
      isValid: countries.length > 0,
      country: countries[0] || null
    };
  },

  async getCountryFromPostalCode(postalCode) {
    return searchCountryByPostalCode(postalCode, {
      fields: 'id,lang,country_name,country_flag_png,country_capital,country_phone_code'
    });
  },

  async matchPostalCodeToCountry(postalCode, countryCode) {
    const result = await this.validatePostalCode(postalCode);
    if (!result.isValid) {
      return { matches: false, error: 'Invalid postal code' };
    }

    return {
      matches: result.country.country_cca2 === countryCode,
      actualCountry: result.country.country_name
    };
  }
};

// Usage
addressValidationService.validatePostalCode('90210')
  .then(result => {
    if (result.isValid) {
      console.log(`Valid postal code for ${result.country.country_name}`);
    }
  });

React

import { useState, useCallback } from 'react';

const API_KEY = 'YOUR_API_KEY';

function usePostalCodeSearch() {
  const [country, setCountry] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [isValid, setIsValid] = useState(null);

  const searchByPostalCode = useCallback(async (postalCode) => {
    if (!postalCode.trim()) {
      setCountry(null);
      setIsValid(null);
      return;
    }

    setLoading(true);
    setError(null);

    const params = new URLSearchParams({
      apikey: API_KEY,
      postalCode: postalCode,
      fields: 'id,lang,country_name,country_flag_png,country_capital,country_phone_code,country_cca2'
    });

    try {
      const response = await fetch(
        `https://api.countrydataapi.com/v1/countries/zipCode?${params}`
      );

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();

      if (data.length > 0) {
        setCountry(data[0]);
        setIsValid(true);
      } else {
        setCountry(null);
        setIsValid(false);
      }
    } catch (err) {
      setError(err.message);
      setCountry(null);
      setIsValid(false);
    } finally {
      setLoading(false);
    }
  }, []);

  const reset = useCallback(() => {
    setCountry(null);
    setIsValid(null);
    setError(null);
  }, []);

  return { country, loading, error, isValid, searchByPostalCode, reset };
}

function PostalCodeValidator() {
  const [postalCode, setPostalCode] = useState('');
  const { country, loading, error, isValid, searchByPostalCode, reset } = usePostalCodeSearch();

  const handleSubmit = (e) => {
    e.preventDefault();
    searchByPostalCode(postalCode);
  };

  const handleInputChange = (e) => {
    setPostalCode(e.target.value);
    if (!e.target.value) {
      reset();
    }
  };

  return (
    <div className="postal-code-validator">
      <h2>Postal Code Validator</h2>
      <p>Enter a postal code to find its country</p>

      <form onSubmit={handleSubmit} className="search-form">
        <input
          type="text"
          value={postalCode}
          onChange={handleInputChange}
          placeholder="Enter postal code (e.g., 10001, SW1A, 75001)"
          className="postal-input"
        />
        <button type="submit" disabled={loading || !postalCode.trim()}>
          {loading ? 'Validating...' : 'Validate'}
        </button>
      </form>

      {error && (
        <div className="error-message">
          Error: {error}
        </div>
      )}

      {isValid === true && country && (
        <div className="result valid">
          <div className="status-badge success">Valid Postal Code</div>
          <div className="country-card">
            <img
              src={country.country_flag_png}
              alt={`${country.country_name} flag`}
              className="country-flag"
            />
            <div className="country-details">
              <h3>{country.country_name}</h3>
              <p><strong>Code:</strong> {country.country_cca2}</p>
              <p><strong>Capital:</strong> {country.country_capital?.join(', ')}</p>
              <p><strong>Phone:</strong> +{country.country_phone_code}</p>
            </div>
          </div>
        </div>
      )}

      {isValid === false && !loading && (
        <div className="result invalid">
          <div className="status-badge error">Invalid Postal Code</div>
          <p>No country found for postal code "{postalCode}"</p>
        </div>
      )}
    </div>
  );
}

export default PostalCodeValidator;

Vue 3

<template>
  <div class="postal-code-validator">
    <h2>Postal Code Validator</h2>
    <p>Enter a postal code to find its country</p>

    <form @submit.prevent="handleSubmit" class="search-form">
      <input
        v-model="postalCode"
        type="text"
        placeholder="Enter postal code (e.g., 10001, SW1A, 75001)"
        class="postal-input"
        @input="handleInputChange"
      />
      <button type="submit" :disabled="loading || !postalCode.trim()">
        {{ loading ? 'Validating...' : 'Validate' }}
      </button>
    </form>

    <div v-if="error" class="error-message">
      {{ error }}
    </div>

    <div v-if="isValid === true && country" class="result valid">
      <div class="status-badge success">Valid Postal Code</div>
      <div class="country-card">
        <img
          :src="country.country_flag_png"
          :alt="`${country.country_name} flag`"
          class="country-flag"
        />
        <div class="country-details">
          <h3>{{ country.country_name }}</h3>
          <p><strong>Code:</strong> {{ country.country_cca2 }}</p>
          <p><strong>Capital:</strong> {{ formatCapital(country.country_capital) }}</p>
          <p><strong>Phone:</strong> +{{ country.country_phone_code }}</p>
        </div>
      </div>
    </div>

    <div v-if="isValid === false && !loading" class="result invalid">
      <div class="status-badge error">Invalid Postal Code</div>
      <p>No country found for postal code "{{ searchedCode }}"</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const API_KEY = 'YOUR_API_KEY';

const postalCode = ref('');
const searchedCode = ref('');
const country = ref(null);
const loading = ref(false);
const error = ref(null);
const isValid = ref(null);

const formatCapital = (capital) => {
  return capital?.join(', ') || 'N/A';
};

const handleInputChange = () => {
  if (!postalCode.value) {
    reset();
  }
};

const reset = () => {
  country.value = null;
  isValid.value = null;
  error.value = null;
};

const handleSubmit = async () => {
  if (!postalCode.value.trim()) return;

  loading.value = true;
  error.value = null;
  searchedCode.value = postalCode.value;

  const params = new URLSearchParams({
    apikey: API_KEY,
    postalCode: postalCode.value,
    fields: 'id,lang,country_name,country_flag_png,country_capital,country_phone_code,country_cca2'
  });

  try {
    const response = await fetch(
      `https://api.countrydataapi.com/v1/countries/zipCode?${params}`
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();

    if (data.length > 0) {
      country.value = data[0];
      isValid.value = true;
    } else {
      country.value = null;
      isValid.value = false;
    }
  } catch (err) {
    error.value = err.message;
    country.value = null;
    isValid.value = false;
  } finally {
    loading.value = false;
  }
};
</script>

<style scoped>
.postal-code-validator {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.search-form {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.postal-input {
  flex: 1;
  padding: 12px;
  font-size: 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

button {
  padding: 12px 24px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.result {
  padding: 20px;
  border-radius: 8px;
  margin-top: 20px;
}

.result.valid {
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
}

.result.invalid {
  background-color: #f8d7da;
  border: 1px solid #f5c6cb;
}

.status-badge {
  display: inline-block;
  padding: 5px 10px;
  border-radius: 4px;
  font-weight: bold;
  margin-bottom: 15px;
}

.status-badge.success {
  background-color: #28a745;
  color: white;
}

.status-badge.error {
  background-color: #dc3545;
  color: white;
}

.country-card {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}

.country-flag {
  width: 100px;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.country-details h3 {
  margin: 0 0 10px 0;
}

.country-details p {
  margin: 5px 0;
}

.error-message {
  color: #dc3545;
  padding: 10px;
  background-color: #f8d7da;
  border-radius: 4px;
}
</style>

Angular

// postal-code.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, catchError, throwError, map } from 'rxjs';

export interface Country {
  id: string;
  lang: string;
  country_name: string;
  country_flag_png?: string;
  country_capital?: string[];
  country_phone_code?: string;
  country_cca2?: string;
  country_cca3?: string;
}

export interface ValidationResult {
  isValid: boolean;
  country: Country | null;
}

@Injectable({
  providedIn: 'root'
})
export class PostalCodeService {
  private http = inject(HttpClient);
  private readonly API_KEY = 'YOUR_API_KEY';
  private readonly BASE_URL = 'https://api.countrydataapi.com/v1/countries';

  searchByPostalCode(postalCode: string, options: Record<string, string> = {}): Observable<Country[]> {
    let params = new HttpParams()
      .set('apikey', this.API_KEY)
      .set('postalCode', postalCode)
      .set('fields', 'id,lang,country_name,country_flag_png,country_capital,country_phone_code,country_cca2');

    Object.entries(options).forEach(([key, value]) => {
      params = params.set(key, value);
    });

    return this.http.get<Country[]>(`${this.BASE_URL}/zipCode`, { params }).pipe(
      catchError(error => {
        console.error('Postal code search error:', error);
        return throwError(() => new Error('Failed to search by postal code'));
      })
    );
  }

  validatePostalCode(postalCode: string): Observable<ValidationResult> {
    return this.searchByPostalCode(postalCode).pipe(
      map(countries => ({
        isValid: countries.length > 0,
        country: countries[0] || null
      }))
    );
  }

  matchPostalCodeToCountry(postalCode: string, countryCode: string): Observable<{matches: boolean; actualCountry?: string}> {
    return this.validatePostalCode(postalCode).pipe(
      map(result => {
        if (!result.isValid) {
          return { matches: false };
        }
        return {
          matches: result.country?.country_cca2 === countryCode,
          actualCountry: result.country?.country_name
        };
      })
    );
  }
}

// postal-code-validator.component.ts
import { Component, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { PostalCodeService, Country } from './postal-code.service';

@Component({
  selector: 'app-postal-code-validator',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div class="postal-code-validator">
      <h2>Postal Code Validator</h2>
      <p>Enter a postal code to find its country</p>

      <form (ngSubmit)="validatePostalCode()" class="search-form">
        <input
          type="text"
          [(ngModel)]="postalCode"
          name="postalCode"
          placeholder="Enter postal code (e.g., 10001, SW1A, 75001)"
          class="postal-input"
          (input)="onInputChange()"
        />
        <button type="submit" [disabled]="loading() || !postalCode.trim()">
          {{ loading() ? 'Validating...' : 'Validate' }}
        </button>
      </form>

      <div *ngIf="error()" class="error-message">
        {{ error() }}
      </div>

      <div *ngIf="isValid() === true && country()" class="result valid">
        <div class="status-badge success">Valid Postal Code</div>
        <div class="country-card">
          <img
            [src]="country()!.country_flag_png"
            [alt]="country()!.country_name + ' flag'"
            class="country-flag"
          />
          <div class="country-details">
            <h3>{{ country()!.country_name }}</h3>
            <p><strong>Code:</strong> {{ country()!.country_cca2 }}</p>
            <p><strong>Capital:</strong> {{ formatCapital(country()!.country_capital) }}</p>
            <p><strong>Phone:</strong> +{{ country()!.country_phone_code }}</p>
          </div>
        </div>
      </div>

      <div *ngIf="isValid() === false && !loading()" class="result invalid">
        <div class="status-badge error">Invalid Postal Code</div>
        <p>No country found for postal code "{{ searchedCode() }}"</p>
      </div>
    </div>
  `,
  styles: [`
    .postal-code-validator {
      max-width: 500px;
      margin: 0 auto;
      padding: 20px;
    }
    .search-form {
      display: flex;
      gap: 10px;
      margin-bottom: 20px;
    }
    .postal-input {
      flex: 1;
      padding: 12px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    button {
      padding: 12px 24px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:disabled {
      background-color: #ccc;
    }
    .result {
      padding: 20px;
      border-radius: 8px;
      margin-top: 20px;
    }
    .result.valid {
      background-color: #d4edda;
      border: 1px solid #c3e6cb;
    }
    .result.invalid {
      background-color: #f8d7da;
      border: 1px solid #f5c6cb;
    }
    .status-badge {
      display: inline-block;
      padding: 5px 10px;
      border-radius: 4px;
      font-weight: bold;
      margin-bottom: 15px;
    }
    .status-badge.success {
      background-color: #28a745;
      color: white;
    }
    .status-badge.error {
      background-color: #dc3545;
      color: white;
    }
    .country-card {
      display: flex;
      gap: 20px;
      align-items: flex-start;
    }
    .country-flag {
      width: 100px;
      height: auto;
      border-radius: 4px;
    }
    .error-message {
      color: #dc3545;
      padding: 10px;
      background-color: #f8d7da;
      border-radius: 4px;
    }
  `]
})
export class PostalCodeValidatorComponent {
  private postalCodeService = inject(PostalCodeService);

  postalCode = '';
  country = signal<Country | null>(null);
  loading = signal(false);
  error = signal<string | null>(null);
  isValid = signal<boolean | null>(null);
  searchedCode = signal('');

  formatCapital(capital?: string[]): string {
    return capital?.join(', ') || 'N/A';
  }

  onInputChange(): void {
    if (!this.postalCode) {
      this.reset();
    }
  }

  reset(): void {
    this.country.set(null);
    this.isValid.set(null);
    this.error.set(null);
  }

  validatePostalCode(): void {
    if (!this.postalCode.trim()) return;

    this.loading.set(true);
    this.error.set(null);
    this.searchedCode.set(this.postalCode);

    this.postalCodeService.validatePostalCode(this.postalCode).subscribe({
      next: (result) => {
        this.country.set(result.country);
        this.isValid.set(result.isValid);
        this.loading.set(false);
      },
      error: (err) => {
        this.error.set(err.message);
        this.country.set(null);
        this.isValid.set(false);
        this.loading.set(false);
      }
    });
  }
}

Python

import requests
from typing import Optional, List, Dict, Any
from dataclasses import dataclass

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.countrydataapi.com/v1/countries"

@dataclass
class PostalCodeResult:
    """Represents a postal code lookup result"""
    is_valid: bool
    country_name: str
    country_code: str
    flag_url: str
    capital: List[str]
    phone_code: str

def search_country_by_postal_code(
    postal_code: str,
    fields: Optional[str] = None,
    lang: str = "en"
) -> List[Dict[str, Any]]:
    """
    Search for countries by postal code.

    Args:
        postal_code: The postal code to search for
        fields: Comma-separated list of fields to return
        lang: Language for the response (default: "en")

    Returns:
        List of countries containing the postal code
    """
    params = {
        "apikey": API_KEY,
        "postalCode": postal_code,
        "lang": lang
    }

    if fields:
        params["fields"] = fields

    try:
        response = requests.get(f"{BASE_URL}/zipCode", params=params)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.HTTPError as e:
        print(f"HTTP Error: {e}")
        raise
    except requests.exceptions.RequestException as e:
        print(f"Request Error: {e}")
        raise

def validate_postal_code(postal_code: str) -> PostalCodeResult:
    """
    Validate a postal code and get country information.

    Args:
        postal_code: The postal code to validate

    Returns:
        PostalCodeResult with validation status and country info
    """
    fields = "id,lang,country_name,country_flag_png,country_capital,country_phone_code,country_cca2"

    try:
        countries = search_country_by_postal_code(postal_code, fields=fields)

        if not countries:
            return PostalCodeResult(
                is_valid=False,
                country_name="",
                country_code="",
                flag_url="",
                capital=[],
                phone_code=""
            )

        country = countries[0]
        return PostalCodeResult(
            is_valid=True,
            country_name=country.get("country_name", ""),
            country_code=country.get("country_cca2", ""),
            flag_url=country.get("country_flag_png", ""),
            capital=country.get("country_capital", []),
            phone_code=country.get("country_phone_code", "")
        )
    except Exception:
        return PostalCodeResult(
            is_valid=False,
            country_name="",
            country_code="",
            flag_url="",
            capital=[],
            phone_code=""
        )

def match_postal_code_to_country(postal_code: str, expected_country_code: str) -> Dict[str, Any]:
    """
    Check if a postal code belongs to an expected country.

    Args:
        postal_code: The postal code to check
        expected_country_code: The expected country code (ISO 3166-1 alpha-2)

    Returns:
        Dictionary with match result and actual country
    """
    result = validate_postal_code(postal_code)

    if not result.is_valid:
        return {
            "matches": False,
            "error": "Invalid postal code"
        }

    matches = result.country_code.upper() == expected_country_code.upper()

    return {
        "matches": matches,
        "expected_country": expected_country_code,
        "actual_country": result.country_name,
        "actual_country_code": result.country_code
    }

class AddressValidator:
    """Service for validating addresses using postal codes"""

    def __init__(self, api_key: str):
        global API_KEY
        API_KEY = api_key

    def validate_address(self, postal_code: str, country_code: str) -> Dict[str, Any]:
        """Validate if postal code matches country"""
        return match_postal_code_to_country(postal_code, country_code)

    def get_country_from_postal_code(self, postal_code: str) -> Optional[str]:
        """Get country name from postal code"""
        result = validate_postal_code(postal_code)
        return result.country_name if result.is_valid else None

# Example usage
if __name__ == "__main__":
    # Basic validation
    print("Validating postal codes...")

    # US ZIP code
    us_result = validate_postal_code("10001")
    if us_result.is_valid:
        print(f"10001 (NYC): {us_result.country_name} ({us_result.country_code})")

    # UK postal code
    uk_result = validate_postal_code("SW1A")
    if uk_result.is_valid:
        print(f"SW1A (London): {uk_result.country_name} ({uk_result.country_code})")

    # German postal code
    de_result = validate_postal_code("10115")
    if de_result.is_valid:
        print(f"10115 (Berlin): {de_result.country_name} ({de_result.country_code})")

    # Match validation
    print("\nMatching postal codes to countries...")
    match_result = match_postal_code_to_country("90210", "US")
    print(f"90210 matches US: {match_result['matches']}")

    wrong_match = match_postal_code_to_country("90210", "GB")
    print(f"90210 matches GB: {wrong_match['matches']} (actual: {wrong_match['actual_country']})")

PHP

<?php

class PostalCodeService
{
    private string $apiKey;
    private string $baseUrl = 'https://api.countrydataapi.com/v1/countries';

    public function __construct(string $apiKey)
    {
        $this->apiKey = $apiKey;
    }

    /**
     * Search for countries by postal code
     *
     * @param string $postalCode The postal code to search for
     * @param array $options Additional options (fields, lang)
     * @return array List of countries
     * @throws Exception
     */
    public function searchByPostalCode(string $postalCode, array $options = []): array
    {
        $params = array_merge([
            'apikey' => $this->apiKey,
            'postalCode' => $postalCode
        ], $options);

        $url = $this->baseUrl . '/zipCode?' . http_build_query($params);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json'
            ]
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new Exception("cURL Error: $error");
        }

        if ($httpCode !== 200) {
            throw new Exception("HTTP Error: $httpCode");
        }

        return json_decode($response, true) ?? [];
    }

    /**
     * Validate a postal code
     *
     * @param string $postalCode The postal code to validate
     * @return array Validation result with country info
     */
    public function validatePostalCode(string $postalCode): array
    {
        try {
            $countries = $this->searchByPostalCode($postalCode, [
                'fields' => 'id,lang,country_name,country_flag_png,country_capital,country_phone_code,country_cca2'
            ]);

            if (empty($countries)) {
                return [
                    'isValid' => false,
                    'country' => null
                ];
            }

            return [
                'isValid' => true,
                'country' => $countries[0]
            ];
        } catch (Exception $e) {
            return [
                'isValid' => false,
                'country' => null,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Check if postal code matches expected country
     *
     * @param string $postalCode The postal code to check
     * @param string $expectedCountryCode Expected country code (ISO 3166-1 alpha-2)
     * @return array Match result
     */
    public function matchPostalCodeToCountry(string $postalCode, string $expectedCountryCode): array
    {
        $result = $this->validatePostalCode($postalCode);

        if (!$result['isValid']) {
            return [
                'matches' => false,
                'error' => 'Invalid postal code'
            ];
        }

        $actualCode = strtoupper($result['country']['country_cca2'] ?? '');
        $expectedCode = strtoupper($expectedCountryCode);

        return [
            'matches' => $actualCode === $expectedCode,
            'expectedCountry' => $expectedCode,
            'actualCountry' => $result['country']['country_name'],
            'actualCountryCode' => $actualCode
        ];
    }
}

/**
 * Address validation helper class
 */
class AddressValidator
{
    private PostalCodeService $postalCodeService;

    public function __construct(string $apiKey)
    {
        $this->postalCodeService = new PostalCodeService($apiKey);
    }

    /**
     * Validate address components
     *
     * @param array $address Address with 'postalCode' and 'countryCode' keys
     * @return array Validation result
     */
    public function validateAddress(array $address): array
    {
        if (empty($address['postalCode'])) {
            return ['valid' => false, 'error' => 'Postal code is required'];
        }

        if (empty($address['countryCode'])) {
            // Just validate the postal code exists
            $result = $this->postalCodeService->validatePostalCode($address['postalCode']);
            return [
                'valid' => $result['isValid'],
                'detectedCountry' => $result['country']['country_name'] ?? null
            ];
        }

        // Validate postal code matches country
        $match = $this->postalCodeService->matchPostalCodeToCountry(
            $address['postalCode'],
            $address['countryCode']
        );

        return [
            'valid' => $match['matches'],
            'detectedCountry' => $match['actualCountry'] ?? null,
            'mismatch' => !$match['matches'] && isset($match['actualCountry'])
        ];
    }
}

// Example usage
$apiKey = 'YOUR_API_KEY';
$postalCodeService = new PostalCodeService($apiKey);

try {
    // Basic validation
    echo "Validating postal codes...\n";

    // US ZIP code
    $usResult = $postalCodeService->validatePostalCode('10001');
    if ($usResult['isValid']) {
        echo "10001 (NYC): " . $usResult['country']['country_name'] . "\n";
    }

    // UK postal code
    $ukResult = $postalCodeService->validatePostalCode('SW1A');
    if ($ukResult['isValid']) {
        echo "SW1A (London): " . $ukResult['country']['country_name'] . "\n";
    }

    // French postal code
    $frResult = $postalCodeService->validatePostalCode('75001');
    if ($frResult['isValid']) {
        echo "75001 (Paris): " . $frResult['country']['country_name'] . "\n";
    }

    // Match validation
    echo "\nMatching postal codes to countries...\n";

    $match1 = $postalCodeService->matchPostalCodeToCountry('90210', 'US');
    echo "90210 matches US: " . ($match1['matches'] ? 'Yes' : 'No') . "\n";

    $match2 = $postalCodeService->matchPostalCodeToCountry('90210', 'GB');
    echo "90210 matches GB: " . ($match2['matches'] ? 'Yes' : 'No');
    echo " (actual: " . $match2['actualCountry'] . ")\n";

    // Address validation
    echo "\nAddress validation...\n";
    $validator = new AddressValidator($apiKey);

    $validAddress = $validator->validateAddress([
        'postalCode' => '10001',
        'countryCode' => 'US'
    ]);
    echo "NYC address valid: " . ($validAddress['valid'] ? 'Yes' : 'No') . "\n";

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

Error Handling

Status Code Description
200 Success - Returns array of countries
400 Bad Request - Invalid or missing postal code parameter
401 Unauthorized - Invalid or missing API key
404 Not Found - No countries found with the given postal code
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error - Server-side error