Documentación de la API - Endpoints y Ejemplos

Obtener Ciudades para Select

Obtén ciudades por estado/provincia, optimizadas para dropdowns de select

El endpoint /v1/select/cities devuelve una lista ligera de ciudades filtradas por estado/provincia, con solo los campos id y name, optimizada para menús desplegables.

Endpoint

GET https://api.countrydataapi.com/v1/select/cities

Autenticación

Incluye tu clave API como parámetro de consulta:

?apikey=tu-clave-api

Tu clave API es como una contraseña, mantenla segura. Obtén tu clave desde el panel de cuenta.

Parámetros de Consulta

Parámetro Tipo Requerido Descripción
apikey string Tu clave de autenticación API
state string ID del estado (del endpoint /select/states)
lang string No Código de idioma para nombres de ciudades (por defecto: en)

Idiomas Soportados

  • en - Inglés (por defecto)
  • es - Español
  • pt - Portugués
  • fr - Francés
  • de - Alemán
  • it - Italiano

Ejemplo de Solicitud

# Obtener ciudades de California
curl "https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=66c7a6cae4bda21f4ab11d1a&lang=es"

JavaScript (Fetch)

const API_KEY = 'tu-clave-api';
const STATE_ID = '66c7a6cae4bda21f4ab11d1a'; // California

async function loadCities(stateId) {
  const response = await fetch(
    `https://api.countrydataapi.com/v1/select/cities?apikey=${API_KEY}&state=${stateId}&lang=es`
  );
  const data = await response.json();
  return data;
}

const cities = await loadCities(STATE_ID);

JavaScript (Axios)

import axios from 'axios';

const response = await axios.get(
  'https://api.countrydataapi.com/v1/select/cities',
  {
    params: {
      apikey: 'tu-clave-api',
      state: '66c7a6cae4bda21f4ab11d1a',
      lang: 'es',
    },
  }
);

Python

import requests

response = requests.get(
    'https://api.countrydataapi.com/v1/select/cities',
    params={
        'apikey': 'tu-clave-api',
        'state': '66c7a6cae4bda21f4ab11d1a',
        'lang': 'es'
    }
)
data = response.json()

PHP

<?php
$apiKey = 'tu-clave-api';
$stateId = '66c7a6cae4bda21f4ab11d1a';
$url = "https://api.countrydataapi.com/v1/select/cities?apikey={$apiKey}&state={$stateId}&lang=es";

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
?>

Angular

// city.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

interface City {
  id: string;
  name: string;
}

interface ApiResponse {
  success: boolean;
  data: City[];
}

@Injectable({
  providedIn: 'root'
})
export class CityService {
  private readonly API_KEY = 'tu-clave-api';
  private readonly BASE_URL = 'https://api.countrydataapi.com/v1';

  constructor(private http: HttpClient) {}

  getCitiesByState(stateId: string, lang: string = 'es'): Observable<City[]> {
    const params = new HttpParams()
      .set('apikey', this.API_KEY)
      .set('state', stateId)
      .set('lang', lang);

    return this.http.get<ApiResponse>(
      `${this.BASE_URL}/select/cities`,
      { params }
    ).pipe(
      map(response => response.data)
    );
  }
}

// city-select.component.ts
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { CityService } from './city.service';

@Component({
  selector: 'app-city-select',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div *ngIf="loading">Cargando ciudades...</div>
    <div *ngIf="error" class="error">{{ error }}</div>
    <select
      *ngIf="!loading && !error"
      [(ngModel)]="selectedCity"
      (ngModelChange)="onCityChange($event)"
      [disabled]="!stateId"
    >
      <option value="">Selecciona una ciudad...</option>
      <option *ngFor="let city of cities" [value]="city.id">
        {{ city.name }}
      </option>
    </select>
  `
})
export class CitySelectComponent implements OnChanges {
  @Input() stateId: string = '';
  @Output() cityChange = new EventEmitter<string>();

  cities: any[] = [];
  selectedCity = '';
  loading = false;
  error: string | null = null;

  constructor(private cityService: CityService) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes['stateId'] && this.stateId) {
      this.loadCities();
    } else {
      this.cities = [];
      this.selectedCity = '';
    }
  }

  loadCities() {
    this.loading = true;
    this.error = null;

    this.cityService.getCitiesByState(this.stateId, 'es').subscribe({
      next: (data) => {
        this.cities = data;
        this.loading = false;
      },
      error: (err) => {
        this.error = 'Error al cargar ciudades';
        this.loading = false;
      }
    });
  }

  onCityChange(cityId: string) {
    this.cityChange.emit(cityId);
  }
}

// Ejemplo completo de formulario en cascada
// address-form.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CountrySelectComponent } from './country-select.component';
import { StateSelectComponent } from './state-select.component';
import { CitySelectComponent } from './city-select.component';

@Component({
  selector: 'app-address-form',
  standalone: true,
  imports: [CommonModule, CountrySelectComponent, StateSelectComponent, CitySelectComponent],
  template: `
    <form>
      <div class="form-group">
        <label>País</label>
        <app-country-select (countryChange)="onCountryChange($event)"></app-country-select>
      </div>

      <div class="form-group">
        <label>Estado</label>
        <app-state-select
          [countryId]="selectedCountry"
          (stateChange)="onStateChange($event)"
        ></app-state-select>
      </div>

      <div class="form-group">
        <label>Ciudad</label>
        <app-city-select
          [stateId]="selectedState"
          (cityChange)="onCityChange($event)"
        ></app-city-select>
      </div>
    </form>
  `
})
export class AddressFormComponent {
  selectedCountry = '';
  selectedState = '';
  selectedCity = '';

  onCountryChange(countryId: string) {
    this.selectedCountry = countryId;
    this.selectedState = '';
    this.selectedCity = '';
  }

  onStateChange(stateId: string) {
    this.selectedState = stateId;
    this.selectedCity = '';
  }

  onCityChange(cityId: string) {
    this.selectedCity = cityId;
    console.log('Seleccionado:', {
      country: this.selectedCountry,
      state: this.selectedState,
      city: this.selectedCity
    });
  }
}

Formato de Respuesta

Respuesta Exitosa (California)

{
  "success": true,
  "data": [
    {
      "id": "66c7a6d1e4bda21f4ab5c825",
      "name": "Adelanto"
    },
    {
      "id": "66c7a6d1e4bda21f4ab5c826",
      "name": "Agoura Hills"
    },
    {
      "id": "66c7a6d1e4bda21f4ab5c827",
      "name": "Alameda"
    },
    {
      "id": "66c7a6d1e4bda21f4ab5c828",
      "name": "Albany"
    },
    {
      "id": "66c7a6d1e4bda21f4ab5c829",
      "name": "Alhambra"
    }
    // ... cientos de ciudades
  ]
}

Campos de Respuesta

Campo Tipo Descripción
success boolean Indica si la solicitud fue exitosa
data array Array de objetos de ciudad
data[].id string Identificador único de la ciudad (MongoDB ObjectId)
data[].name string Nombre de la ciudad en el idioma solicitado

Respuesta de Error

{
  "success": false,
  "error": {
    "code": "MISSING_PARAMETER",
    "message": "Falta el parámetro requerido 'state'"
  }
}

Resultado Vacío

Si un estado no tiene ciudades en la base de datos:

{
  "success": true,
  "data": []
}

Uso de Tokens

Este endpoint consume 1 token por solicitud.

Importante: Algunos estados tienen miles de ciudades. Considera implementar paginación o funcionalidad de búsqueda para mejor UX.

Dropdown en Cascada Completo

HTML + JavaScript

<select id="country" onchange="loadStates(this.value)">
  <option value="">Selecciona país...</option>
</select>

<select id="state" onchange="loadCities(this.value)" disabled>
  <option value="">Selecciona estado...</option>
</select>

<select id="city" disabled>
  <option value="">Selecciona ciudad...</option>
</select>

<script>
const API_KEY = 'tu-clave-api';
const BASE_URL = 'https://api.countrydataapi.com/v1';

// Cargar países al cargar la página
async function loadCountries() {
  const response = await fetch(`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=es`);
  const { data } = await response.json();

  const select = document.getElementById('country');
  data.forEach(country => {
    select.add(new Option(country.name, country.id));
  });
}

async function loadStates(countryId) {
  const stateSelect = document.getElementById('state');
  const citySelect = document.getElementById('city');

  // Resetear
  stateSelect.innerHTML = '<option value="">Selecciona estado...</option>';
  citySelect.innerHTML = '<option value="">Selecciona ciudad...</option>';
  stateSelect.disabled = true;
  citySelect.disabled = true;

  if (!countryId) return;

  stateSelect.innerHTML = '<option value="">Cargando...</option>';

  const response = await fetch(
    `${BASE_URL}/select/states?apikey=${API_KEY}&country=${countryId}&lang=es`
  );
  const { data } = await response.json();

  stateSelect.innerHTML = '<option value="">Selecciona estado...</option>';
  data.forEach(state => {
    stateSelect.add(new Option(state.name, state.id));
  });
  stateSelect.disabled = false;
}

async function loadCities(stateId) {
  const citySelect = document.getElementById('city');

  // Resetear
  citySelect.innerHTML = '<option value="">Selecciona ciudad...</option>';
  citySelect.disabled = true;

  if (!stateId) return;

  citySelect.innerHTML = '<option value="">Cargando...</option>';

  const response = await fetch(
    `${BASE_URL}/select/cities?apikey=${API_KEY}&state=${stateId}&lang=es`
  );
  const { data } = await response.json();

  citySelect.innerHTML = '<option value="">Selecciona ciudad...</option>';
  data.forEach(city => {
    citySelect.add(new Option(city.name, city.id));
  });
  citySelect.disabled = false;
}

// Cargar países al cargar la página
loadCountries();
</script>

Componente React

import { useState, useEffect } from 'react';

function CitySelect({ stateId, onChange }) {
  const [cities, setCities] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!stateId) {
      setCities([]);
      return;
    }

    setLoading(true);
    fetch(
      `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${stateId}&lang=es`
    )
      .then(res => res.json())
      .then(({ data }) => {
        setCities(data || []);
        setLoading(false);
      })
      .catch(err => {
        console.error(err);
        setLoading(false);
      });
  }, [stateId]);

  return (
    <select
      onChange={e => onChange(e.target.value)}
      disabled={!stateId || loading}
    >
      <option value="">
        {loading ? 'Cargando ciudades...' : 'Selecciona ciudad...'}
      </option>
      {cities.map(city => (
        <option key={city.id} value={city.id}>
          {city.name}
        </option>
      ))}
    </select>
  );
}

export default CitySelect;

Componente Vue

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

const props = defineProps(['stateId']);
const emit = defineEmits(['change']);

const cities = ref([]);
const loading = ref(false);

watch(() => props.stateId, async (stateId) => {
  if (!stateId) {
    cities.value = [];
    return;
  }

  loading.value = true;
  try {
    const response = await fetch(
      `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${stateId}&lang=es`
    );
    const { data } = await response.json();
    cities.value = data || [];
  } catch (err) {
    console.error(err);
  } finally {
    loading.value = false;
  }
});
</script>

<template>
  <select
    @change="emit('change', $event.target.value)"
    :disabled="!stateId || loading"
  >
    <option value="">
      {{ loading ? 'Cargando ciudades...' : 'Selecciona ciudad...' }}
    </option>
    <option v-for="city in cities" :key="city.id" :value="city.id">
      {{ city.name }}
    </option>
  </select>
</template>

Ejemplos Específicos por Estado

California (1000+ ciudades)

const CA_ID = '66c7a6cae4bda21f4ab11d1a';
const response = await fetch(
  `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${CA_ID}`
);
// Devuelve: Los Ángeles, San Francisco, San Diego, Sacramento, etc.

Texas (900+ ciudades)

const TX_ID = '66c7a6cbe4bda21f4ab11f55';
const response = await fetch(
  `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${TX_ID}`
);
// Devuelve: Houston, Dallas, Austin, San Antonio, etc.

Nueva York (500+ ciudades)

const NY_ID = '66c7a6cae4bda21f4ab11e8f';
const response = await fetch(
  `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${NY_ID}`
);
// Devuelve: Nueva York, Buffalo, Rochester, Albany, etc.

Implementación de Búsqueda/Filtro

Para estados con muchas ciudades, implementa búsqueda del lado del cliente:

function filterCities(cities, searchTerm) {
  if (!searchTerm) return cities;

  return cities.filter(city =>
    city.name.toLowerCase().includes(searchTerm.toLowerCase())
  );
}

// Uso
const allCities = await loadCities(stateId);
const filteredCities = filterCities(allCities, 'Los');
// Devuelve: Los Ángeles, Los Gatos, Los Altos, etc.

Ejemplo de Dropdown con Búsqueda

<input
  type="text"
  id="city-search"
  placeholder="Buscar ciudades..."
  oninput="filterCityDropdown(this.value)"
>
<select id="city">
  <option value="">Selecciona ciudad...</option>
</select>

<script>
let allCities = [];

async function loadCities(stateId) {
  const response = await fetch(
    `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${stateId}`
  );
  const { data } = await response.json();
  allCities = data;
  renderCities(allCities);
}

function filterCityDropdown(searchTerm) {
  const filtered = allCities.filter(city =>
    city.name.toLowerCase().includes(searchTerm.toLowerCase())
  );
  renderCities(filtered);
}

function renderCities(cities) {
  const select = document.getElementById('city');
  select.innerHTML = '<option value="">Selecciona ciudad...</option>';
  cities.forEach(city => {
    select.add(new Option(city.name, city.id));
  });
}
</script>

Implementación de Autocompletado

Para mejor UX, implementa autocompletado:

<input
  type="text"
  id="city-autocomplete"
  placeholder="Escribe para buscar ciudades..."
  list="cities-datalist"
>
<datalist id="cities-datalist">
  <!-- Poblado dinámicamente -->
</datalist>

<script>
async function loadCitiesAutocomplete(stateId) {
  const response = await fetch(
    `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${stateId}`
  );
  const { data } = await response.json();

  const datalist = document.getElementById('cities-datalist');
  datalist.innerHTML = '';
  data.forEach(city => {
    const option = document.createElement('option');
    option.value = city.name;
    option.dataset.id = city.id;
    datalist.appendChild(option);
  });
}
</script>

Estrategia de Caché

Cachea ciudades por estado para minimizar llamadas API:

const cityCache = new Map();

async function getCities(stateId) {
  // Verificar caché primero
  if (cityCache.has(stateId)) {
    return cityCache.get(stateId);
  }

  // Obtener de la API
  const response = await fetch(
    `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${stateId}&lang=es`
  );
  const { data } = await response.json();

  // Guardar en caché (limitar tamaño de caché para prevenir problemas de memoria)
  if (cityCache.size > 50) {
    const firstKey = cityCache.keys().next().value;
    cityCache.delete(firstKey);
  }
  cityCache.set(stateId, data);

  return data;
}

Optimización de Rendimiento

Carga Perezosa

Solo carga ciudades cuando el dropdown de estado tenga foco:

stateSelect.addEventListener('focus', () => {
  if (!citiesLoaded) {
    loadCities(stateSelect.value);
    citiesLoaded = true;
  }
});

Scroll Virtual

Para listas muy grandes, usa librerías de scroll virtual:

import { FixedSizeList } from 'react-window';

function VirtualCitySelect({ cities }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {cities[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={300}
      itemCount={cities.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

Manejo de Errores

Siempre maneja errores con gracia:

async function loadCities(stateId) {
  try {
    const response = await fetch(
      `https://api.countrydataapi.com/v1/select/cities?apikey=tu-clave-api&state=${stateId}`
    );

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

    const result = await response.json();

    if (!result.success) {
      throw new Error(result.error?.message || 'Error de API');
    }

    return result.data;
  } catch (err) {
    console.error('Error al cargar ciudades:', err);
    alert('Error al cargar ciudades. Por favor intenta de nuevo.');
    return [];
  }
}

Límites de Tasa

  • Tier gratuito: 100 solicitudes/día
  • Tier básico: 1,000 solicitudes/día
  • Tier pro: 10,000 solicitudes/día
  • Tier enterprise: Ilimitado

Consulta nuestra página de precios para más detalles.

Errores Comunes

Código de Error Descripción Solución
INVALID_API_KEY Clave API inválida Verifica tu clave API en el panel de cuenta
MISSING_PARAMETER Falta parámetro state Incluye ID de estado en la solicitud
INVALID_STATE_ID Formato de ID de estado inválido Usa un ObjectId válido de MongoDB de /select/states
QUOTA_EXCEEDED Límite diario de tokens alcanzado Actualiza tu plan o espera el reinicio

Consulta la documentación de Códigos de Error para todos los errores posibles.

Endpoints Relacionados

Guías de Integración Completas

¿Necesitas Ayuda?


Consejo Pro: Para estados con 500+ ciudades, considera implementar un dropdown de autocompletado con búsqueda en lugar de un elemento select tradicional. Esto mejora significativamente la experiencia de usuario y el rendimiento.