Documentazione API - Endpoint ed Esempi

Completamento Automatico degli Indirizzi

Da un campo vuoto a un indirizzo validato e salvato

Questa guida costruisce il passo indirizzo di un checkout: l'utente digita, sceglie una città, e i campi paese, provincia e codice postale si compilano da soli e vengono validati. Circa 60 righe di JavaScript.

Tre endpoint fanno il lavoro:

  1. /v1/places/autocomplete — suggerimenti durante la digitazione
  2. /v1/places/details — la scheda completa di quello che è stato scelto
  3. /v1/places/validate — verifica finale prima di salvare

Prima di tutto, il token di sessione

Un completamento automatico invia una richiesta a ogni tasto. Per tenere il costo prevedibile, genera un sessiontoken quando il campo riceve il focus e riusalo per tutte le richieste di quel campo, inclusa la chiamata finale a details. L'intero campo costa 1 token, per quanto l'utente digiti.

let session = crypto.randomUUID();

input.addEventListener('focus', () => {
  session = crypto.randomUUID(); // una sessione per ogni indirizzo che l'utente inserisce
});

Le sessioni durano 3 minuti. Se ometti il token, ogni richiesta costa 1 token.

JavaScript puro

<input id="city" placeholder="Inizia a digitare la tua città..." autocomplete="off" />
<ul id="suggestions"></ul>

<input id="country" readonly />
<input id="state" readonly />
<input id="zip" placeholder="Codice postale" />
<p id="zip-error"></p>
const API_KEY = 'la-tua-chiave-api';
const BASE = 'https://api.countrydataapi.com/v1/places';

const input = document.getElementById('city');
const list = document.getElementById('suggestions');

let session = crypto.randomUUID();
let timer;
let postal = null;

input.addEventListener('focus', () => { session = crypto.randomUUID(); });

// Debounce: una richiesta per pausa, non per tasto.
input.addEventListener('input', () => {
  clearTimeout(timer);
  const q = input.value.trim();
  if (q.length < 2) { list.innerHTML = ''; return; }

  timer = setTimeout(() => search(q), 150);
});

async function search(q) {
  const params = new URLSearchParams({
    apikey: API_KEY,
    q,
    types: 'city',
    limit: '5',
    lang: 'it',
    sessiontoken: session,
  });

  const response = await fetch(`${BASE}/autocomplete?${params}`);
  const { suggestions } = await response.json();

  list.innerHTML = '';
  suggestions.forEach((suggestion) => {
    const item = document.createElement('li');
    item.textContent = suggestion.description; // "Madrid, Comunità di Madrid, Spagna"
    item.onclick = () => choose(suggestion);
    list.appendChild(item);
  });
}

async function choose(suggestion) {
  list.innerHTML = '';
  input.value = suggestion.name;

  const params = new URLSearchParams({
    apikey: API_KEY,
    id: suggestion.id,
    type: suggestion.type,
    lang: 'it',
    sessiontoken: session, // chiude la sessione: questa chiamata è gratuita
  });

  const response = await fetch(`${BASE}/details?${params}`);
  const { place } = await response.json();

  document.getElementById('country').value = place.components.country?.name ?? '';
  document.getElementById('state').value = place.components.state?.name ?? '';

  // Le regole del codice postale del paese, per validare il campo successivo nel browser.
  postal = place.postal;
  document.getElementById('zip').placeholder = postal.example ?? 'Codice postale';
}

document.getElementById('zip').addEventListener('blur', (event) => {
  const error = document.getElementById('zip-error');
  if (!postal?.regex) { error.textContent = ''; return; }

  const ok = new RegExp(postal.regex).test(event.target.value.trim());
  error.textContent = ok ? '' : `Formato atteso: ${postal.example ?? postal.format}`;
});

Questa è tutta l'interazione. Il campo description arriva già formattato per un menu a tendina, quindi non c'è nessuna stringa da comporre.

React

import { useEffect, useRef, useState } from 'react';

const API_KEY = 'la-tua-chiave-api';
const BASE = 'https://api.countrydataapi.com/v1/places';

export function useAddressAutocomplete() {
  const [query, setQuery] = useState('');
  const [suggestions, setSuggestions] = useState([]);
  const [address, setAddress] = useState(null);
  const session = useRef(crypto.randomUUID());

  useEffect(() => {
    if (query.trim().length < 2) {
      setSuggestions([]);
      return;
    }

    // Annulla le richieste in corso perché una risposta lenta non sovrascriva una più recente.
    const controller = new AbortController();
    const timer = setTimeout(async () => {
      const params = new URLSearchParams({
        apikey: API_KEY,
        q: query.trim(),
        types: 'city',
        limit: '5',
        lang: 'it',
        sessiontoken: session.current,
      });

      try {
        const response = await fetch(`${BASE}/autocomplete?${params}`, {
          signal: controller.signal,
        });
        const data = await response.json();
        setSuggestions(data.suggestions ?? []);
      } catch (error) {
        if (error.name !== 'AbortError') throw error;
      }
    }, 150);

    return () => {
      clearTimeout(timer);
      controller.abort();
    };
  }, [query]);

  async function select(suggestion) {
    const params = new URLSearchParams({
      apikey: API_KEY,
      id: suggestion.id,
      type: suggestion.type,
      lang: 'it',
      sessiontoken: session.current,
    });

    const response = await fetch(`${BASE}/details?${params}`);
    const { place } = await response.json();

    setAddress(place);
    setSuggestions([]);
    setQuery(place.name);
    session.current = crypto.randomUUID(); // indirizzo successivo, sessione successiva
  }

  return { query, setQuery, suggestions, address, select };
}
function AddressField() {
  const { query, setQuery, suggestions, address, select } = useAddressAutocomplete();

  return (
    <div>
      <input
        value={query}
        onChange={(event) => setQuery(event.target.value)}
        placeholder="Inizia a digitare la tua città..."
        autoComplete="off"
      />

      {suggestions.length > 0 && (
        <ul>
          {suggestions.map((suggestion) => (
            <li key={suggestion.id} onClick={() => select(suggestion)}>
              {suggestion.description}
            </li>
          ))}
        </ul>
      )}

      {address && (
        <>
          <input readOnly value={address.components.country?.name ?? ''} />
          <input readOnly value={address.components.state?.name ?? ''} />
          <input placeholder={address.postal.example ?? 'Codice postale'} />
        </>
      )}
    </div>
  );
}

Validazione finale sul server

La validazione nel browser è a beneficio dell'utente. Prima di salvare o spedire qualsiasi cosa, conferma la combinazione completa sul server:

const params = new URLSearchParams({
  apikey: process.env.COUNTRY_DATA_API_KEY,
  country: form.country,
  state: form.state,
  city: form.city,
  zipcode: form.zip,
  lang: 'it',
});

const response = await fetch(
  `https://api.countrydataapi.com/v1/places/validate?${params}`
);
const { result } = await response.json();

if (!result.valid && result.has_corrections) {
  // Proponi la correzione invece di rifiutare il modulo di netto.
  return { needsConfirmation: result.corrections };
}

// Salva la forma canonica, non quello che ha digitato l'utente.
await orders.save({
  country: result.normalized.country?.name,
  state: result.normalized.state?.name,
  city: result.normalized.city?.name,
  zipcode: result.normalized.zipcode,
});

Salvare result.normalized invece dell'input grezzo è ciò che rende i tuoi dati di indirizzo interrogabili in seguito: niente Cataluna accanto a Cataluña, niente MADRID accanto a Madrid.

Chiedere meno

Se il tuo modulo ha solo codice postale e città, il campo provincia non ti serve affatto: lo risolve il codice postale.

const { result } = await api.places.validate({ country: 'ES', zipcode: '28001' });
result.normalized.state?.name; // "Comunità di Madrid"

Un campo in meno migliora in modo misurabile la conversione di un checkout.

Quanto costa

Azione Token
Un campo indirizzo completo (quanti tasti servono + details) 1
Validazione sul server del modulo inviato 1
Formati postali di tutti i paesi (mettili in cache) 1, una volta

Quindi un indirizzo di checkout completato costa 2 token, e non importa quanto velocemente digiti l'utente.

Cosa non fa

Il set di dati è amministrativo: paesi, regioni, città e codici postali. Non ci sono dati a livello di via, né coordinate sotto il livello del paese, né punti di interesse — niente attività commerciali, orari, recensioni o foto. Se devi geocodificare un indirizzo completo con la via o cercare un ristorante, non è lo strumento giusto.

Vedi anche