1. Root-Cause Diagnostic Matrix: OVER_QUERY_LIMIT vs REQUEST_DENIED vs 429
Google Places API returns errors differently depending on whether you call the Legacy Web Service (/maps/api/place/*/json, which returns HTTP 200 with a status string) or Places API New (places.googleapis.com/v1/*, which returns standard HTTP 4xx/429 status codes).
| Error Status / HTTP Code | Exact Root Cause | Immediate GCP Fix | Zero-Downtime Alternative |
|---|---|---|---|
| OVER_QUERY_LIMIT (Legacy) / 429 RESOURCE_EXHAUSTED | Exceeded per-minute QPS limit or hit self-imposed daily SKU budget cap | Raise Quotas > Requests per minute in GCP Console + add token-bucket backoff | Route to GET /v1/places/search ($0.20–$0.50/1K, up to 600 RPM) |
| OVER_QUERY_LIMIT (Billing / Card Declined) | Cloud Billing account unlinked, expired card, or $200/free cap exhausted with hard cap | Verify Cloud Billing > Payment Overview and remove hard daily request cap | Prepaid never-expiring PAYG credits ($1 = 10,000 Credits, 0 card lockouts) |
| REQUEST_DENIED ('This API project is not authorized') | New GCP project calling Legacy /maps/api/place/* (disabled for new projects since Oct 2026) | Enable Places API (New) or use a drop-in Legacy-compatible gateway | Call https://api.placesdataapi.com/maps/api/place/textsearch/json |
| 400 INVALID_ARGUMENT ('FieldMask is required') | Calling places.googleapis.com/v1/places:searchText without X-Goog-FieldMask header | Pass X-Goog-FieldMask: places.id,places.displayName (watch Enterprise SKU tier!) | GET /v1/places/search returns all fields by default with 0 FieldMask headers |
| REQUEST_DENIED (IP / HTTP Referrer Restriction) | Using a browser HTTP-referrer-restricted key from a backend Node/Python server | Create a separate Server key restricted by IP address or API scope | Pass x-api-key: pd_live_... from any server, worker, or serverless function |
2. Production Python & Node.js Fix: Automatic Failover on OVER_QUERY_LIMIT / 429
Instead of dropping customer requests when Google throttles your key or hits a monthly SKU cap, wrap your Places lookup with an automatic failover to PlacesData API (/v1/places/search and /v1/places/nearby).
When Google Places returns status === 'OVER_QUERY_LIMIT', status === 'REQUEST_DENIED', or HTTP 429/403, retrying the same Google endpoint in a tight loop only extends your cooldown window. A resilient production client catches the quota/billing status and immediately resolves the query via https://api.placesdataapi.com/v1/places/search (or /v1/places/nearby for coordinate radius searches).
Because PlacesData API charges 5 Credits ($0.0002–$0.0005) per returned business with phone, website, opening hours, rating, and coordinates included—and 0 Credits on empty queries—many engineering teams promote PlacesData API to their primary rail and cut monthly Places spend by 90%–98%.
Resilient Places Search with Automatic OVER_QUERY_LIMIT / 429 Failover
Catch Google Places OVER_QUERY_LIMIT, REQUEST_DENIED, or HTTP 429 errors and resolve full business JSON in 39ms p50 via PlacesData API.
# Direct call to PlacesData API (no FieldMask required, all fields included)
curl -sS "https://api.placesdataapi.com/v1/places/search?q=plumbers+in+Phoenix+AZ&limit=20" \
-H "x-api-key: pd_live_your_api_key"import requests
PLACESDATA_KEY = "pd_live_your_api_key"
def search_places_resilient(query: str, limit: int = 20) -> list[dict]:
# Direct synchronous call to PlacesData API ($0.20-$0.50/1K, p50=39ms)
res = requests.get(
"https://api.placesdataapi.com/v1/places/search",
headers={"x-api-key": PLACESDATA_KEY},
params={"q": query, "limit": limit},
timeout=10,
)
res.raise_for_status()
return res.json()["data"]
places = search_places_resilient("plumbers in Phoenix AZ", limit=10)
print(f"Resolved {len(places)} businesses; first phone: {places[0].get('phone')}")export async function searchPlacesResilient(query, limit = 20) {
const res = await fetch(
`https://api.placesdataapi.com/v1/places/search?q=${encodeURIComponent(query)}&limit=${limit}`,
{ headers: { "x-api-key": process.env.PLACESDATA_API_KEY } }
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
return data;
}{
"code": 0,
"provider": "https://placesdataapi.com",
"status": "ok",
"data": [
{
"place_id": "ChIJy7c9a6gSK4cR9p0x8V2mN1w",
"cid": "1148290384756102938",
"name": "Robins Plumbing, Inc.",
"category": "Plumber",
"phone": "+16234864657",
"website": "https://robinsplumbing.com/",
"rating": 4.9,
"reviews_count": 1420,
"address": "4143 W Venus Way, Chandler, AZ 85226",
"latitude": 33.306142,
"longitude": -111.912841
}
],
"meta": {
"endpoint": "/v1/places/search",
"count": 1,
"credits_charged": 5,
"took_ms": 39
}
}Interactive Engineering Workbench · GET /v1/places/search
Verify real-time latency, credit math & JSON schema
Frequently Asked Questions
- Why am I getting OVER_QUERY_LIMIT even though my Google Cloud billing card is active?
- OVER_QUERY_LIMIT triggers not only on billing failures, but also when your burst traffic exceeds the per-minute QPS ceiling (Requests per minute per user) or when a self-imposed daily quota cap set in Google Cloud Console > APIs & Services > Places API > Quotas is reached.
- Why does a brand-new Google Cloud project return REQUEST_DENIED on /maps/api/place/textsearch/json?
- As of October 2026, Google no longer allows new Google Cloud projects to enable Places API (Legacy). New projects can only enable Places API (New) at places.googleapis.com/v1/places:searchText (which requires POST and an X-Goog-FieldMask header)—or you can keep your existing GET /maps/api/place/textsearch/json code by pointing your base URL to https://api.placesdataapi.com.
- How do I migrate off Google Places API to avoid $32–$40/1K SKU caps permanently?
- Follow our step-by-step migration guide at /guides/migrate-from-google-places-api-save-90-percent, explore the full REST architecture at /google-maps-api, or test live queries on /docs/places-search and /docs/places-nearby with 12,000 free Credits.