Startup geocoding: free tier to first paid plan, the right way
Build your MVP on 3,000 free geocoding calls/day, instrument the ceiling, and know when $54/month unlocks 100,000 calls. Keep the bill flat.
Most startups never need to leave the free tier. That sentence belongs at the top, not buried in an FAQ, because the credibility of everything that follows depends on it being said plainly. Three thousand API calls per day is enough to run a lot of real products in production, indefinitely, for free. If your product does not yet have three thousand daily active users each triggering a geocoding call, you are almost certainly not bottlenecked on geocoding credits.
This post is for founders and first engineers who are watching every dollar. It walks through the full arc: building your MVP entirely on the free tier, instrumenting your own usage so the ceiling does not blindside you, recognising the genuine signals that mean you should upgrade, and the caching and batching habits that keep your bill flat long after you have crossed into paid territory. There is no upsell disguised as advice here. If the free tier covers your use case, use it. When it does not, the path to the first paid plan is straightforward — and the architecture habits that get you there also protect you from runaway spend on the far side.
What the free tier actually gives you
3,000 calls per day, no credit card required. That resets at midnight UTC. The calls cover any endpoint — geocoding, reverse geocoding, batch geocoding, elevation — so if you need multiple data types per user session, each call to each endpoint counts separately. Get your key at /api-keys, paste it into an environment variable, and you are running in three minutes.
To ground the number: 3,000 calls per day is 90,000 calls per month, 1,095,000 per year. At one geocode per user action, that is roughly 3,000 daily active users each triggering one geocoding event per day. Most MVPs live well below that threshold for their first six to twelve months. Many products that are not geocoding-heavy — a property search tool that geocodes on address entry, a delivery app that geocodes the drop-off once per order, a SAAS product that geocodes company HQ addresses on signup — stay below 3,000 calls/day for years.
The free tier is not a toy. It is a real tier with real rate limits, the full API surface, and the same coverage (504M+ addresses, 63 countries) as every paid plan. The only hard constraint is the daily cap.
Building the MVP on the free tier
The right mental model for the MVP phase is: every geocoding call should do real work, and real work should not repeat itself. Two patterns give you that for free, before you have written a line of caching infrastructure.
Pattern 1: geocode on write, not on read
If your product stores user-submitted addresses — delivery destinations, business locations, property addresses — geocode the address once when it arrives and persist the lat/lng alongside it. Do not geocode on every page load, every map render, or every API response. This is the single most common source of unnecessary call volume in products that are not yet thinking carefully about it.
A simple Python example to ground the pattern:
import os
import requests
GEOCODE_URL = "https://csv2geo.com/api/v1/geocode"
KEY = os.environ["CSV2GEO_API_KEY"]
def geocode_and_persist(address: str, db_row: dict) -> dict:
"""Call once on write. Never again unless the address changes."""
if db_row.get("lat") and db_row.get("lng"):
return db_row # already geocoded; skip the call
r = requests.get(
GEOCODE_URL,
params={"q": address, "api_key": KEY},
timeout=10,
)
r.raise_for_status()
result = r.json()["results"][0]
db_row["lat"] = result["lat"]
db_row["lng"] = result["lng"]
db_row["geocoded_at"] = result.get("timestamp")
return db_rowThe guard on line four (if db_row.get("lat")) is the entire caching strategy for the MVP phase. Addresses do not move. A geocode result from six months ago is still correct. You are not buying freshness by re-geocoding — you are spending credits you do not need to spend.
Pattern 2: batch one-off backfills through the WEB tool
When you import a CSV of existing addresses — a customer list, a property database, a seed dataset — do not loop through them in application code and burn 1,000 free-tier calls in a morning. Use the WEB batch tool in the dashboard. Upload the CSV, map the address column, run the job. The coordinates come back in a new CSV that you load straight into your database. This is the right tool for any one-off enrichment of a static dataset. It does not consume your daily free-tier budget the way a scripted loop would, and it handles retry logic, partial failures, and large file sizes better than an ad-hoc script will.
Reserve the REST API — and therefore the daily call budget — for live, user-triggered events. One-off imports belong in the WEB tool.
Pattern 3: instrument from day one
This is the only point in the post that is not about saving money directly. It is about not being surprised. Build a counter into your application from the moment you write the first geocoding call. The simplest version is one line in your existing request logging:
import logging
logger = logging.getLogger("geocoding")
def geocode(address: str) -> dict:
logger.info("geocode_call", extra={"address": address})
r = requests.get(
GEOCODE_URL,
params={"q": address, "api_key": KEY},
timeout=10,
)
r.raise_for_status()
return r.json()["results"][0]Aggregate geocode_call events in whatever log sink you already have — Datadog, CloudWatch, a Postgres table with a daily rollup, a hand-rolled Slack alert. The goal is a number you can look at once a week: daily geocoding calls, trending up or down. When that number starts approaching 2,400 (80% of the 3,000 cap), you have a week to decide whether to upgrade or to tighten the caching. That is the ceiling becoming visible — which is what you want.
The signals that mean it is time to upgrade
Three genuine signals. Not "you might want to consider upgrading." Signals that, if present, mean the free tier is constraining your product.
Signal 1: you are hitting the daily cap. If your application is returning errors because you have exhausted the 3,000 daily calls before midnight UTC, the free tier has done its job and you need more runway. Upgrade to the $54/month plan at csv2geo.com/pricing/api and you get 100,000 calls per month — about 3,300 per day on average, but without the hard daily reset. The mental model shifts from "daily budget" to "monthly pool."
Signal 2: you are rate-limiting user-visible features to stay below the cap. If you have written code that deliberately skips geocoding for some users, queues geocoding for later instead of doing it at signup, or degrades the map view for users who arrive late in the UTC day — that is product damage to protect a cost ceiling. The product damage costs more than $54/month.
Signal 3: your daily call count is growing week-over-week in a predictable curve. If you are at 1,500 today and the 30-day trend says you will be at 3,200 in six weeks, upgrade proactively. An unexpected hard stop at 3,000 calls on a Tuesday afternoon is worse than a planned upgrade on a Thursday when nothing is on fire. The $54/month plan gives you 100,000 calls per month — enough headroom to grow through roughly 33× the daily average you are at today before the next bracket becomes relevant.
What the first paid plan actually costs in context
The $54/month plan includes 100,000 calls. For a startup, the useful comparisons:
- 100,000 geocoded addresses per month is a meaningful dataset. If your product is processing 100,000 user-submitted addresses monthly, you are not an MVP anymore — you have users, data, and (most likely) revenue.
- The marginal cost per call at 100,000 calls/month is $0.00054. That is sub-cent per geocode.
- If your product has a paid tier at any price point, one additional paying customer covers several months of geocoding cost.
The decision to upgrade is almost never a budget question once you have product-market fit. It is a question of whether your call volume genuinely requires it. If it does, the first paid plan is a line item that rounds to zero in your operating budget.
Caching and batching: keeping the bill flat as you scale
The two habits that determine whether your geocoding cost scales linearly with users (bad) or stays flat despite growth (good). Neither requires external infrastructure at the start.
Application-layer caching
The in-process guard shown earlier (if db_row.get("lat")) is the MVP version. In production, the same principle scales to a database lookup or a Redis cache keyed by a canonical address string. A worked example in Python:
import hashlib
import redis
import json
cache = redis.Redis(host="localhost", port=6379, db=0)
CACHE_TTL = 60 * 60 * 24 * 90 # 90 days; addresses do not move
def canonical_key(address: str) -> str:
return "geo:" + hashlib.sha256(address.strip().lower().encode()).hexdigest()
def geocode_cached(address: str) -> dict | None:
key = canonical_key(address)
cached = cache.get(key)
if cached:
return json.loads(cached)
r = requests.get(
GEOCODE_URL,
params={"q": address, "api_key": os.environ["CSV2GEO_API_KEY"]},
timeout=10,
)
if not r.ok:
return None
result = r.json()["results"][0]
cache.set(key, json.dumps(result), ex=CACHE_TTL)
return resultAnd the equivalent in Node:
import { createClient } from 'redis';
import crypto from 'node:crypto';
const cache = createClient({ url: process.env.REDIS_URL });
await cache.connect();
const CACHE_TTL = 60 * 60 * 24 * 90; // 90 days
const API = 'https://csv2geo.com/api/v1/geocode';
const KEY = process.env.CSV2GEO_API_KEY;
function canonicalKey(address) {
const hash = crypto.createHash('sha256')
.update(address.trim().toLowerCase())
.digest('hex');
return `geo:${hash}`;
}
async function geocodeCached(address) {
const key = canonicalKey(address);
const hit = await cache.get(key);
if (hit) return JSON.parse(hit);
const url = `${API}?q=${encodeURIComponent(address)}&api_key=${KEY}`;
const r = await fetch(url);
if (!r.ok) return null;
const data = await r.json();
const result = data.results[0];
await cache.set(key, JSON.stringify(result), { EX: CACHE_TTL });
return result;
}A 90-day TTL is conservative. If your product has a meaningful proportion of returning users who trigger geocoding for addresses they have entered before — a delivery app where customers order to the same home address repeatedly, a property platform where agents look up the same listings — a Redis cache will collapse your actual API call volume to a fraction of the raw event count. The full argument for this, with real numbers, is in Caching Geocoding Results — 90% Cost Reduction.
Batching for bulk operations
The geocoding endpoint accepts a single address per call. When you are processing a CSV — nightly onboarding imports, weekly address list updates, a one-off data enrichment — do not call the endpoint one address at a time in a tight loop. Two reasons: it burns rate limit faster than necessary, and a single failed call in a sequential loop forces a retry that delays the whole job.
The better pattern is to use the WEB batch tool for static datasets, and to parallelise API calls with controlled concurrency for dynamic jobs. Controlled concurrency — not unlimited parallelism — because unbounded concurrent requests will saturate the rate limiter even on a paid plan. The detailed treatment of concurrency tuning lives in Concurrency Tuning — Finding the Geocoding Sweet Spot; the short version is: start at 5 concurrent requests, measure throughput and error rate, increase to 10 if the error rate stays below 0.5%.
A minimal Python implementation with controlled concurrency:
from concurrent.futures import ThreadPoolExecutor, as_completed
def geocode_batch(addresses: list[str], concurrency: int = 5) -> list[dict]:
results = [None] * len(addresses)
def worker(idx, addr):
return idx, geocode_cached(addr)
with ThreadPoolExecutor(max_workers=concurrency) as executor:
futures = {
executor.submit(worker, i, addr): i
for i, addr in enumerate(addresses)
}
for future in as_completed(futures):
idx, result = future.result()
results[idx] = result
return resultsThe ordering is preserved because results is pre-allocated by index. If a call fails and returns None, the caller sees None at that position and can decide whether to retry or skip — no silent data loss.
Observability: the view you need before the ceiling hits
Your application already knows how many geocoding calls it is making; it is making them. The gap between "making the calls" and "having a view of the calls" is one line of structured logging and one aggregation query. Both are cheap. Both matter.
A minimal daily rollup if you are logging to Postgres:
-- Run nightly, or as a materialized view
SELECT
date_trunc('day', occurred_at) AS day,
COUNT(*) AS calls,
COUNT(*) FILTER (WHERE cache_hit) AS cache_hits,
COUNT(*) FILTER (WHERE NOT cache_hit) AS api_calls
FROM geocoding_events
WHERE occurred_at > now() - interval '30 days'
GROUP BY 1
ORDER BY 1 DESC;The cache_hit boolean tells you how effectively your caching layer is working. If api_calls is growing but cache_hits is flat, your cache is not covering the new traffic — investigate whether new users are entering novel addresses (expected) or whether the cache is being bypassed by a code path you forgot to instrument (fixable). The fuller treatment of this pattern is in Observability for Geocoding Pipelines.
The one metric that matters most as you approach the free-tier ceiling: a 7-day rolling average of daily API calls. If that number is consistently above 2,000, upgrade before the week it hits 3,000. Proactive upgrades cost nothing extra. Emergency debugging on a Tuesday afternoon because your product is silently failing for users who arrive after you hit the cap is expensive in ways that do not show up in the API bill.
The architecture habits that protect you on the far side of the upgrade
Upgrading to $54/month for 100,000 calls does not mean the cost optimisation work is done. It means you have more room to grow into — and more room to waste if the habits are not in place. Three habits that matter:
1. Log cache hit rate as a first-class metric. Once you are on a paid plan, the cache hit rate is a cost efficiency number. A product with a 70% cache hit rate is spending 30 cents of every dollar in geocoding cost on redundant calls. A product with a 95% cache hit rate is spending 5 cents. The difference, at scale, is meaningful. Treat cache hit rate the way you treat database query performance — review it quarterly, set a floor (85% is a reasonable starting target), and investigate regressions.
2. Never geocode in a hot render loop. The geocoding call belongs in a background job or on write — not in the synchronous path that renders a page or responds to a user API request. At the free tier this matters for correctness (you will exhaust the cap during a traffic spike). At the paid tier it matters for reliability (a 200 ms geocoding call in your render path adds 200 ms to every page load, and a timeout adds 10 seconds). The architecture is the same whether you are on the free tier or spending $540/month.
3. Rate-limit your own application before the API rate-limits you. The API has rate limits; you should have your own. A user who submits a form with 10,000 addresses should not trigger 10,000 concurrent geocoding calls that exhaust your daily budget in 90 seconds. A job queue with a configurable throughput cap is the right answer. See Rate Limiting — Token Bucket vs Leaky Bucket for the implementation patterns.
The one-off backfill playbook
A common milestone in a startup's life: you have been running without geocoding, and you suddenly need to geocode your entire user or property database in one go. This is the situation the WEB batch tool was built for.
The playbook:
- Export the addresses from your database to a CSV with a unique ID column and an address column.
- Upload to the WEB batch tool. Map the address column. Start the job.
- Download the enriched CSV when it completes. The output includes lat/lng and a confidence score per row.
- Review rows with confidence below 0.7 — these are likely malformed or ambiguous addresses that need manual review.
- Import the enriched CSV back into your database, joining on the unique ID.
This consumes credits from your plan but does not require any application code changes, any retry logic, or any rate-limit management on your side. For a one-time backfill of tens of thousands of addresses, this is almost always the right approach. Reserve the REST API for live, ongoing, user-triggered events.
---
How to grow from free tier to paid plan
Step 1: Get your API key and make your first call
Navigate to /api-keys and generate a key. Set it as an environment variable (CSV2GEO_API_KEY) in your development environment and your CI/CD pipeline from day one — hardcoding it into source is a security problem that will cost you more than the geocoding bill if a key rotates after a leak. Make one test call:
curl -s "https://csv2geo.com/api/v1/geocode" \
--data-urlencode "q=1600 Pennsylvania Ave NW Washington DC" \
--data-urlencode "api_key=$CSV2GEO_API_KEY" \
-G | python3 -m json.toolConfirm you see a lat, lng, and a confidence value in the response. That is all you need to know the integration is alive.
Step 2: Add geocode-on-write to your data model
Before you write any other geocoding code, add lat, lng, geocoded_at, and geocoding_confidence columns to whatever table stores your addresses. Make geocoded_at nullable — a null value means "not yet geocoded," a non-null value means "skip the call." This schema decision costs you five minutes now and saves you from a category of duplicate-call bugs indefinitely.
Step 3: Instrument the call counter
Before you go to production, add a counter to every code path that calls the geocoding API. Log it as a structured event with at minimum: timestamp, whether the result came from cache or the API, and the confidence score returned. A structured log line in Python:
logger.info("geocode_event", extra={
"source": "cache" if from_cache else "api",
"confidence": result.get("confidence"),
"ts": datetime.utcnow().isoformat(),
})This is the data you will aggregate into your daily call count. Without it, you are flying blind toward the ceiling.
Step 4: Set a personal alert at 80% of the daily cap
Eighty percent of 3,000 is 2,400 calls per day. Write a cron job, a Datadog monitor, or a simple Slack webhook that fires when your 7-day rolling average crosses 2,400. This is not an emergency; it is a signal that you have a week or two to make a deliberate decision: optimise the caching, add a batch path, or upgrade. The alert at 80% is what converts "the ceiling surprised me" into "I saw the ceiling coming."
Step 5: Evaluate whether to optimise or upgrade
When the alert fires, run through a quick checklist before reaching for the credit card:
- Is the cache hit rate above 80%? If not, fix the cache before upgrading — you may cut call volume in half without spending anything.
- Are there code paths that geocode on read instead of write? Fix those first.
- Is there a bulk import or backfill running against the API that should be going through the WEB tool? Move it.
If you fix the quick wins and the daily average is still above 2,400, the product has genuinely grown past the free tier. Upgrade. The $54/month plan at csv2geo.com/pricing/api gives you 100,000 calls per month — roughly 33× your current daily average — with plenty of headroom for growth before the next bracket is relevant.
---
Frequently Asked Questions
Does the free tier require a credit card? No. Three thousand calls per day, no card required. You get a real API key, the full API surface, and the same data coverage as paid plans. The only constraint is the daily cap.
What happens when I hit the 3,000 daily cap? Calls that exceed the daily cap return an HTTP 429. Your application should handle this gracefully — log the error, return a degraded result to the user if geocoding is not strictly required, and do not retry in a tight loop. If you are hitting the cap regularly, that is Signal 1 from the upgrade section above.
Can I use the free tier for commercial products? Yes. The free tier is not limited to personal or non-commercial use. If your product stays below 3,000 calls per day, you can run it on the free tier indefinitely. Many do.
How does the 100,000-call paid plan differ from 3,000 calls/day on the free tier? The free tier resets at midnight UTC each day; any unused calls from Monday do not carry over to Tuesday. The 100,000-call paid plan is a monthly pool — you can burst to 10,000 in a single day if needed, as long as you stay within the monthly total. This smooths out traffic spikes without requiring you to engineer your own burst buffering.
Is there a way to monitor usage without building custom instrumentation? The usage data lives in your application's logs — we do not expose a usage dashboard in the current product surface. Instrument your own calls from day one as described in this post. It is four lines of structured logging and a daily aggregation query; it is worth doing properly.
What if my startup's geocoding needs are genuinely tiny — one address per new signup, a few dozen per day? Stay on the free tier. Seriously. A product with a few dozen daily active users is nowhere near the free-tier ceiling. Upgrade when the instrumentation tells you to, not before.
Are there architecture changes I should make before upgrading rather than after? Yes: implement the geocode-on-write pattern, add Redis or database-level caching, and move one-off backfills to the WEB tool. These changes often reduce your call volume enough that the free tier continues to cover you. Do the architecture work first, then look at the call counter again.
---
Related Articles
- Caching Geocoding Results — 90% Cost Reduction — the full caching pattern with Redis, TTL strategy, and cache-hit-rate monitoring
- Benchmarking Geocoding APIs — Honest Numbers — what to measure before committing to a geocoding provider
- Concurrency Tuning — Finding the Geocoding Sweet Spot — how to parallelise batch geocoding without tripping the rate limiter
- Observability for Geocoding Pipelines — the metrics, dashboards, and alerts that tell you what your pipeline is doing
- Rate Limiting — Token Bucket vs Leaky Bucket — implementing your own rate limiter so the API never has to do it for you
---
*I.A. / CSV2GEO Creator*
Use our batch geocoding tool to convert thousands of addresses to coordinates in minutes. Start with 100 free addresses.
Try Batch Geocoding Free →