How to Add Address Autocomplete to a Python App
Add US address autocomplete to your Python app in minutes. Free API, production-ready code, and Flask integration included.
Your checkout form collects addresses as freeform text. Users mistype street names, skip apartment numbers, and guess at zip codes. That bad data flows into your database, and each failed delivery costs $15-20 to re-ship.
Address autocomplete fixes the problem at the source. Users type a few characters, pick the correct address from a dropdown, and you get a postal-formatted string with unit numbers and zip+4 - ready to print on a shipping label.
This tutorial shows you how to add US address autocomplete to a Python app using sthan.io's address API. Works with Flask, Django, FastAPI, or any Python backend.
Quick summary: Installrequests, get free credentials from sthan.io, callGET /AutoComplete/USA/Address/{text}with a Bearer token. You get back a JSON array of formatted US addresses - no credit card required.
What you'll need: Python 3.7+ and a free sthan.io account. No credit card, no approval process. The free tier gives you 100,000 requests/month - enough for roughly 20,000 address lookups (assuming ~5 keystrokes per lookup).
Try it first
Type any partial US address - no signup required:
Try it live
That's what you're building. Type "123 main st" - lowercase, abbreviated, no city or state - and the API returns complete, postal-formatted addresses with apartment numbers, zip+4 codes, and proper casing.
What the API returns
The API wraps every response in an envelope. The address suggestions are in the Result field:
{
"Id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"Result": [
"123 Main St APT 1, Andover, MA 01810-3816",
"123 Main St APT 1, Delhi, NY 13753-1257",
"123 Main St STE 1, Caldwell, ID 83605-5476",
"123 Main St STE 1, Corinth, NY 12822-1010",
"123 Main St STE 1, Delhi, NY 13753-1258"
],
"StatusCode": 200,
"IsError": false,
"Errors": []
}
Each address includes the full street, unit designation (APT, STE, UNIT), city, state code, and zip+4. The API handles abbreviations (St, Ave, Blvd) and directional prefixes (N, S, E, W).
Get your API credentials
You get credentials immediately - no approval queue.
Install requests
pip install requests
Get an auth token
The API uses JWT authentication. First call gets a token, then you use it for autocomplete requests.
import os
import requests
PROFILE_NAME = os.environ.get("STHAN_PROFILE_NAME", "YOUR_PROFILE_NAME")
PROFILE_PASSWORD = os.environ.get("STHAN_PROFILE_PASSWORD", "YOUR_PROFILE_PASSWORD")
BASE_URL = "https://api.sthan.io"
def get_token():
response = requests.get(
f"{BASE_URL}/Auth/Token",
headers={
"profileName": PROFILE_NAME,
"profilePassword": PROFILE_PASSWORD,
},
)
response.raise_for_status()
data = response.json()
result = data["Result"]
return result["access_token"], result["expiration"]
token, expiration = get_token()
print(f"Token expires: {expiration}")
The token lasts 15 minutes. The production client below caches and refreshes it automatically.
Call the autocomplete endpoint
from urllib.parse import quote
def autocomplete(query, token):
response = requests.get(
f"{BASE_URL}/AutoComplete/USA/Address/{quote(query)}",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
return response.json()["Result"]
results = autocomplete("123 main st", token)
for address in results:
print(address)
The output matches the JSON shown above - the same five addresses, standardized from the abbreviated input.
Production-ready client with token caching
In production, you don't want to fetch a new token on every request. Here's a client that caches the token and refreshes it automatically:
import os
import requests
from urllib.parse import quote
from datetime import datetime, timezone
class SthanClient:
def __init__(self, profile_name=None, profile_password=None):
self.profile_name = profile_name or os.environ["STHAN_PROFILE_NAME"]
self.profile_password = profile_password or os.environ["STHAN_PROFILE_PASSWORD"]
self.base_url = "https://api.sthan.io"
self._token = None
self._expiration = None
def _get_token(self):
if self._token and self._expiration:
if datetime.now(timezone.utc) < self._expiration:
return self._token
response = requests.get(
f"{self.base_url}/Auth/Token",
headers={
"profileName": self.profile_name,
"profilePassword": self.profile_password,
},
)
response.raise_for_status()
data = response.json()["Result"]
self._token = data["access_token"]
self._expiration = datetime.fromisoformat(
data["expiration"].replace("Z", "+00:00")
)
return self._token
def autocomplete(self, query, min_length=3):
"""Fetch address suggestions for a partial query."""
if len(query.strip()) < min_length:
return []
token = self._get_token()
response = requests.get(
f"{self.base_url}/AutoComplete/USA/Address/{quote(query)}",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
return response.json()["Result"]
# Usage
client = SthanClient()
results = client.autocomplete("123 main st")
for address in results:
print(address)
This client:
- Caches the JWT and reuses it across requests
- Refreshes automatically when the token expires
- Enforces a minimum query length (3 characters) to avoid wasting API calls
- Reads credentials from environment variables by default
- Works with any Python web framework
Add it to a Flask app
Here's a minimal Flask endpoint that your frontend can call:
from flask import Flask, request, jsonify
app = Flask(__name__)
client = SthanClient() # reads from STHAN_PROFILE_NAME / STHAN_PROFILE_PASSWORD env vars
@app.route("/api/autocomplete")
def autocomplete_endpoint():
query = request.args.get("q", "")
if len(query) < 3:
return jsonify([])
results = client.autocomplete(query)
return jsonify(results)
if __name__ == "__main__":
app.run(port=3000)
Your frontend calls /api/autocomplete?q=123 main st and gets back a JSON array of addresses. Credentials stay on the server - never exposed to the browser.
The same pattern works with Django (views.py), FastAPI (@app.get), or any other Python web framework.
Handle errors
Two error codes to handle:
- 401 - Token expired. Refresh and retry.
- 429 - Rate limit hit. Back off and show what the user has typed so far.
def autocomplete_with_retry(client, query):
try:
return client.autocomplete(query)
except requests.HTTPError as e:
if e.response.status_code == 401:
# Force token refresh and retry once
client._token = None
return client.autocomplete(query)
elif e.response.status_code == 429:
# Rate limited — return empty, don't crash
return []
raise
What's next
Address autocomplete is step 1 of a complete address pipeline. The same credentials unlock these endpoints:
- Address Parser - Split "123 Main St, Andover, MA 01810" into street, city, state, zip fields
- Address Verification - Check if an address is real and deliverable
- Forward Geocoding - Convert an address to latitude/longitude
- Reverse Geocoding - Convert coordinates to an address
- IP Geolocation - Get location data from an IP address
For a complete Python guide covering all endpoints with async examples, see Integrate Address APIs in Python.
Frequently Asked Questions
Install requests, create a SthanClient with your free sthan.io credentials, and expose a /api/autocomplete endpoint. The full working Flask example is in Step 6 above. The same approach works with Django and FastAPI.
The free tier gives you 100,000 requests per month - roughly 20,000 address lookups assuming ~5 keystrokes per lookup. No credit card required. No trial period. See pricing for higher-volume plans.
Geocoding APIs are designed to find places - restaurants, landmarks, businesses. They often drop apartment numbers, ignore suite designations, and don't return zip+4 codes. A dedicated address autocomplete API returns postal-formatted mailing addresses from the first keystroke: unit numbers, zip+4, and standard postal abbreviations included.
No. Call it from your Python backend, not the browser. The Flask proxy in Step 6 handles this - your frontend calls your server, your server calls the API.
Typically under 100ms. The API is designed for real-time suggestions as users type.
Yes. The SthanClient class is framework-agnostic. Wire it to any route that accepts a query parameter. For Django: use it in views.py. For FastAPI: use it with @app.get.
Start Building with Sthan.io
Get 100,000 free API calls per month. Add address autocomplete to your Python app - no credit card required.