DEV Community

Cover image for The Google Indexing API: what it's for, its quotas, and a working Python client
IndexerPal
IndexerPal

Posted on

The Google Indexing API: what it's for, its quotas, and a working Python client

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 JobPosting or BroadcastEvent embedded in a VideoObject.

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

  1. In Google Cloud, create a project and enable the Web Search Indexing API.
  2. Create a service account and download its JSON key. Store it as a secret, never in the repo.
  3. 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.
  4. Install the client libraries:
pip install google-auth requests
Enter fullscreen mode Exit fullscreen mode
  1. Make sure the pages carry valid JobPosting or BroadcastEvent structured 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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_UPDATED when 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_DELETED when 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 JobPosting or BroadcastEvent in a VideoObject
  • [ ] 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)