A ConnectRPC proxy in front of Google Places. Every place_id we've seen before comes back from CockroachDB instead of Google — free, fast, deterministic.
Always attach user_id when you have one. It's an opaque string — internal id, UUID, email, whatever identifies your end-user. Stored verbatim, returned by GetUsage, never forwarded to Google. Skipping it means you can't break your usage down per end-user later.
Response
AutocompleteResponse
suggestions
Suggestion[]
Predictions Google returned for this keystroke. Empty list if nothing matched.
Suggestion
place_id
string
Google place ID — pass this to GetPlace to resolve the full place.
text
string
Full single-line label ("Eiffel Tower, Paris, France").
primary_text
string
Main text, bolded in Google's own UI ("Eiffel Tower").
secondary_text
string
Secondary / context text ("Paris, France").
types
string[]
Place types — e.g. ["tourist_attraction", "point_of_interest"].
Global Plus Code — short, address-free code for this place.
viewport_sw_lat
double
Viewport southwest corner latitude.
viewport_sw_lng
double
Viewport southwest corner longitude.
viewport_ne_lat
double
Viewport northeast corner latitude.
viewport_ne_lng
double
Viewport northeast corner longitude.
Opaque blobs
regular_opening_hours_json
bytes
Google's opening-hours object {openNow, periods[], weekdayDescriptions[]}, JSON-encoded. Empty when absent.
raw_json
bytes
Full raw Google response — kept verbatim so new Google fields don't require a re-fetch.
Note —bytes fields arrive as base64 strings over JSON. Decode, then JSON.parse.
Session dance — reuse the same session_token for every keystroke of an Autocomplete interaction and the trailing GetPlace. Google bills the whole thing as one lookup; a fresh token per keystroke is per-keystroke billing.
Client — GetUsage
POST/cache_places.pb_places.RpcPlaces/GetUsage
Read back your own traffic. Scope is pinned to the x-api-key on the request — you can never observe another tenant's rows. Pass user_id to narrow to one of your end-users; omit it for everything under this key.
# All usage for this API keycurl -X POST "https://cache-places.dimitri.land/cache_places.pb_places.RpcPlaces/GetUsage" \
-H "x-api-key: $API_KEY" \
-H 'Content-Type: application/json' \
-d '{}'# Usage for one end-usercurl -X POST "https://cache-places.dimitri.land/cache_places.pb_places.RpcPlaces/GetUsage" \
-H "x-api-key: $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"user_id": "user_123"}'
Response
GetUsageResponse
Aggregates (over the filtered window)
total_requests
int64
Count of matching request_logs rows.
autocomplete_calls
int64
Rows with method=autocomplete. Autocomplete only writes one row per session (not per keystroke).
get_place_calls
int64
Rows with method=get_place.
cache_hits
int64
get_place rows served from the cache (we didn't pay Google).
errors
int64
Rows with status=error.
total_sessions
int64
Distinct autocomplete_sessions rows in the filtered window.
Detail
entries
UsageEntry[]
request_logs rows, newest-first.
sessions
UsageSession[]
autocomplete_sessions rows, newest-first.
UsageEntry
id
string
Row UUID.
created_at
Timestamp
When the call landed.
method
string
"autocomplete" or "get_place".
query
string
Typed string (autocomplete) or place_id (get_place).
user_id
string
Whatever you sent on the original call. Empty if none was attached.
session_token
string
Stitches to sessions.
cache_hit
bool
Only meaningful for get_place.
latency_ms
int32
Server-side latency.
status
string
"ok" / "error" / "not_found".
error
string
Truncated error message when status=error.
UsageSession
id
string
Row UUID.
started_at
Timestamp
First Autocomplete call of the session.
last_activity
Timestamp
Most recent call on this session.
ended_at
Timestamp
Set when a trailing GetPlace closed the session. Unset for abandoned sessions.
session_token
string
Join key to entries.
user_id
string
Latched from the first call that opened the session.
The response carries two values: key — the private bearer for the x-api-key header, keep it server-side — and public_key (cppk_…), a publishable token safe to embed in browser ?pk= photo-proxy URLs. It authorizes photo resolution only. Photo URLs returned by GetPlace already carry your pk.
Go SDK
import cache_places "github.com/ethanquix/autocomplete_places"
c := cache_places.New("https://cache-places.dimitri.land", os.Getenv("CACHE_PLACES_API_KEY"))
// Always pass WithUserID when you have one — unlocks per-user GetUsage.
s := c.NewSession()
uid := cache_places.WithUserID("user_123")
hits, _ := s.Autocomplete(ctx, "sohm", uid)
res, _ := s.GetPlace(ctx, hits[0].PlaceID, cache_places.WithGetPlaceUserID("user_123"))
// Read your own usage back out.
u, _ := c.GetUsage(ctx, cache_places.WithUsageUserID("user_123"))
fmt.Println(u.TotalRequests, u.CacheHits)
Full integration guide on README.md. Copy-paste brief for AI agents at /prompt.