Fix Google Maps API 403 Error: REQUEST_DENIED, RefererNotAllowedMapError & Timeout Troubleshooting Guide
Fix Google Maps API 403 errors fast. Covers REQUEST_DENIED, RefererNotAllowedMapError, billing issues, key restrictions, quota limits, and timeout fixes.
- A 403 REQUEST_DENIED most often means billing is not enabled on your Google Cloud project — enable it in the Billing console even if you're within the free tier.
- RefererNotAllowedMapError means your HTTP referrer (domain or localhost) is not listed in the API key's allowed referrers; add the exact origin including protocol and wildcard path.
- ApiNotActivatedMapError means you are calling an API (e.g., Geocoding, Places, Directions) that has not been enabled in the Google Cloud Console — enable each API your code calls individually.
- OVER_QUERY_LIMIT / quota 403s require either exponential-backoff retry logic or a quota increase request in the Google Cloud Console.
- Timeout errors from the Maps API are usually caused by missing await/Promise handling, rate limiting triggering slow retries, or exceeding the 10-request-per-second default per-client limit — implement client-side request queuing.
| Method | When to Use | Time to Fix | Risk |
|---|---|---|---|
| Enable billing on GCP project | First 403 with REQUEST_DENIED and no billing set up | 2 min | Low — free tier still applies |
| Add referrer to API key restrictions | RefererNotAllowedMapError in browser console | 3 min | Low — only affects key scope |
| Enable the specific Maps API | ApiNotActivatedMapError or 403 on REST endpoint | 2 min | Low |
| Remove all key restrictions (debug only) | Isolating whether restrictions cause the 403 | 1 min | High — never leave unrestricted in production |
| Rotate and replace API key | Key compromised, invalid, or deleted | 10 min | Medium — requires deployment update |
| Implement exponential backoff retry | OVER_QUERY_LIMIT / quota 403 on burst traffic | 1–2 hrs dev | Low — improves reliability |
| Request quota increase in GCP Console | Consistently hitting daily or per-minute quota limits | 1–3 business days | Low |
| Add client-side request queue (timeout fix) | Maps API calls timing out under load | 2–4 hrs dev | Low — transparent to users |
Understanding the Google Maps API 403 Error
HTTP 403 from the Google Maps API is an authorization failure: the server received your request and understood it, but refuses to fulfill it. Unlike a 401, which signals missing credentials, a 403 means the credentials you supplied (your API key) were recognized but lacked permission for the requested operation. The Maps Platform returns the specific reason in two places: the HTTP status code (403) and a machine-readable status field in the JSON body (e.g., "status": "REQUEST_DENIED") or a JavaScript error code in the browser console (e.g., RefererNotAllowedMapError).
The Google Maps JavaScript API additionally logs errors to window.gm_authFailure — you can intercept this callback to surface errors gracefully to users.
Step 1: Identify the Exact Error Variant
Before fixing anything, determine which 403 variant you have. Open your browser DevTools (F12) → Console tab, or inspect the raw HTTP response body of your Maps API REST call.
Browser console variants (Maps JavaScript API):
RefererNotAllowedMapError— Your page's origin is not in the API key's HTTP referrer allowlist.ApiNotActivatedMapError— The Maps JavaScript API is not enabled in your GCP project.MissingKeyMapError— No API key was supplied at all.InvalidKeyMapError— The key string is malformed or does not exist.ExpiredKeyMapError— The key was deleted or the project was shut down.
REST API JSON body variants (Geocoding, Directions, Places, etc.):
{ "status": "REQUEST_DENIED", "error_message": "This API project is not authorized to use this API." }
{ "status": "REQUEST_DENIED", "error_message": "The provided API key is not authorized to use this service." }
{ "status": "OVER_QUERY_LIMIT", "error_message": "You have exceeded your daily request quota for this API." }
{ "status": "REQUEST_DENIED", "error_message": "Requests from this IP address are blocked." }
Timeout-related symptoms:
net::ERR_TIMED_OUTon maps script load- Promise never resolves on geocoder or directions service calls
UNKNOWN_ERRORstatus returned after a long delay (Maps API internal timeout)- Node.js
Error: ETIMEDOUTorError: socket hang upon server-side Maps API calls
Step 2: Enable Billing (Most Common Fix)
As of June 2018, Google requires a billing account linked to every project using the Maps Platform, even if your usage stays within the $200/month free credit. A project with APIs enabled but no billing account attached will return 403 REQUEST_DENIED on almost every call.
How to fix:
- Go to console.cloud.google.com/billing.
- Click Link a billing account and select or create a billing account.
- Navigate to APIs & Services → Credentials and confirm your API key belongs to the billed project.
- Wait 1–5 minutes for propagation, then re-test.
Verification command (using the Geocoding REST API):
curl -s "https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway&key=YOUR_API_KEY" | python3 -m json.tool
A successful response has "status": "OK". A 403 still shows "status": "REQUEST_DENIED".
Step 3: Fix API Key Referrer Restrictions
If you see RefererNotAllowedMapError, the HTTP Referrer header your browser sends does not match any pattern in the key's allowlist.
Fix in the GCP Console:
- Go to APIs & Services → Credentials and click your key.
- Under Application restrictions, select HTTP referrers (web sites).
- Add entries for every origin where your map loads:
https://www.example.com/*https://example.com/*http://localhost/*(for local dev)http://localhost:3000/*(for React/Next.js dev server)https://*.example.com/*(for subdomains)
- Save and wait up to 5 minutes.
Gotcha: The referrer check uses the Referer HTTP header, which browsers sometimes omit on direct navigation or when <meta name="referrer" content="no-referrer"> is set. Test with the referrer explicitly set:
curl -s -H "Referer: https://www.example.com/map" \
"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY" -I
Gotcha for IP restrictions: If you're hitting the REST API server-side and have IP restrictions enabled, the allowed IP must be the outbound IP of your server, not your local machine. Confirm with:
curl -s https://ifconfig.me
Then verify that IP is in your key's IP address allowlist.
Step 4: Enable the Specific APIs Your Code Uses
Each Google Maps service (JavaScript API, Geocoding, Directions, Places, Distance Matrix, Static Maps, etc.) is a separate API that must be individually enabled. Enabling one does not enable the others.
Enable via gcloud CLI:
# List enabled APIs in your project
gcloud services list --enabled --project=YOUR_PROJECT_ID | grep maps
# Enable the APIs you need
gcloud services enable maps-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable geocoding-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable directions-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable places-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable maps-javascript.googleapis.com --project=YOUR_PROJECT_ID
Common mismatch: Developers enable the Maps JavaScript API but then make server-side calls to the Geocoding REST API — these are separate products requiring separate enablement and (for server-side calls) a server key with IP restrictions, not an HTTP referrer-restricted browser key.
Step 5: Handle OVER_QUERY_LIMIT (Quota 403s)
When you exceed the requests-per-second or daily quota, Maps APIs return HTTP 429 or a JSON body with "status": "OVER_QUERY_LIMIT". Some older API versions return this with a 403 status code.
Immediate mitigation — exponential backoff in JavaScript:
async function geocodeWithRetry(geocoder, request, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const result = await new Promise((resolve) => {
geocoder.geocode(request, (results, status) => resolve({ results, status }));
});
if (result.status === 'OK') return result.results;
if (result.status === 'OVER_QUERY_LIMIT') {
const delay = Math.pow(2, attempt) * 500 + Math.random() * 500;
await new Promise(r => setTimeout(r, delay));
continue;
}
throw new Error(`Geocoding failed: ${result.status}`);
}
throw new Error('Max retries exceeded (OVER_QUERY_LIMIT)');
}
Request quota increase:
- In GCP Console, go to IAM & Admin → Quotas.
- Filter by service (e.g., "Geocoding API").
- Select the quota metric and click Edit Quotas.
- Submit the increase request with justification.
Step 6: Fix Google Maps API Timeout Issues
Timeouts occur when a Maps API request takes longer than your client's configured timeout before receiving a response. This is separate from a 403 but often co-occurs under load.
Diagnose the source:
- Script load timeout: The
<script src="https://maps.googleapis.com/maps/api/js">never firesonload. Usually a DNS or network issue. Test:curl -w "%{time_total}" -o /dev/null -s https://maps.googleapis.com/maps/api/js?key=YOUR_KEY - Geocoder/service call timeout: The callback or Promise never resolves. Usually rate limiting, OVER_QUERY_LIMIT, or a dropped connection.
- Server-side Node.js timeout: The HTTP client (axios, node-fetch, got) times out before Maps API responds.
Node.js server-side timeout fix with @googlemaps/google-maps-services-js:
import { Client } from '@googlemaps/google-maps-services-js';
import axiosRetry from 'axios-retry';
const client = new Client({});
async function geocodeAddress(address) {
const response = await client.geocode({
params: {
address,
key: process.env.GOOGLE_MAPS_API_KEY,
},
timeout: 10000, // 10 second timeout
retries: 3, // built-in retry support
});
if (response.data.status !== 'OK') {
throw new Error(`Maps API error: ${response.data.status} - ${response.data.error_message}`);
}
return response.data.results[0];
}
Client-side request queue to prevent rate-limit-induced timeouts:
class MapsRequestQueue {
constructor(rateLimit = 50) { // requests per second
this.queue = [];
this.processing = false;
this.interval = 1000 / rateLimit;
}
enqueue(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
if (!this.processing) this.process();
});
}
async process() {
this.processing = true;
while (this.queue.length > 0) {
const { fn, resolve, reject } = this.queue.shift();
try { resolve(await fn()); } catch (e) { reject(e); }
await new Promise(r => setTimeout(r, this.interval));
}
this.processing = false;
}
}
const queue = new MapsRequestQueue(10); // 10 RPS safe limit
// Usage: const result = await queue.enqueue(() => geocodeWithRetry(geocoder, req));
Step 7: Intercept and Handle Auth Failures Gracefully
For the Maps JavaScript API, always implement the gm_authFailure callback to catch 403s at runtime rather than silently failing:
window.gm_authFailure = function() {
console.error('Google Maps authentication failed. Check your API key, billing, and referrer restrictions.');
document.getElementById('map').innerHTML =
'<div class="map-error">Map unavailable. Please try again later.</div>';
// Optionally: send to your error tracking service
Sentry?.captureMessage('Google Maps gm_authFailure triggered', 'error');
};
Step 8: Audit and Rotate Compromised Keys
If your key was exposed (committed to git, leaked in client-side source, indexed by scrapers), restrict or rotate it immediately.
# Scan git history for leaked API keys
git log --all --full-history -- '*.js' '*.ts' '*.env' '*.json' | \
xargs -I{} git show {} | grep -E 'AIza[0-9A-Za-z_-]{35}'
# Or use truffleHog for a thorough scan
trufflehog git file://. --only-verified
After rotating: update the key in all deployment environments (CI/CD secrets, Kubernetes secrets, .env files, CDN edge configs), then restrict the new key immediately.
Frequently Asked Questions
#!/usr/bin/env bash
# Google Maps API 403 Diagnostic Script
# Usage: GOOGLE_MAPS_API_KEY=AIza... bash diagnose-maps-api.sh
KEY="${GOOGLE_MAPS_API_KEY:-YOUR_API_KEY_HERE}"
BASE="https://maps.googleapis.com"
echo "=== Google Maps API 403 Diagnostic Tool ==="
echo "API Key prefix: ${KEY:0:8}..."
echo ""
# 1. Test Geocoding API (most common REST endpoint)
echo "[1] Testing Geocoding API..."
GEO=$(curl -s "${BASE}/maps/api/geocode/json?address=New+York&key=${KEY}")
STATUS=$(echo "$GEO" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','NO_STATUS'))" 2>/dev/null)
ERROR=$(echo "$GEO" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error_message',''))" 2>/dev/null)
echo " Status: $STATUS"
[ -n "$ERROR" ] && echo " Error: $ERROR"
echo ""
# 2. Test Maps JavaScript API script load (HTTP status only)
echo "[2] Testing Maps JS API script load..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -L \
"${BASE}/maps/api/js?key=${KEY}&libraries=places")
echo " HTTP status: $HTTP_CODE"
[ "$HTTP_CODE" = "403" ] && echo " => 403 on script load: check billing and key validity"
echo ""
# 3. Test with a specific Referer header
echo "[3] Testing with Referer: https://localhost/..."
REF_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Referer: https://localhost/" \
"${BASE}/maps/api/geocode/json?address=Paris&key=${KEY}")
echo " HTTP status with localhost Referer: $REF_CODE"
echo ""
# 4. Check which GCP APIs are enabled (requires gcloud auth)
echo "[4] Checking enabled Maps APIs (requires gcloud auth)..."
if command -v gcloud &>/dev/null; then
PROJECT=$(gcloud config get-value project 2>/dev/null)
if [ -n "$PROJECT" ]; then
echo " Project: $PROJECT"
gcloud services list --enabled --project="$PROJECT" 2>/dev/null | \
grep -E '(maps|geocod|places|direction|distance|elevation|roads)' | \
awk '{print " Enabled: " $1}'
else
echo " No active gcloud project set. Run: gcloud config set project YOUR_PROJECT_ID"
fi
else
echo " gcloud CLI not found. Install from: https://cloud.google.com/sdk"
fi
echo ""
# 5. Measure response time (timeout diagnosis)
echo "[5] Measuring Geocoding API response time..."
TIME=$(curl -s -o /dev/null -w "%{time_total}" \
"${BASE}/maps/api/geocode/json?address=London&key=${KEY}")
echo " Response time: ${TIME}s"
if (( $(echo "$TIME > 5" | bc -l) )); then
echo " WARNING: Response time > 5s — possible rate limiting or network issue"
fi
echo ""
# 6. Summary
echo "=== Diagnosis Summary ==="
case $STATUS in
OK) echo " Key is working correctly." ;;
REQUEST_DENIED) echo " FIX: Enable billing at console.cloud.google.com/billing" ;;
echo " Enable the Geocoding API at console.cloud.google.com/apis/library" ;;
OVER_QUERY_LIMIT) echo " FIX: Implement exponential backoff or request quota increase" ;;
INVALID_REQUEST) echo " FIX: Check request parameters (missing required fields)" ;;
*) echo " Unknown status: $STATUS — check raw response above" ;;
esacError Medic Editorial
The Error Medic Editorial team consists of senior DevOps engineers, SREs, and full-stack developers with hands-on experience operating production systems on GCP, AWS, and Azure. We specialize in API integration troubleshooting, cloud infrastructure debugging, and developer tooling. Our guides are written from real incident postmortems and reviewed against current official documentation.
Sources
- https://developers.google.com/maps/documentation/javascript/error-messages
- https://developers.google.com/maps/faq#troubleshooting
- https://developers.google.com/maps/documentation/javascript/usage-and-billing
- https://cloud.google.com/apis/docs/errors
- https://stackoverflow.com/questions/14294745/google-maps-api-3-request-denied
- https://github.com/googlemaps/google-maps-services-js/issues?q=403+REQUEST_DENIED
- https://developers.google.com/maps/documentation/geocoding/usage-and-billing