Search for "fast Google indexing" and you'll find tutorials telling you to push every new blog post through the Google Indexing API. Google's own documentation says something narrower. This post covers what the API is actually for, the quotas you get, a small Python client that works, and what to use for the pages the API doesn't cover.
What Google says the API is for
From the Indexing API quickstart:
The Indexing API can only be used to crawl pages with either
JobPostingorBroadcastEventembedded in aVideoObject.
That's job listings and livestream video pages: content that appears and expires quickly, where a crawl a week later is useless. The same page warns that every submission goes through spam detection, and that attempts to abuse the API, including using multiple accounts to exceed quotas, "may result in access being revoked."
So if your site is a job board or streams live events, this API is the right tool. If it's a blog, a shop or a SaaS marketing site, it isn't, and the rest of this post still tells you what to use instead.
The quotas
From the quota page, checked on 21 October 2026 (Google last updated the page on 16 October 2026):
| Quota | Default |
|---|---|
publish requests per day per project (URL_UPDATED and URL_DELETED combined) |
200 |
getMetadata requests per minute per project |
180 |
| All requests per minute per project | 380 |
The daily quota resets at midnight Pacific Time. Use of the API is free. Batching lets you combine up to 100 calls in one HTTP request, but each call inside the batch still counts against the 200.
Setup in five steps
- In Google Cloud, create a project and enable the Web Search Indexing API.
- Create a service account and download its JSON key. Store it as a secret, never in the repo.
- In Search Console, open the property, go to Settings > Users and permissions and add the service account's email address as an Owner. Without owner rights, every call returns 403.
- Install the client libraries:
pip install google-auth requests
- Make sure the pages carry valid
JobPostingorBroadcastEventstructured data. Test one in the Rich Results Test before you automate anything.
A small client
# indexing_api.py
import json
import sys
import time
import requests
from google.oauth2 import service_account
from google.auth.transport.requests import Request
SCOPES = ["https://www.googleapis.com/auth/indexing"]
ENDPOINT = "https://indexing.googleapis.com/v3/urlNotifications"
def session(key_file: str) -> requests.Session:
creds = service_account.Credentials.from_service_account_file(
key_file, scopes=SCOPES
)
creds.refresh(Request())
s = requests.Session()
s.headers.update({
"Authorization": f"Bearer {creds.token}",
"Content-Type": "application/json",
})
return s
def publish(s: requests.Session, url: str, deleted: bool = False) -> dict:
body = {"url": url, "type": "URL_DELETED" if deleted else "URL_UPDATED"}
r = s.post(f"{ENDPOINT}:publish", data=json.dumps(body))
if r.status_code == 429:
raise RuntimeError("Daily quota used up. Try again after midnight PT.")
r.raise_for_status()
return r.json()
def metadata(s: requests.Session, url: str) -> dict:
r = s.get(f"{ENDPOINT}/metadata", params={"url": url})
r.raise_for_status()
return r.json()
if __name__ == "__main__":
s = session("service-account.json")
for line in sys.stdin:
url = line.strip()
if not url:
continue
print(url, publish(s, url)["urlNotificationMetadata"]["latestUpdate"])
time.sleep(0.2) # stay well under the per-minute limit
Feed it URLs from your job board's "published" and "expired" events:
psql -Atc "select url from jobs where published_at > now() - interval '1 hour'" \
| python indexing_api.py
For expired listings, call publish(s, url, deleted=True) once the page returns 404 or 410, or once it no longer contains JobPosting markup. Sending URL_DELETED while the page still shows the job only confuses things.
Reading the responses
| Status | Usual cause |
|---|---|
| 200 | Notification accepted. Not the same as indexed. |
| 403 "Permission denied" | Service account isn't an owner of the Search Console property, or the API isn't enabled on the project. |
| 429 | Daily or per-minute quota reached. |
| 400 | Malformed URL or body. Check for a missing scheme or trailing whitespace. |
getMetadata tells you the last time Google received each type of notification for a URL. It does not tell you whether the page is indexed. For that, use the URL Inspection tool or the Search Console API.
Planning around 200 a day
Two hundred notifications a day sounds tight, but for most job boards the real number is smaller than it looks:
- Send
URL_UPDATEDwhen a listing goes live and when a material field changes (salary, location, closing date). Don't send it on every view-counter update. - Send
URL_DELETEDwhen a listing expires. - Queue anything over the quota and send it after the reset, oldest first.
You can see your current quota in the Google API Console. If you consistently need more, check the Indexing API documentation for Google's current process for requesting a higher quota.
What to use for every other page
For pages that aren't job postings or livestreams, these are the tools Google documents:
| Method | Good for | Limit |
|---|---|---|
XML sitemap with accurate lastmod (how to build one Google trusts) |
Every page you want indexed | 50,000 URLs or 50 MB per file |
| Internal links from crawled pages | New pages and updated pages | None, but they need to be real links in the HTML |
| URL Inspection > Request indexing | A few important URLs | A daily quota per property |
| Search Console Sitemaps report | Seeing when Google read your sitemap | One entry per sitemap |
Google's recrawl guide sums it up: use URL Inspection for a few URLs and a sitemap for many, and be patient, since crawling "can take anywhere from a few days to a few weeks."
Checklist
- [ ] The pages contain
JobPostingorBroadcastEventin aVideoObject - [ ] Web Search Indexing API enabled on the Cloud project
- [ ] Service account added as Owner in Search Console
- [ ] Key stored as a secret
- [ ] Notifications sent on publish, material change and expiry only
- [ ] 429 handled with a queue, not a retry loop
- [ ] Indexing verified separately, not inferred from a 200
Written by the IndexerPal team. We build tools for URL and backlink indexing. For regular pages, which the Indexing API doesn't cover, see how IndexerPal handles website indexing.
Top comments (0)