Python guide · Forward geocoding

Geocode addresses in Python.

Turn a street address or place name into latitude and longitude using the standard library's urllib. This guide makes one server-side API request, checks the result and handles failures without an SDK or a pip install.

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

1. Set up Python and a secret API key

Use Python 3.9 or newer. The script imports only the standard library, so there is nothing to install and no virtual environment is required. In the dashboard, create a secret key, not a browser key.

Python does not read .env files on its own. Export the key in your terminal; this prompt keeps it out of the command line and shell history:

Terminal
read -rs OPENGEOCODING_API_KEY && export OPENGEOCODING_API_KEY

Paste the complete key and press Enter. On a deployed server, set the same environment variable through your hosting platform's secret settings. Never commit the key, put it in browser JavaScript or send it to a client. See API key authentication.

2. Save and run the forward-geocoding script

Save this as geocode.py. 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.py
import json
import os
import re
import sys
from urllib.error import HTTPError
from urllib.request import HTTPRedirectHandler, Request, build_opener

API_URL = os.environ.get('OPENGEOCODING_API_URL', 'https://api.opengeocoding.com') + '/v1/search'
api_key = os.environ.get('OPENGEOCODING_API_KEY', '').strip()
address = ' '.join(sys.argv[1:]).strip()


class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, *args, **kwargs):
        return None


def fail(message):
    print(message, file=sys.stderr)
    raise SystemExit(1)


if not re.fullmatch(r'og_[A-Za-z0-9_-]{32}', api_key):
    fail('Set OPENGEOCODING_API_KEY to a complete secret key.')
if not address or len(address) > 500:
    fail('Pass an address of 1-500 characters: python3 geocode.py "address"')

request = Request(
    API_URL,
    data=json.dumps({'addressString': address}).encode(),
    headers={'x-api-key': api_key, 'Content-Type': 'application/json'},
    method='POST',
)
try:
    with build_opener(NoRedirect).open(request, timeout=15) as response:
        result = json.load(response)
except HTTPError as error:
    print(f'HTTP {error.code}. See https://opengeocoding.com/docs/errors', file=sys.stderr)
    retry_after = error.headers.get('Retry-After')
    if error.code == 429 and retry_after:
        print(f'Wait {retry_after} seconds before retrying.', file=sys.stderr)
    raise SystemExit(1)
except (OSError, ValueError):
    fail('Network, timeout or invalid response. No automatic retry was made.')

point = result.get('geoCoordinates')
if point is None:
    fail('No coordinates returned. Review the address before trying again.')
print(
    json.dumps(
        {
            'displayName': result.get('displayName'),
            'lat': point.get('lat', 0),
            'lon': point.get('lon', 0),
            'goodMatch': result.get('goodMatch', False),
        },
        indent=2,
        ensure_ascii=False,
    )
)

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

Terminal
python3 geocode.py "10 Rue de Rivoli, 75004 Paris"

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

A non-2xx reply raises HTTPError, which the script reports without printing response bodies, request addresses or keys. Failed calls exit with status 1.

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 Python. 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 Python process. This one-address script does not schedule a batch. For a CSV or a larger queue, use the bulk-geocoding guide, whose script paces requests, rather than looping over addresses or starting an unbounded thread pool.

Use it in a Python 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. To reuse it in a web app or job, move the request into a function that raises on failure instead of exiting. 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.