Geocode an address in JavaScript and Node.js

This tutorial uses only the built-in fetch of Node 18 or later (or a browser). It geocodes one address, handles the rate limit politely, then geocodes a list with a small queue. A typed client exists too: see the TypeScript SDK.

The base URL https://api.addrova.eu is not live yet. Everything below follows the API specification. [À VÉRIFIER : service ouvert, URL de base définitive.]

1. One address

const resp = await fetch("https://api.addrova.eu/v1/geocode", {
  method: "POST",
  headers: { "X-API-Key": process.env.ADDROVA_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ q: "4 place d'Armes, Belfort", country: "FR", limit: 1 }),
});
if (!resp.ok) throw new Error((await resp.json()).code);
const { results, attribution } = await resp.json();
const best = results[0];
console.log(best.label, best.location, best.precision, best.confidence_level);
console.log(attribution.map((a) => a.text));

The address travels in the body of a POST, not in the URL, so it never appears in a URL or query string (the API logs only the route template, ZL-01). Keep the attribution with the result if you store it.

2. Wrap it, and retry on a rate limit

When you exceed your rate, the API answers 429 with a Retry-After header. Honour it instead of hammering.

async function geocode(q, country = "FR", attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const resp = await fetch("https://api.addrova.eu/v1/geocode", {
      method: "POST",
      headers: { "X-API-Key": process.env.ADDROVA_API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({ q, country, limit: 1 }),
    });
    if (resp.status === 429 && i < attempts - 1) {
      const wait = Number(resp.headers.get("retry-after") ?? 1);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    if (!resp.ok) throw new Error(`${resp.status} ${(await resp.json()).code}`);
    return (await resp.json()).results[0] ?? null;
  }
}

const hit = await geocode("4 place d'Armes, Belfort");
console.log(hit?.label ?? "not found");

3. A list, with a small queue

A short list does not need a batch job. Run a few requests at a time rather than all at once.

async function geocode(q) {
  const resp = await fetch("https://api.addrova.eu/v1/geocode", {
    method: "POST",
    headers: { "X-API-Key": process.env.ADDROVA_API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ q, country: "FR", limit: 1 }),
  });
  if (!resp.ok) throw new Error(`${resp.status}`);
  return (await resp.json()).results[0] ?? null;
}

async function geocodeAll(addresses, concurrency = 4) {
  const out = new Array(addresses.length);
  let next = 0;
  const worker = async () => {
    while (next < addresses.length) {
      const i = next++;
      out[i] = await geocode(addresses[i]);
    }
  };
  await Promise.all(Array.from({ length: Math.min(concurrency, addresses.length) }, worker));
  return out;
}

const rows = await geocodeAll(["4 place d'Armes, Belfort", "10 rue de Rivoli, Paris"]);
console.log(rows.map((r) => r?.label ?? null));

For thousands of rows, use the batch endpoint or the CSV geocoder instead of a loop.

Good habits

  • Do not put a live key in a public page. In a browser, use a key restricted by origin or relay the call from your server.
  • Check confidence_level and precision before using a point: see precision levels and confidence thresholds.
  • Never log the address you send. The service writes no address to its logs (ZL-10); your own code should not either.