Python

Mapricot's API is plain HTTPS and JSON, so Python needs no Mapricot package to call it. This page uses requests to call each API: geocoding, places, routing, and IP geolocation, and shows where the free map tiles fit. It was tested with Python 3.13 and requests 2.34.

Set up

pip install requests

Create a server key in the dashboard with the products you'll use: Places for geocoding and places, Routing, and IP geolocation. Set it as an environment variable, so it stays out of your code:

export MAPRICOT_KEY="mapricot_sk_…"

Then make a session that sends the key with every request and retries when the API is busy, and two functions that send a request and return its JSON:

import os

import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['MAPRICOT_KEY']}"
retry = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 503],
    allowed_methods=None,
    raise_on_status=False,
)
session.mount("https://", HTTPAdapter(max_retries=retry))


def get(path, **params):
    response = session.get(f"https://api.mapricot.com{path}", params=params, timeout=30)
    response.raise_for_status()
    return response.json()


def post(path, body):
    response = session.post(f"https://api.mapricot.com{path}", json=body, timeout=30)
    response.raise_for_status()
    return response.json()

Geocode an address

/v1/search turns an address or place name into coordinates:

data = get("/v1/search", q="128 SW 3rd Ave, Portland", limit=1)
for result in data["results"]:
    print(result["label"], result["lat"], result["lon"])
128 SW 3rd Avenue, Portland, OR, USA 45.521736 -122.673276

Each result also has its address in parts under address, and a type, such as address, street, or locality, that says how exact the match is. To limit results to some countries, pass country="US,CA". Geocoding lists every parameter and field.

Find the address at a point

/v1/reverse turns coordinates into the nearest address:

data = get("/v1/reverse", lat=45.5217, lon=-122.6733, limit=1)
result = data["results"][0]
print(result["label"], result["distanceMeters"])
128 SW 3rd Avenue, Portland, OR, USA 4

Geocode a CSV file

This reads addresses.csv, which has an address column, and writes each row to geocoded.csv with the coordinates and the matched address added:

import csv


def geocode(address):
    results = get("/v1/search", q=address, limit=1)["results"]
    if not results:
        return None, None, None
    return results[0]["lat"], results[0]["lon"], results[0]["label"]


with open("addresses.csv", newline="") as source, open("geocoded.csv", "w", newline="") as target:
    reader = csv.DictReader(source)
    writer = csv.DictWriter(target, [*reader.fieldnames, "lat", "lon", "label"])
    writer.writeheader()
    for row in reader:
        lat, lon, label = geocode(row["address"])
        writer.writerow({**row, "lat": lat, "lon": lon, "label": label})

It sends one request at a time. For large files, the batch geocoding script sends a few at once. Each account has a limit on requests in progress at once, so sending many in parallel gets 429 responses. Rows with no match get empty coordinates. Compare label with your address to check the match.

With pandas, the same geocode function fills new columns:

import pandas as pd

df = pd.read_csv("addresses.csv")
df[["lat", "lon", "label"]] = df["address"].apply(geocode).tolist()

Each row is one request. The first 250,000 every 30 days are free, then they cost $0.10 per 1,000. See pricing.

Suggest places as someone types

/v1/autocomplete takes the text typed so far. focus ranks results near a point first:

data = get("/v1/autocomplete", q="stumpt", focus="45.52,-122.68", limit=1)
suggestion = data["results"][0]
print(suggestion["label"], suggestion["id"])
Stumptown Coffee Roasters, Portland, OR, USA mp_b3BlbnN0cmVldG1hcDp2ZW51ZTpub2RlLzExOTg2MjI2NjYw

Find places nearby

/v1/nearby lists places of a category around a point, nearest first:

data = get("/v1/nearby", lat=45.52, lon=-122.68, category="food:coffee_shop", radius=1000, limit=1)
for place in data["results"]:
    print(place["name"], place["distanceMeters"])
Public Domain 37

Look up a place by ID

Every result has an id. Save it, and /v1/places/{id} fetches that place again later:

data = get(f"/v1/places/{suggestion['id']}")
print(data["result"]["label"])
Stumptown Coffee Roasters, Portland, OR, USA

Get a route

/v1/route takes a JSON body with two to 20 waypoints and a mode of drive, walk, or bicycle:

data = post("/v1/route", {"waypoints": [{"lat": 43.7396, "lon": 7.4275}, {"lat": 43.734, "lon": 7.4222}], "mode": "walk"})
route = data["routes"][0]
print(route["distanceMeters"], route["durationSeconds"])
1044 739

Distances are in meters and times in seconds. When no path connects the waypoints, routes is empty. The route's line is in polyline, encoded with six decimal places. To get its points, pip install polyline and decode with precision 6, as decoding a route polyline explains:

import polyline

points = polyline.decode(route["polyline"], 6)

Look up an IP address

/v1/ip/{ip} gives the approximate location of an IPv4 or IPv6 address:

data = get("/v1/ip/8.8.8.8")
print(data["city"], data["countryCode"])
Mountain View US

Show a map

Map tiles are free and need no key. MapLibre GL JS draws them in the browser from https://tiles.mapricot.com/styles/light.json, so Python doesn't fetch them: send your results to a web page, and draw them there, as Map tiles shows. MapLibre takes coordinates as [lon, lat].

Errors and retries

raise_for_status raises requests.HTTPError for any error response. The body says what went wrong:

try:
    get("/v1/search", q="")
except requests.HTTPError as error:
    print(error.response.status_code, error.response.json()["error"]["message"])
400 The service rejected the request.

The session retries 429 and 503 responses up to three times, waiting longer each time. Errors aren't billed, so retrying them costs nothing. A 429 that keeps coming back can also mean the account is out of credit. The API reference lists every status.

Questions

Can I use geopy with Mapricot? geopy doesn't include a geocoder for Mapricot. The get function above does the same job in a few lines, with every parameter the API takes.

Is there a Mapricot Python package? Not yet. The API is small enough that requests covers it, as above.

How do I geocode addresses in Python for free? Mapricot gives every account 250,000 free requests every 30 days, with no credit card, and lets you keep the results. Getting started takes a few minutes.