Mock geocoding & places API โ€” lat/lng test data without a Google API key

You're building the map page: markers, a store locator, a "search near me" box, maybe a geocode-on-submit form. You need coordinate-shaped JSON โ€” and every mainstream geocoder puts a key (and usually a credit card) between you and your first response. Verified with plain unauthenticated curls, September 2026:

Those keys and policies exist for good reasons โ€” real geocoding costs someone money. But if what you need today is deterministic coordinate-shaped data for a frontend, a demo, a workshop, or CI, this guide builds a hosted places API in one paste: no account, no key, CORS-open, HTTPS.

Building your map UI, marker clustering, store locator, or geocode-form error handling? That's this page. Turning a real address into real coordinates? Use a real geocoder โ€” see the honest comparison below.

1. A places mock in one paste

BASE=https://mockbird.mockbird.workers.dev
P=$(curl -s -X POST $BASE/api/projects -H 'content-type: application/json' \
     -d '{"name":"fake-geo","blank":true}')
PID=$(echo $P | sed 's/.*"id": *"\([^"]*\)".*/\1/')
KEY=$(echo $P | sed 's/.*"adminKey": *"\([^"]*\)".*/\1/')

curl -s -X POST $BASE/api/projects/$PID/resources -H "x-admin-key: $KEY" \
  -H 'content-type: application/json' -d '{
  "name":"places",
  "fields":[
    {"name":"name","type":"company"},
    {"name":"address","type":"address"},
    {"name":"city","type":"city"},
    {"name":"country","type":"country"},
    {"name":"lat","type":"latitude"},
    {"name":"lng","type":"longitude"},
    {"name":"category","type":"oneOf","values":["restaurant","cafe","hotel","museum","park"]}
  ],
  "seed":25
}'

latitude and longitude are first-class field types โ€” real numbers in range (โˆ’90โ€ฆ90, โˆ’180โ€ฆ180), not strings. A seeded place looks like this (live output; your values will differ):

{
  "id": 2,
  "name": "Northloop",
  "address": "1756 Pine Ln",
  "city": "Kyoto",
  "country": "Nigeria",
  "lat": -47.625583,
  "lng": 3.21361,
  "category": "cafe"
}

Honest note: seeded fields are independently random โ€” you'll get a Kyoto in Nigeria. That's fine for marker rendering and query logic; when a demo needs coherent pins, POST your own (section 3) or edit records in the dashboard.

2. The queries a map UI actually makes

Every list endpoint supports comparison suffixes on any numeric field โ€” which on lat/lng is a bounding-box query, the request every "search this area" map pane fires:

# viewport bbox: roughly Europe
curl "$BASE/m/$PID/places?lat_gte=35&lat_lte=60&lng_gte=-10&lng_lte=40"

# only cafรฉs, and only the fields your markers need
curl "$BASE/m/$PID/places?category=cafe&select=name,lat,lng"

# text search + pagination
curl "$BASE/m/$PID/places?q=north&page=1&limit=10"

# spreadsheet / QGIS import
curl "$BASE/m/$PID/places?mock_format=csv&select=name,lat,lng"

?select= keeps marker payloads small (id is always included); mock_format=csv returns RFC-4180 CSV of exactly those columns. Full parameter reference in the docs.

3. It's stateful โ€” pins persist

curl -s -X POST "$BASE/m/$PID/places" -H 'content-type: application/json' -d '{
  "name":"Cafe Test","address":"Unter den Linden 1","city":"Berlin",
  "country":"Germany","lat":52.5163,"lng":13.3777,"category":"cafe"
}'
# โ†’ {"id":26,...}   and GET /places/26 returns it โ€” writes are real

Unlike read-only sample APIs, this is a database: your "add location" form, admin CRUD, and optimistic-update logic all work against it, and the record is still there tomorrow.

4. A /geocode endpoint

Forms don't query lists โ€” they call a geocoder. Add a custom route that answers like one, echoing the submitted address back with fixed coordinates:

curl -s -X POST $BASE/api/projects/$PID/routes -H "x-admin-key: $KEY" \
  -H 'content-type: application/json' -d '{
  "method":"GET","path":"/geocode","contentType":"application/json",
  "body":"{\"query\":\"{{query.address}}\",\"results\":[{\"formatted\":\"{{query.address}}\",\"lat\":52.516275,\"lng\":13.377704,\"confidence\":9}],\"status\":\"OK\",\"ts\":\"{{now}}\"}"
}'
curl "$BASE/m/$PID/geocode?address=Brandenburg+Gate,+Berlin"
{
  "query": "Brandenburg Gate, Berlin",
  "results": [
    {"formatted": "Brandenburg Gate, Berlin", "lat": 52.516275, "lng": 13.377704, "confidence": 9}
  ],
  "status": "OK",
  "ts": "2026-09-16T09:47:00.662Z"
}

Deterministic by design: it never turns an address into true coordinates โ€” it returns a stable, geocoder-shaped response your form handler can parse, in tests, forever, with zero rate limit anxiety. Need several fixture addresses with distinct coordinates? Add one route per path (/geocode/berlin, /geocode/tokyo) or keep fixtures as records and query ?address=โ€ฆ exact-match on the places resource.

5. Google-envelope parity

Code written against Google reads response.results and checks response.status. Reshape any list response into that envelope with a response-envelope template โ€” no client changes needed:

# template: {"results":"$data","status":"OK"}   (URL-encoded below)
curl "$BASE/m/$PID/places?limit=2&mock_envelope=%7B%22results%22%3A%22%24data%22%2C%22status%22%3A%22OK%22%7D"
# โ†’ {"results":[{...},{...}],"status":"OK"}

Set it project-wide with PUT /api/projects/$PID/settings {"envelope":โ€ฆ} and every GET answers Google-shaped.

6. Drill the failures geocoders actually throw

The geocoding failure modes worth testing are throttling and latency โ€” Google's OVER_QUERY_LIMIT, Nominatim's one-per-second policy, a slow third-party hop:

# deterministic retry drill: first two calls 429, third succeeds
curl -i "$BASE/m/$PID/places?mock_seq=429,429,200&mock_seq_key=geo1"

# a real rolling rate limit: 5 requests/min per client, then 429 + Retry-After
curl -i "$BASE/m/$PID/places?mock_ratelimit=5"

# the slow-geocoder spinner test
curl "$BASE/m/$PID/geocode?address=Berlin&mock_delay=2000"

All simulation flags (mock_status, mock_chaos, mock_jitter, sequences, snapshots) work on both resource endpoints and custom routes โ€” the full list is in the docs, sequences in their own section.

Geocoding concern โ†’ Mockbird

You needReal-world frictionHere
First test responseGoogle: API key + billing-enabled account; Mapbox/OpenCage: signup + tokenOne anonymous curl
Places/POI list for markersPaid per-request Places APIsSeeded places resource, yours to shape
Viewport / bbox queryProvider-specific syntaxlat_gte/lat_lte/lng_gte/lng_lte
Geocode form endpointRate-limited, non-deterministicTemplated /geocode route, stable output
Hammering it from CIAgainst Nominatim policy; costs money on Google10k req/project/day, that's what it's for
Google-shaped envelopeโ€”mock_envelope template, byte-compatible results/status

Where the real ones win

The clean split: Mockbird for map frontends, store-locator demos, form handlers, and CI. A real geocoder the moment a coordinate has to be true. They compose โ€” env base URL in dev, real API in prod.

Notes & limits

Create yours

The paste block in section 1 is the whole setup โ€” or open the dashboard and click it together. The same project also serves GraphQL, exports OpenAPI + TypeScript types, and ejects to db.json anytime โ€” no lock-in.

โšก Skip the terminal: this link creates a live, seeded e-commerce backend (products, orders, customers, reviews) in the dashboard โ€” real URL, data browser already open, no signup. Or import your own OpenAPI spec, db.json, CSV, Postman collection, or HAR and mock your exact shapes.