cache_places/docs

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.

Surfaces

ServiceAuthPurpose
RpcPlaces x-api-key header Autocomplete + GetPlace + GetUsage for clients
RpcApiKeys ?auth_password=… Admin: issue / list / revoke keys

Client — Autocomplete

POST /cache_places.pb_places.RpcPlaces/Autocomplete
curl -X POST "https://cache-places.dimitri.land/cache_places.pb_places.RpcPlaces/Autocomplete" \
  -H "x-api-key: $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"input": "sohm", "session_token": "<uuid>", "user_id": "user_123"}'
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
suggestionsSuggestion[]Predictions Google returned for this keystroke. Empty list if nothing matched.
Suggestion
place_idstringGoogle place ID — pass this to GetPlace to resolve the full place.
textstringFull single-line label ("Eiffel Tower, Paris, France").
primary_textstringMain text, bolded in Google's own UI ("Eiffel Tower").
secondary_textstringSecondary / context text ("Paris, France").
typesstring[]Place types — e.g. ["tourist_attraction", "point_of_interest"].

Client — GetPlace

POST /cache_places.pb_places.RpcPlaces/GetPlace
curl -X POST "https://cache-places.dimitri.land/cache_places.pb_places.RpcPlaces/GetPlace" \
  -H "x-api-key: $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"place_id": "ChIJ...", "session_token": "<uuid>", "user_id": "user_123"}'

Response

GetPlaceResponse
placePlaceResolved place — schema below.
cache_hitbooltrue when served from our cache (Google was not called). Use this to track your hit rate.
Place
Identity
place_idstringGoogle place ID — primary key of the cache.
namestringDisplay name ("Eiffel Tower").
Address
short_addressstringCondensed address.
formatted_addressstringFull Google-formatted address.
countrystringISO-3166 short code — "US", "FR".
citystringLocality / city.
postal_codestringPostal / ZIP code.
Location
latdoubleLatitude.
lngdoubleLongitude.
Type
primary_typestringMain Google type id — e.g. "restaurant".
primary_type_displaystringHuman-readable form of primary_type.
typesstring[]All Google-assigned place types.
Ratings & pricing
ratingdoubleAverage Google rating (0–5). Read alongside has_rating.
has_ratingboolDistinguishes "no rating" from a literal 0.0.
user_rating_countint64Number of ratings. Read alongside has_user_rating_count.
has_user_rating_countboolDistinguishes "no count" from a literal 0.
price_levelstring"PRICE_LEVEL_FREE" / "INEXPENSIVE" / "MODERATE" / "EXPENSIVE" / "VERY_EXPENSIVE".
price_range_jsonbytesNewer {startPrice, endPrice} object, JSON-encoded. Empty when absent.
Contact
website_uristringBusiness website.
google_maps_uristringCanonical maps.google.com URL.
national_phone_numberstringNational-format phone number.
international_phone_numberstringInternational-format phone number.
Status & time
business_statusstring"OPERATIONAL" / "CLOSED_TEMPORARILY" / "CLOSED_PERMANENTLY" / "BUSINESS_STATUS_UNSPECIFIED".
timezone_idstringIANA zone id ("America/New_York").
utc_offset_minutesint32Offset from UTC in minutes.
Map framing
plus_code_globalstringGlobal Plus Code — short, address-free code for this place.
viewport_sw_latdoubleViewport southwest corner latitude.
viewport_sw_lngdoubleViewport southwest corner longitude.
viewport_ne_latdoubleViewport northeast corner latitude.
viewport_ne_lngdoubleViewport northeast corner longitude.
Opaque blobs
regular_opening_hours_jsonbytesGoogle's opening-hours object {openNow, periods[], weekdayDescriptions[]}, JSON-encoded. Empty when absent.
raw_jsonbytesFull 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 key
curl -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-user
curl -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_requestsint64Count of matching request_logs rows.
autocomplete_callsint64Rows with method=autocomplete. Autocomplete only writes one row per session (not per keystroke).
get_place_callsint64Rows with method=get_place.
cache_hitsint64get_place rows served from the cache (we didn't pay Google).
errorsint64Rows with status=error.
total_sessionsint64Distinct autocomplete_sessions rows in the filtered window.
Detail
entriesUsageEntry[]request_logs rows, newest-first.
sessionsUsageSession[]autocomplete_sessions rows, newest-first.
UsageEntry
idstringRow UUID.
created_atTimestampWhen the call landed.
methodstring"autocomplete" or "get_place".
querystringTyped string (autocomplete) or place_id (get_place).
user_idstringWhatever you sent on the original call. Empty if none was attached.
session_tokenstringStitches to sessions.
cache_hitboolOnly meaningful for get_place.
latency_msint32Server-side latency.
statusstring"ok" / "error" / "not_found".
errorstringTruncated error message when status=error.
UsageSession
idstringRow UUID.
started_atTimestampFirst Autocomplete call of the session.
last_activityTimestampMost recent call on this session.
ended_atTimestampSet when a trailing GetPlace closed the session. Unset for abandoned sessions.
session_tokenstringJoin key to entries.
user_idstringLatched from the first call that opened the session.
autocomplete_callsint32Keystroke count (rolled up).
selected_place_idstringplace_id the user eventually picked, if any.
last_inputstringLatest typed string seen on the session.

Admin — issue a key

POST /cache_places.pb_api_keys.RpcApiKeys/CreateApiKey
curl -X POST "https://cache-places.dimitri.land/cache_places.pb_api_keys.RpcApiKeys/CreateApiKey?auth_password=$ADMIN_PW" \
  -H 'Content-Type: application/json' \
  -d '{"name": "sidekick_api prod"}'

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.