A Python wrapper for the NYC OTI Geoclient 2.0 API
Project description
Python Geoclient MOO
- Author @SteveScott
A comprehensive Python wrapper for the NYC OTI Geoclient 2.0 API, providing easy access to New York City's official geocoding service.
Overview
The NYC Geoclient API provides developer-friendly access to the Department of City Planning's official geocoding service (Geosupport). This Python wrapper makes it easy to geocode NYC addresses, intersections, places, and more.
Features
- 🏢 Address Geocoding: Convert NYC addresses to coordinates and property information
- 🔢 BBL/BIN Lookups: Get property details by Borough-Block-Lot or Building Identification Number
- 🛣️ Street Operations: Geocode intersections and blockfaces
- 🏛️ Place Names: Find coordinates for well-known NYC locations
- 🔍 Single-Field Search: Parse natural language location queries
- 🛡️ Type Safety: Full type hints and response models
- ⚡ Robust Error Handling: Comprehensive exception handling with retries
- 🧪 Well Tested: Extensive unit and integration test coverage
Installation
pip install python-geoclient-moo
Quick Start
from geoclient import GeoClient
# Initialize the client with your API credentials
client = GeoClient(
app_id="your_app_id",
app_key="your_app_key"
)
# Geocode an address
result = client.address("314", "west 100 st", "manhattan")
print(f"Coordinates: {result.latitude}, {result.longitude}")
print(f"BBL: {result.bbl}")
# Use as a context manager (recommended)
with GeoClient("your_app_id", "your_app_key") as client:
result = client.search("empire state building")
print(f"Found: {result.results[0].response}")
Getting API Credentials
To use the Geoclient API, you need to register for free API credentials:
- Visit the NYC Developer Portal
- Create an account and register a new application
- Subscribe to the Geoclient API
- There are two keys, a primary and secondary key. Take the first key and save it as the enviroment variable GEOCLIENT_SUBSCRIPTION_KEY.
API Methods
Address Geocoding
# Geocode with borough
result = client.address("314", "west 100 st", "manhattan")
# Geocode with ZIP code
result = client.address("314", "west 100 st", zip_code="10025")
# Access response data
print(f"Address: {result.house_number} {result.street_name}")
print(f"Borough: {result.borough_name}")
print(f"Coordinates: {result.latitude}, {result.longitude}")
print(f"BBL: {result.bbl}, BIN: {result.bin}")
Property Lookups
# Look up by Borough-Block-Lot (BBL)
result = client.bbl("manhattan", "1889", "1")
print(f"Address: {result.house_number} {result.street_name}")
# Look up by Building Identification Number (BIN)
result = client.bin_("1079043")
print(f"BBL: {result.bbl}")
Street Operations
# Geocode intersection
result = client.intersection("broadway", "west 100 st", "manhattan")
print(f"Intersection: {result.latitude}, {result.longitude}")
# Get blockface information
result = client.blockface(
on_street="amsterdam ave",
cross_street_one="west 110 st",
cross_street_two="west 111 st",
borough="manhattan"
)
print(f"Segment length: {result.segment_length} feet")
Place Names
# Geocode well-known places
result = client.place("empire state building", "manhattan")
print(f"Place: {result.place_name}")
print(f"Address: {result.house_number} {result.street_name}")
Single-Field Search
The search endpoint can parse various input formats:
# Address search
result = client.search("314 west 100 st manhattan")
# Intersection search
result = client.search("broadway and west 100 st")
# BBL search
result = client.search("1018890001")
# BIN search
result = client.search("1079043")
# Blockface search
result = client.search("broadway between west 100 st and west 101 st")
# Place search
result = client.search("empire state building")
# Process results
for search_result in result.results:
if search_result.status == "EXACT_MATCH":
print(f"Found exact match: {search_result.response}")
Search Options
Customize search behavior with optional parameters:
result = client.search(
"314 west 100 st",
exact_match_for_single_success=True, # Treat single result as exact match
exact_match_max_level=3, # Maximum search levels
return_tokens=True, # Return parsed input tokens
return_rejections=True, # Include rejected results
similar_names_distance=8 # Levenshtein distance for similar names
)
Response Models
All API methods return typed response objects with parsed data:
result = client.address("314", "west 100 st", "manhattan")
# Basic address info
print(result.house_number) # "314"
print(result.street_name) # "WEST 100 STREET"
print(result.borough_name) # "MANHATTAN"
print(result.zip_code) # "10025"
# Geographic coordinates
print(result.latitude) # 40.79582646522169
print(result.longitude) # -73.96790007529056
print(result.x_coordinate) # State plane coordinate
print(result.y_coordinate) # State plane coordinate
# Property identifiers
print(result.bbl) # "1018890001"
print(result.bin) # "1079043"
# Administrative areas
print(result.community_district) # "109"
print(result.census_tract) # "243"
# Processing status
print(result.geosupport.return_code) # "00" (success)
print(result.geosupport.message) # Status message
# Raw API response (if needed)
print(result.raw_data) # Complete API response dict
Error Handling
The library provides comprehensive error handling:
from geoclient.exceptions import (
GeoClientError, # Base exception
GeoClientHTTPError, # HTTP errors (4xx, 5xx)
GeoClientAuthError, # Authentication errors (401, 403)
GeoClientAPIError, # Geosupport API errors
)
try:
result = client.address("999999", "invalid street", "manhattan")
except GeoClientAuthError:
print("Check your API credentials")
except GeoClientAPIError as e:
print(f"Geosupport error: {e.geosupport_return_code} - {e.message}")
except GeoClientHTTPError as e:
print(f"HTTP error {e.status_code}: {e.message}")
except GeoClientError as e:
print(f"General error: {e.message}")
Configuration
Customize client behavior:
client = GeoClient(
app_id="your_app_id",
app_key="your_app_key",
base_url="https://api.nyc.gov/geo/geoclient/v2/", # Custom base URL
timeout=30.0, # Request timeout in seconds
retries=3, # Number of retries for failed requests
retry_delay=1.0, # Delay between retries in seconds
)
Advanced Usage
Batch Geocoding
The library includes a batch geocoding module for processing many addresses at once, with built-in rate limiting and error handling.
CSV-to-CSV (simplest)
Geocode an entire CSV file in one call:
from geoclient import geocode_csv
# Input CSV must have columns: house_number, street, borough
geocode_csv("addresses.csv", "results.csv", delay=0.1)
Programmatic batch
For more control, use batch_geocode_addresses with your own client:
from geoclient import GeoClient, batch_geocode_addresses
addresses = [
{"house_number": "314", "street": "west 100 st", "borough": "manhattan"},
{"house_number": "350", "street": "5th ave", "borough": "manhattan"},
]
with GeoClient() as client:
results = batch_geocode_addresses(addresses, client, delay=0.1)
for r in results:
if r["success"]:
print(f"{r['normalized_address']}: {r['latitude']}, {r['longitude']}")
else:
print(f"FAILED {r['input_house_number']} {r['input_street']}: {r['error_message']}")
Load addresses from CSV
from geoclient import load_addresses_from_csv
addresses = load_addresses_from_csv("addresses.csv")
# Returns: [{"house_number": "314", "street": "west 100 st", "borough": "manhattan"}, ...]
Column names are case-insensitive and accept spaces or underscores (e.g., House Number or house_number).
Save results to CSV
from geoclient import save_results_to_csv
save_results_to_csv(results, "output.csv")
Result fields
Each result dict contains:
| Field | Success | Failure |
|---|---|---|
input_house_number, input_street, input_borough |
Original input | Original input |
success |
True |
False |
latitude, longitude |
Coordinates | None |
normalized_address, normalized_borough |
Geosupport-normalized | None |
zip_code, bbl, bin, community_district |
Property info | None |
cross_street_one, cross_street_two |
Cross streets | None |
error_message |
None |
Error description |
geosupport_return_code |
"00" |
Error code or None |
Borough Validation
# The client accepts various borough formats:
client.address("314", "west 100 st", "manhattan") # Full name
client.address("314", "west 100 st", "Manhattan") # Title case
client.address("314", "west 100 st", "MANHATTAN") # Upper case
client.address("314", "west 100 st", "1") # Borough code
client.address("314", "west 100 st", "si") # Staten Island abbreviation
# Valid borough names/codes:
# Manhattan: "manhattan", "1"
# Bronx: "bronx", "2"
# Brooklyn: "brooklyn", "3"
# Queens: "queens", "4"
# Staten Island: "staten island", "si", "5"
Testing
Run the test suite:
# Install development dependencies
pip install -e ".[dev]"
# Run unit tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=geoclient --cov-report=html
# Run integration tests (requires API credentials)
export GEOCLIENT_APP_ID="your_app_id"
export GEOCLIENT_APP_KEY="your_app_key"
pytest tests/test_integration.py -v
API Reference
For complete API documentation, see the official Geoclient documentation.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- 📧 Email: Steve Scott
- 🐛 Issues: GitHub Issues
- 📖 Documentation: Official Geoclient Docs
Changelog
v1.0.0 (2026-03-19)
- Initial release
- Full support for all Geoclient 2.0 API endpoints
- Comprehensive error handling and type safety
- Extensive test coverage
- Complete documentation and examples
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_geoclient_moo-1.0.5.1.tar.gz.
File metadata
- Download URL: python_geoclient_moo-1.0.5.1.tar.gz
- Upload date:
- Size: 42.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dbe5cd03ba8a6b859154a70635d141a94678f011d96adfc53fac3428fa156744
|
|
| MD5 |
0d2f5e53934c864c35c161beffdfc092
|
|
| BLAKE2b-256 |
16ce10d3809642c310573b4102823fe9aee3a95c3cc881f9d464d2413617e565
|
Provenance
The following attestation bundles were made for python_geoclient_moo-1.0.5.1.tar.gz:
Publisher:
publish.yml on NYCMayorOps/python-geoclient-moo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_geoclient_moo-1.0.5.1.tar.gz -
Subject digest:
dbe5cd03ba8a6b859154a70635d141a94678f011d96adfc53fac3428fa156744 - Sigstore transparency entry: 1162354818
- Sigstore integration time:
-
Permalink:
NYCMayorOps/python-geoclient-moo@49c69d8669836e7030425988d5f3eb8efc343aa9 -
Branch / Tag:
refs/tags/v1.0.5.1 - Owner: https://github.com/NYCMayorOps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49c69d8669836e7030425988d5f3eb8efc343aa9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file python_geoclient_moo-1.0.5.1-py3-none-any.whl.
File metadata
- Download URL: python_geoclient_moo-1.0.5.1-py3-none-any.whl
- Upload date:
- Size: 17.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
739f8e0e9bd4f25b10fe261558ce9994192bef8a8558ddc164181b42b0f535bc
|
|
| MD5 |
c099091ef5596ae5415d82692850872a
|
|
| BLAKE2b-256 |
a12e09adf6ed772ffbf43f1a9c1a26436baced91c60612c68391d6d0dbadb82d
|
Provenance
The following attestation bundles were made for python_geoclient_moo-1.0.5.1-py3-none-any.whl:
Publisher:
publish.yml on NYCMayorOps/python-geoclient-moo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_geoclient_moo-1.0.5.1-py3-none-any.whl -
Subject digest:
739f8e0e9bd4f25b10fe261558ce9994192bef8a8558ddc164181b42b0f535bc - Sigstore transparency entry: 1162354850
- Sigstore integration time:
-
Permalink:
NYCMayorOps/python-geoclient-moo@49c69d8669836e7030425988d5f3eb8efc343aa9 -
Branch / Tag:
refs/tags/v1.0.5.1 - Owner: https://github.com/NYCMayorOps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49c69d8669836e7030425988d5f3eb8efc343aa9 -
Trigger Event:
push
-
Statement type: