Node.js guide · Forward geocoding

Geocode addresses in Node.js.

Turn a street address or place name into latitude and longitude using Node's built-in fetch. This guide makes one server-side API request, checks the result and handles failures without an SDK or an npm install.

50,000 monthly credits on the Free plan, shared across your keys. Each successful search costs 1 credit.

1. Set up Node.js and a secret API key

Use Node.js 22 or newer. Its built-in fetch API sends the request; no geocoding package is required. In the dashboard, create a secret key, not a browser key.

Create a local .env file and replace the placeholder with the complete key:

.env
OPENGEOCODING_API_KEY=YOUR_SECRET_API_KEY

Keep .env out of version control with .gitignore, and restrict access to the file. On a deployed server, set the same environment variable through your hosting platform's secret settings. Never put this key in browser JavaScript or a public environment variable. See API key authentication.

2. Save and run the forward-geocoding script

Save this as geocode.mjs. It sends addressString as JSON to POST /v1/search, with the key in an x-api-key header rather than in the URL. The request has a 15-second timeout and refuses redirects so credentials cannot be forwarded to a different host.

geocode.mjs
const apiKey = process.env.OPENGEOCODING_API_KEY?.trim() ?? '';
const addressString = process.argv.slice(2).join(' ').trim();

if (!/^og_[A-Za-z0-9_-]{32}$/.test(apiKey)) {
	console.error('Set OPENGEOCODING_API_KEY to a complete secret key.');
	process.exitCode = 1;
} else if (!addressString || [...addressString].length > 500) {
	console.error('Pass an address of 1–500 characters: node geocode.mjs "address"');
	process.exitCode = 1;
} else {
	try {
		const response = await fetch('https://api.opengeocoding.com/v1/search', {
			method: 'POST',
			redirect: 'error',
			headers: {
				'x-api-key': apiKey,
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({ addressString }),
			signal: AbortSignal.timeout(15_000)
		});

		if (!response.ok) {
			console.error(`HTTP ${response.status}. See https://opengeocoding.com/docs/errors`);
			const retryAfter = response.headers.get('retry-after');
			if (response.status === 429 && retryAfter) {
				console.error(`Wait ${retryAfter} seconds before retrying.`);
			}
			process.exitCode = 1;
		} else {
			const { displayName, geoCoordinates, goodMatch = false } = await response.json();
			if (!geoCoordinates) {
				console.error('No coordinates returned. Review the address before trying again.');
				process.exitCode = 1;
			} else {
				console.log(
					JSON.stringify(
						{
							displayName,
							lat: geoCoordinates.lat ?? 0,
							lon: geoCoordinates.lon ?? 0,
							goodMatch
						},
						null,
						2
					)
				);
			}
		}
	} catch {
		console.error('Network, timeout or invalid response. No automatic retry was made.');
		process.exitCode = 1;
	}
}

Run it from the directory containing both files, with the address in quotes:

Terminal
node --env-file=.env geocode.mjs "10 Rue de Rivoli, 75004 Paris"

Node's --env-file option loads the key without adding it to the command or shell history. If the key or input is missing or malformed, the script stops before calling the API. The sample prints results to your terminal, not to a third-party logging service.

3. Read the coordinates and check the match

The API returns a structured address. This is an illustrative response from the OpenAPI specification, not a recorded live lookup:

Illustrative API response
{
  "addressDetails": {
    "city": "Paris",
    "country": "France",
    "countryCode": "fr",
    "houseNumber": "10",
    "postcode": "75004",
    "road": "Rue de Rivoli"
  },
  "displayName": "Example address",
  "geoCoordinates": {
    "lat": 48.855,
    "lon": 2.36
  },
  "goodMatch": true
}

The script extracts geoCoordinates.lat and geoCoordinates.lon and prints them as lat and lon, alongside displayName and goodMatch.

  • Review partial matches. goodMatch: false means the result is not a confident full match. Ask for confirmation rather than treating it as a validated delivery address. A true value is not proof that an address is deliverable.
  • Handle missing coordinates. A successful reply may not contain a location. The script reports that case instead of inventing a point. A successful search still spends a credit when it finds nothing.
  • Keep zero distinct from missing. Default scalar fields can be omitted: an omitted goodMatch means false, and an omitted latitude or longitude within geoCoordinates means zero. The script supplies those defaults without turning an absent coordinate object into a location.

Coverage and address detail vary by country and source. Review results for your own workload. You can store the results without a contractual deletion deadline, subject to the underlying data licences, including attribution and applicable share-alike requirements.

4. Handle HTTP errors and timeouts

The script checks response.ok before treating JSON as a result. Failed calls set a nonzero exit code and do not print response bodies, request addresses or keys in error messages.

400 · Invalid request
Check that addressString is not blank and is at most 500 characters. Use the documented field names.
401 · Authentication failed
Check the complete key, its expiry and whether it was revoked. Stop retrying: repeated authentication failures can temporarily lock your IP.
403 · Key restrictions
Use a secret key for Node.js. Browser keys require an allowed Origin header and are not the right key for this script.
429 · Rate limit or no credits
If Retry-After is present, wait that many seconds. Without it, check your credit balance: retrying does not restore credits.
503 · Service unavailable
Retry temporary service failures later, with bounded backoff. Do not retry authentication or input errors unchanged.

No automatic retries are made. If the response is lost or your timeout expires after the server completed the lookup, the call may already have spent a credit; sending it again can spend another. Errors returned by the API do not consume credits. Read the error reference and credit and rate-limit rules before adding retries or concurrency.

The Free plan allows up to 2 requests per second, shared across the account, not per key or Node process. This one-address script does not schedule a batch. For a CSV or a larger queue, use the bulk-geocoding guide rather than starting an unbounded Promise.all.

Use it in a Node.js application

Keep this request on your server, validate the address at your application boundary and return only the result your client needs. Do not log API keys or address inputs. For an address form with suggestions while typing, use autocomplete followed by retrieval instead of repeatedly running forward geocoding. For coordinates you already have, use reverse geocoding.