How to Migrate from Google Maps Geocoding API to Geocoder.ca in 2025

(And Save 70-90% on North American Lookups While Avoiding Daily Limits)

If you're reading this, you’ve probably hit one (or all) of these pain points with Google’s Geocoding API:

Geocoder.ca has been running since 2005 as a no-nonsense, developer-friendly alternative focused on Canada + USA.

Feature Google Maps Geocoding Geocoder.ca
Pricing$5 per 1,000 requests$1–$1.50 per 1,000 (volume discounts)
Free daily quotaEffectively none after limitsNo daily limits, credits never expire
Caching / storage of resultsRestricted (legal headaches)Fully allowed – cache forever
Canadian coverage (rural, French, PO boxes)Often fails or low confidenceExcellent – built on Canada Post + provincial data
Rate limitingStrict QPS + daily capsVery generous (10–50 req/sec typical)

Ready to switch? It’s usually <30 minutes of work.

Step 1: Get Your Geocoder.ca API Key (30 seconds)

  1. Go to https://geocoder.ca
  2. Click “Buy Credits” or “Register”
  3. Buy any amount (even $10 gets you ~8,000 lookups)
  4. Copy your API key from the account page.

No credit card required for tiny test amounts – you can start with the free demo mode (watermarked, 1 req/sec).

Step 2: Code Examples in Popular Languages

All examples below use the exact same endpoint structure:

Forward geocoding:
https://geocoder.ca/?locate=POSTAL_OR_ADDRESS&auth=YOUR_API_KEY&json=1

Reverse geocoding:
https://geocoder.ca/?latt=LAT&longt=LONG&reverse=1&auth=YOUR_API_KEY&json=1

import requests

API_KEY = "your_api_key_here"

def geocode_address(locate):
    url = "https://geocoder.ca/"
    params = {
        "locate": locate,
        "auth": API_KEY,
        "json": 1
    }
    r = requests.get(url, params=params)
    data = r.json()
    if 'latt' in data:
        return float(data['latt']), float(data['longt'])
    else:
        print("Error:", data.get('error', 'Unknown'))
        return None

# Example
lat, lng = geocode_address("30 Wellington St W, Toronto, ON")
print(lat, lng)  # → 43.647413 -79.38315

import httpx
import asyncio

async def geocode_async(client, locate):
    params = {"locate": locate, "auth": API_KEY, "json": 1}
    r = await client.get("https://geocoder.ca/", params=params)
    data = r.json()
    return (float(data['latt']), float(data['longt'])) if 'latt' in data else None

async def main():
    async with httpx.AsyncClient() as client:
        tasks = [geocode_async(client, addr) for addr in ["V6B 3K3", "90210", "K1P 1J1"]]
        results = await asyncio.gather(*tasks)
        print(results)

asyncio.run(main())

const API_KEY = "your_api_key_here";

async function geocode(locate) {
  const params = new URLSearchParams({
    locate: locate,
    auth: API_KEY,
    json: 1
  });
  const res = await fetch(`https://geocoder.ca/?${params}`);
  const data = await res.json();
  if (data.latt) {
    return { lat: parseFloat(data.latt), lng: parseFloat(data.longt) };
  } else {
    console.error(data.error || data);
    return null;
  }
}

// Usage
geocode("1000 Rue Saint-Antoine O, Montréal").then(console.log);

<?php
$api_key = "your_api_key_here";

function geocode_ca($locate) {
    $url = "https://geocoder.ca/?locate=" . urlencode($locate) 
         . "&auth=$api_key&json=1";
    $json = file_get_contents($url);
    $data = json_decode($json, true);
    
    if (isset($data['latt'])) {
        return ['lat' => (float)$data['latt'], 'lng' => (float)$data['longt']];
    }
    return null;
}

print_r(geocode_ca("515 Legget Drive, Kanata, ON"));
?>

require 'net/http'
require 'json'

API_KEY = "your_api_key_here"

def geocode(locate)
  uri = URI("https://geocoder.ca/")
  params = { locate: locate, auth: API_KEY, json: 1 }
  uri.query = URI.encode_www_form(params)
  
  res = Net::HTTP.get_response(uri)
  data = JSON.parse(res.body)
  return { lat: data['latt'].to_f, lng: data['longt'].to_f } if data['latt']
  nil
end

puts geocode("1600 Pennsylvania Ave NW, Washington")

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

type Result struct {
	Latt  string `json:"latt"`
	Longt string `json:"longt"`
	Error string `json:"error,omitempty"`
}

func geocode(locate string) (*Result, error) {
	u, _ := url.Parse("https://geocoder.ca/")
	q := u.Query()
	q.Set("locate", locate)
	q.Set("auth", "your_api_key_here")
	q.Set("json", "1")
	u.RawQuery = q.Encode()

	resp, err := http.Get(u.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var r Result
	json.NewDecoder(resp.Body).Decode(&r)
	if r.Error != "" {
		return nil, fmt.Errorf(r.Error)
	}
	return &r, nil
}

func main() {
	r, _ := geocode("M5V 3L9") // Toronto Union Station
	fmt.Println(r.Latt, r.Longt)
}

#!/bin/bash
API_KEY="your_api_key_here"
LOCATE="100 King St W, Toronto"

curl -G "https://geocoder.ca/" \
     --data-urlencode "locate=$LOCATE" \
     --data-urlencode "auth=$API_KEY" \
     --data-urlencode "json=1" | jq .

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

public class GeocoderCaExample {
    private static final String API_KEY = "your_api_key_here";
    private static final HttpClient client = HttpClient.newHttpClient();
    private static final Gson gson = new Gson();

    public static class Coordinates {
        public double lat;
        public double lng;

        public Coordinates(double lat, double lng) {
            this.lat = lat;
            this.lng = lng;
        }
    }

    public static Coordinates geocode(String locate) throws Exception {
        String encodedLocate = URLEncoder.encode(locate, StandardCharsets.UTF_8);
        String url = "https://geocoder.ca/?locate=" + encodedLocate 
                   + "&auth=" + API_KEY + "&json=1";

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .GET()
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        JsonObject json = gson.fromJson(response.body(), JsonObject.class);

        if (json.has("latt") && json.has("longt")) {
            double lat = json.get("latt").getAsDouble();
            double lng = json.get("longt").getAsDouble();
            return new Coordinates(lat, lng);
        } else {
            String error = json.has("error") ? json.get("error").getAsString() : "Unknown error";
            throw new RuntimeException("Geocoding failed: " + error);
        }
    }

    public static void main(String[] args) throws Exception {
        Coordinates coords = geocode("30 Wellington St W, Toronto, ON");
        System.out.println("Lat: " + coords.lat + ", Lng: " + coords.lng);
        // → Lat: 43.647413, Lng: -79.38315
    }
}

OkHttpClient client = new OkHttpClient();

public Coordinates geocodeOkHttp(String locate) throws Exception {
    HttpUrl url = HttpUrl.parse("https://geocoder.ca/").newBuilder()
        .addQueryParameter("locate", locate)
        .addQueryParameter("auth", API_KEY)
        .addQueryParameter("json", "1")
        .build();

    Request request = new Request.Builder().url(url).build();
    try (Response response = client.newCall(request).execute()) {
        String body = response.body().string();
        JsonObject json = new Gson().fromJson(body, JsonObject.class);

        if (json.has("latt")) {
            return new Coordinates(
                json.get("latt").getAsDouble(),
                json.get("longt").getAsDouble()
            );
        }
        throw new RuntimeException("Error: " + json.get("error").getAsString());
    }
}

object GeocoderCa {
    private const val API_KEY = "your_api_key_here"
    private val client = OkHttpClient()
    private val gson = Gson()

    data class Result(val latt: Double = 0.0, val longt: Double = 0.0, val error: String? = null)

    fun geocode(locate: String, callback: (Result) -> Unit) {
        val url = HttpUrl.Builder()
            .scheme("https")
            .host("geocoder.ca")
            .addQueryParameter("locate", locate)
            .addQueryParameter("auth", API_KEY)
            .addQueryParameter("json", "1")
            .build()

        val request = Request.Builder().url(url).build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) = callback(Result(error = e.message))

            override fun onResponse(call: Call, response: Response) {
                response.body?.string()?.let { json ->
                    val result = gson.fromJson(json, Result::class.java)
                    callback(result)
                } ?: callback(Result(error = "Empty response"))
            }
        })
    }
}

// Usage
GeocoderCa.geocode("30 Wellington St W, Toronto, ON") { result ->
    if (result.error == null) {
        Log.d("Geocoder.ca", "Lat: ${result.latt}, Lng: ${result.longt}")
        // → Lat: 43.647413, Lng: -79.38315
    } else {
        Log.e("Geocoder.ca", "Error: ${result.error}")
    }
}

using System.Net.Http.Json;
using System.Text.Json;

var client = new HttpClient();
var key = "your_api_key_here";

async Task<(double? lat, double? lng)> GeocodeAsync(string locate)
{
    var uri = $"https://geocoder.ca/?locate={Uri.EscapeDataString(locate)}&auth={key}&json=1";
    var response = await client.GetFromJsonAsync<JsonElement>(uri);

    if (response.TryGetProperty("latt", out var latProp) && 
        response.TryGetProperty("longt", out var lngProp))
    {
        return (latProp.GetDouble(), lngProp.GetDouble());
    }
    return (null, null);
}

// Usage
var (lat, lng) = await GeocodeAsync("30 Wellington St W, Toronto, ON");
Console.WriteLine($"{lat}, {lng}"); // 43.647413, -79.38315

Reverse Geocoding (all languages use the same pattern)

Just add these parameters:

latt=45.5017&longt=-73.5673&reverse=1&all=1

The all=1 flag returns street address + city + province/state + postal code – perfect for enriching GPS logs.

object GeocoderCa {
    private const val API_KEY = "your_api_key_here"
    private val client = OkHttpClient()
    private val gson = Gson()

    data class ReverseResult(
        val standard: StandardAddress?,
        val error: String? = null
    )

    data class StandardAddress(
        val stnumber: String? = null,
        val staddress: String? = null,
        val city: String? = null,
        val prov: String? = null,      // CA province or US state
        val postcode: String? = null,
        val latt: Double = 0.0,
        val longt: Double = 0.0
    )

    fun reverseGeocode(lat: Double, lng: Double, callback: (ReverseResult) -> Unit) {
        val url = HttpUrl.Builder()
            .scheme("https")
            .host("geocoder.ca")
            .addQueryParameter("latt", lat.toString())
            .addQueryParameter("longt", lng.toString())
            .addQueryParameter("reverse", "1")
            .addQueryParameter("all", "1")           // remove if you only need basic fields
            .addQueryParameter("auth", API_KEY)
            .addQueryParameter("json", "1")
            .build()

        val request = Request.Builder().url(url).build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) =
                callback(ReverseResult(null, e.message))

            override fun onResponse(call: Call, response: Response) {
                response.body?.string()?.let { json ->
                    val result = gson.fromJson(json, ReverseResult::class.java)
                    callback(result)
                } ?: callback(ReverseResult(null, "Empty response"))
            }
        })
    }
}

// Usage
GeocoderCa.reverseGeocode(45.5017, -73.5673) { result ->
    if (result.error == null && result.standard != null) {
        with(result.standard) {
            Log.d("Reverse", "$stnumber $staddress, $city, $prov $postcode")
            // Example output:
            // 1000 Rue de la Gauchetière O, Montréal, QC H3B 4W5
        }
    } else {
        Log.e("Reverse", "Error: ${result.error}")
    }
}

import requests

def reverse_geocode(lat, lng):
    params = {
        "latt": lat,
        "longt": lng,
        "reverse": 1,
        "all": 1,
        "auth": "your_api_key_here",
        "json": 1
    }
    r = requests.get("https://geocoder.ca/", params=params).json()
    return r.get("standard", {})

print(reverse_geocode(43.6532, -79.3832))
# → {'staddress': 'Toronto City Hall', 'city': 'Toronto', 'prov': 'ON', 'postcode': 'M5G 1P5', ...}

async function reverseGeocode(lat, lng) {
  const params = new URLSearchParams({
    latt: lat,
    longt: lng,
    reverse: 1,
    all: 1,
    auth: API_KEY,
    json: 1
  });
  const res = await fetch(`https://geocoder.ca/?${params}`);
  const data = await res.json();
  return data.standard || { error: data.error };
}

reverseGeocode(49.2827, -123.1207).then(console.log);
// → { stnumber: '555', staddress: 'West Hastings St', city: 'Vancouver', prov: 'BC', ... }

Bonus: Canadian Address Autocomplete (Better than Google Places for Canada!)

One of the biggest hidden gems: geocoder.ca offers a lightweight, zero-dependency JavaScript autocomplete built specifically for Canadian addresses. It handles postal codes, rural routes, French street names, and PO boxes far better than global providers — and it’s only ~5 KB.

How to Add Canadian Autocomplete to Any Form (5-minute setup)

  1. Download the script from: geocoderAutocomplete.js (or host it yourself)
  2. Include in your HTML:
<script src="geocoderAutocomplete.js"></script>

3. Initialize in JavaScript (replace with your real API key!):

var autocomplete = new autocomplete({
  input: document.getElementById("your_input_id"),  // your address input field
  key: 'your_api_key_here'  // required - your geocoder.ca API key

  // Optional: customize suggestion template
  // itemtemplate: '<a href="#" class="list-group-item list-group-item-action"><strong>{{geocodeaddr}}</strong><br><small>{{city}}, {{prov}} {{postcode}}</small></a>'
});

Live Demo

Bonus: Batch Geocoding (up to 100,000 addresses at once)

Upload a plain-text file (one address per line) at
https://geocoder.ca/?batch=1
or use the API endpoint programmatically – way cheaper than doing 10,000 individual calls.

Final Checklist Before You Cut Google Loose

That’s it. Most developers finish the migration in under an hour and immediately see their monthly geocoding bill drop by 70–90% for North American traffic.

Questions? Hit me up in the comments or email [email protected] – happy to help you migrate!

P.S. If you found this guide useful, share it with one teammate who’s still paying Google tax 😄


Home | Buy Credits | Pricing | Contact
© 2025 geocoder.ca