Google Indexing API — who it is for and how to set it up [2026]
The Indexing API is Google's interface for notifying it about added, updated and removed pages, but only for job postings (
JobPosting) or livestreams (BroadcastEventin aVideoObject). If you run a job board or a livestreaming site, this tutorial walks you through setup and gives you a ready-to-use script. If you run a blog, a store or a company website, the Indexing API is not for you, and below I cover what to use instead.
TL;DR — Google Indexing API in 5 points
| # | What | Details |
|---|---|---|
| 1. Scope | Only pages with JobPosting or BroadcastEvent in a VideoObject | per Google's documentation |
| 2. Setup | Google Cloud project + Indexing API enabled + service account | JSON key |
| 3. Search Console | Service account as owner of the property | without it the API rejects submissions |
| 4. Quota | 200 publish requests per day per project | resets at midnight Pacific Time |
| 5. Endpoint | urlNotifications.publish with {url, type: URL_UPDATED} | OAuth scope indexing |
What the Indexing API is and what it is for
The Indexing API is Google's programmatic interface that lets a site owner tell Google a page has been added, updated or removed, so Google can schedule it for a fresh crawl. It also lets you check when Google last received a notification for a given URL, and combine up to 100 calls into a single HTTP request.
Scope according to Google's documentation
Google's documentation says it plainly:
"The Indexing API can only be used to crawl pages with either
JobPostingorBroadcastEventembedded in aVideoObject."
The reasoning is practical: job postings and livestreams are short-lived, so search results need to reflect quickly when they appear and disappear. The same documentation warns that all submissions undergo spam detection, and that attempts to abuse the API, including using multiple accounts to exceed quotas, may result in access being revoked.
When does the Indexing API make sense?
| Situation | Use it? | Why |
|---|---|---|
Job board (JobPosting) | ✅ YES | Within the documented scope |
Livestreaming site (BroadcastEvent) | ✅ YES | Within the documented scope |
| A job posting expired or was removed | ✅ YES | URL_DELETED tells Google the page is gone |
| Blog, store, service page | ❌ NO | Outside the API's scope; use a sitemap and URL Inspection |
| Migration or new domain without job postings | ❌ NO | Outside the API's scope; see how to speed up indexing |
Step-by-step setup
Step 1: Google Cloud Console — project + API
- Open console.cloud.google.com
- Create a new project (e.g.,
seo-indexing-tools) - In the menu choose APIs & Services → Library
- Search for
Indexing APIand click Enable - You'll be redirected to the API dashboard, which confirms it is enabled
Step 2: Service account + JSON key
- Open the Service accounts page in Google Cloud Console and select the project
- Click Create Service Account, enter a name (e.g.,
seo-indexing-bot) and a description - The service account permissions section is optional; continue
- In the Create key section choose JSON and click Create
- The JSON file downloads automatically and is the only copy of the key. Store it securely and never commit it to a repository
You'll find the service account email in the client_email field of the downloaded JSON file. It has the format name@project-id.iam.gserviceaccount.com; you'll use it in step 3.
Step 3: Google Search Console — service account as owner
- Verify site ownership in Search Console with any supported method (a Domain property or a URL-prefix property)
- Open Search Console and select the property you verified
- Add the service account email from step 2 as a (delegated) owner
- Confirm
Google's documentation requires owner status; lower permission levels are not enough to send submissions.
Step 4: Test the setup — first request
from google.oauth2 import service_account
from googleapiclient.discovery import build
CREDENTIALS_PATH = "/path/to/gsc-service-account.json"
SCOPES = ["https://www.googleapis.com/auth/indexing"]
credentials = service_account.Credentials.from_service_account_file(
CREDENTIALS_PATH, scopes=SCOPES
)
service = build("indexing", "v3", credentials=credentials)
response = service.urlNotifications().publish(body={
"url": "https://your-domain.com/jobs/123",
"type": "URL_UPDATED"
}).execute()
print(response)
Expected response:
{
"urlNotificationMetadata": {
"url": "https://your-domain.com/jobs/123",
"latestUpdate": {
"url": "https://your-domain.com/jobs/123",
"type": "URL_UPDATED",
"notifyTime": "2026-05-03T20:14:32.123456Z"
}
}
}
The response confirms that the notification was received. It does not mean the page has already been crawled or indexed.
Most common errors:
403 Permission denied→ the service account is not an owner of the Search Console property (step 3)403saying the API has not been used in the project → the Indexing API was not enabled in Google Cloud (step 1)400 Invalid argument→ invalid URL (it must be a full URL including the protocol)
Python script for bulk submissions
The script submits URLs from a text file, respects the daily quota, retries on 5xx errors and writes a JSON log. It is meant for job posting or livestream pages.
#!/usr/bin/env python3
"""Bulk Indexing API submissions — template for the default 200/day quota."""
import json
import sys
import time
from datetime import date
from pathlib import Path
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
CREDENTIALS_PATH = Path("/path/to/gsc-service-account.json")
SCOPES = ["https://www.googleapis.com/auth/indexing"]
RATE_LIMIT_SLEEP = 0.5 # 0.5 s between requests = ~120 req/min
DAILY_QUOTA = 200
def submit_url(service, url: str) -> dict:
"""Submit 1 URL with retry on 5xx."""
body = {"url": url, "type": "URL_UPDATED"}
for attempt in range(5):
try:
return service.urlNotifications().publish(body=body).execute()
except HttpError as e:
if e.resp.status >= 500:
time.sleep(2 ** attempt)
continue
raise
raise RuntimeError(f"Failed after 5 retries: {url}")
def main(urls_file: str) -> None:
urls = Path(urls_file).read_text().splitlines()
urls = [u.strip() for u in urls if u.strip() and not u.startswith("#")]
if len(urls) > DAILY_QUOTA:
print(f"WARN: {len(urls)} URLs > daily quota {DAILY_QUOTA}. Truncating.")
urls = urls[:DAILY_QUOTA]
creds = service_account.Credentials.from_service_account_file(
CREDENTIALS_PATH, scopes=SCOPES
)
service = build("indexing", "v3", credentials=creds, cache_discovery=False)
results = []
for i, url in enumerate(urls, 1):
print(f"[{i}/{len(urls)}] {url}")
try:
response = submit_url(service, url)
results.append({"url": url, "status": "OK", "response": response})
except Exception as e:
results.append({"url": url, "status": "ERROR", "error": str(e)})
time.sleep(RATE_LIMIT_SLEEP)
log_path = Path(f"indexing-log-{date.today().isoformat()}.json")
log_path.write_text(json.dumps(results, indent=2))
ok_count = sum(1 for r in results if r["status"] == "OK")
print(f"\nDone: {ok_count}/{len(urls)} OK. Log: {log_path}")
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "urls.txt")
urls.txt file format
https://your-domain.com/jobs/123
https://your-domain.com/jobs/124
# comments are ignored
https://your-domain.com/live/conference-2026
Run
python3 indexing_bulk.py urls.txt
The output is a JSON log of every request and response. You can load it into a dashboard or simply check which URLs returned an error.
What the Indexing API won't fix
A notification asks Google to crawl a page. It does not guarantee indexing or rankings.
Situations where the Indexing API won't help
- The page has
noindexin meta robots — Google will crawl it but won't index it. First fix<meta name="robots">. - Robots.txt blocks the URL — Googlebot won't crawl it at all. Check robots.txt.
- The page has little valuable content — Google may crawl it and decide it isn't worth indexing.
- A duplicate without a clear canonical — Google will choose the version to index itself. Configure
<link rel="canonical">. - The page is neither a job posting nor a livestream — then the API is the wrong tool altogether.
What to use for other pages
| Tool | Purpose | When |
|---|---|---|
| Sitemap.xml | A map of URLs to crawl | Every site; the basic signal for new and changed pages |
| URL Inspection in Search Console | Diagnosing a single page and requesting indexing | Individual important pages |
| Internal linking | A path to new pages | Every new page should be linked from one that is already indexed |
| IndexNow (Bing/Yandex) | Notifications for search engines that support the protocol | A complement on the Bing side |
FAQ — most common questions
Can I use the Indexing API for a client's site?
Yes, if the client's site contains job postings or livestreams. The service account must be an owner of the client's Search Console property, which requires the client's consent. Keep in mind that the 200-a-day quota applies to the Google Cloud project, so several clients in one project share the same quota.
What is the URL_DELETED type?
urlNotifications.publish also accepts type: URL_DELETED. It tells Google that the page has been removed from your server, so Google can remove it from the index and stop crawling it. For a job board this is the typical case: a posting has expired, the page returns 404 or 410, and the API speeds up its removal from results.
How do I check whether Google received the notification?
The urlNotifications.getMetadata endpoint returns the time of the latest notification for a given URL (latestUpdate.notifyTime). That confirms the signal was received, not that the page was indexed. You can check indexing status in Search Console with the URL Inspection tool.
Can I submit the same URL every week to improve its ranking?
No. A notification makes sense when a page is new, has changed significantly or has been removed. Repeating submissions without changes uses up the quota and does not affect rankings.
What's next
- Check whether your pages are in scope — if you have no job postings or livestreams, start with a sitemap and internal linking.
- Job board — complete steps 1-3 and hook the script into publishing and expiry (
URL_UPDATEDwhen a posting is added,URL_DELETEDwhen it is removed). - Read next:
Get an SEO audit
Your sitemap lists many URLs, but only a few of them are in Google's index? In our free SEO audit we check indexing, robots.txt, the sitemap, redirects and canonicalization. You get a prioritized report within 48 business hours.