The Google Indexing API lets a site owner tell Google directly that a URL was added, updated or removed, so Google can schedule a fresh crawl. Officially it is only for pages with JobPosting structured data or BroadcastEvent embedded in a VideoObject. The default quota is 200 publish requests per day per Google Cloud project, the calling service account must be an Owner of the Search Console property, and a successful call is a crawl request, never an indexing guarantee.
Most guides skip the second sentence. This one does not, because whether you should use the API at all depends on it.
What the API does and does not do
There are only two real operations:
| Operation | Request | Purpose |
|---|---|---|
| Publish | POST https://indexing.googleapis.com/v3/urlNotifications:publish |
Notify Google that a URL was updated (URL_UPDATED) or removed (URL_DELETED) |
| Get metadata | GET https://indexing.googleapis.com/v3/urlNotifications/metadata?url=... |
See the last notifications you sent for a URL |
What people often get wrong:
- A 200 response means "notification received". It does not mean crawled, and it certainly does not mean indexed.
getMetadatadoes not report index status. It only echoes your own notifications and returns 404 if you never sent one for that URL. To see whether a page is indexed, you need the URL Inspection tool or the URL Inspection API, as described in how to check if a page is indexed.URL_DELETEDis for pages that are really gone. The page should return 404 or 410, or carry anoindex, before you send it.
Who it is officially for
Google's documentation is explicit: the Indexing API can only be used to crawl pages with either JobPosting or BroadcastEvent embedded in a VideoObject. These are short-lived pages where hours matter. A job ad that is filled, or a livestream that starts tonight, is useless if it is crawled next week.
In September 2024 Google added further wording to the docs:
- All submissions go through spam detection.
- Attempts to abuse the API, "including the use of multiple accounts or other means to exceed usage quotas", may result in access being revoked.
- Google's spam policies apply to content submitted through the API.
- The default 200 per day quota is described as being for onboarding and submission testing, with additional approval required beyond that.
The honest position on other content types
The API accepts any URL on a property you own. It does not check for job or broadcast markup at submission time. Many site owners and tools use it for blog posts, products and landing pages, and commonly report that those URLs get crawled quickly.
Here is what you should weigh before doing the same:
- It is unsupported. Google's John Mueller has said repeatedly that the API supports a very limited set of use cases and that people should stick to the documented ones. Google can stop honouring other submissions at any time without notice.
- A crawl is not an index entry. If a page is thin or duplicated, the API gets it crawled sooner and rejected sooner. It does not fix Crawled - currently not indexed.
- Quota evasion is the clearly risky part. Rotating many Google Cloud projects or accounts to push thousands of URLs per day is exactly what the abuse wording describes. The exposure is loss of API access, and your site being treated as a spam source.
- Quota increases are hard to get. Requests go through a form linked from the quota page. Third-party reports indicate approvals have been rare, particularly for sites that are not job boards or livestream platforms.
My view: for job and livestream pages, use it without hesitation. For other content, staying within the default quota on a single project is a low-volume, best-effort crawl hint that you should be ready to lose. It is no substitute for sitemaps, internal links and content Google wants to keep. TurboIndex works this way by default: one project per site, the standard 200 per day, and index status verified separately through the URL Inspection API so you see what actually happened.
Quotas
| Quota | Default |
|---|---|
Publish requests (URL_UPDATED plus URL_DELETED) |
200 per day per project |
| Metadata requests | 180 per minute per project |
| All requests | 380 per minute per project |
The daily counter resets at midnight Pacific Time. The API is free. Google notes that quota may increase or decrease based on document quality. Multiple service accounts inside one project share the same 200. To estimate how long your site would take at this rate, use the indexing time calculator.
Setup
1. Create a project and enable the API
- Open the Google Cloud Console and create a project (or pick an existing one).
- Go to APIs & Services > Library, search for "Web Search Indexing API" and click Enable.
2. Create a service account and key
- Go to IAM & Admin > Service Accounts > Create service account. No project roles are needed.
- Open the account, go to Keys > Add key > Create new key > JSON and download the file.
- Note the
client_email. It looks like[email protected].
The JSON file is a full credential. Keep it out of git, store it as a secret and rotate it if it leaks. If your Google Cloud organisation enforces the policy that disables service account key creation, an admin has to allow it for the project first.
3. Add the service account as an Owner in Search Console
- In Search Console select the property, then Settings > Users and permissions.
- Click Add user, paste the service account email, and set Permission to Owner.
Both Domain properties and URL-prefix properties work. With a URL-prefix property the submitted URL must fall under that exact prefix. This is the step where most setups fail: adding the account as "Full" produces a 403 on every call.
4. Send a notification (Node.js)
// npm install google-auth-library
import { readFileSync } from "node:fs";
import { JWT } from "google-auth-library";
const key = JSON.parse(readFileSync("./service-account.json", "utf8"));
const client = new JWT({
email: key.client_email,
key: key.private_key,
scopes: ["https://www.googleapis.com/auth/indexing"],
});
async function notify(url, type = "URL_UPDATED") {
try {
const res = await client.request({
url: "https://indexing.googleapis.com/v3/urlNotifications:publish",
method: "POST",
data: { url, type },
});
return res.data.urlNotificationMetadata;
} catch (err) {
const status = err.response?.status;
const message = err.response?.data?.error?.message;
if (status === 429) {
// Daily quota used up: stop until midnight Pacific Time.
throw new Error("Quota exhausted: " + message);
}
if (status === 403) {
// Ownership or API not enabled: retrying will not help.
throw new Error("Permission problem: " + message);
}
throw err;
}
}
console.log(await notify("https://www.example.com/jobs/frontend-developer"));
The library signs a JWT with your private key, exchanges it for a one-hour access token and refreshes it automatically. If you load the private key from an environment variable, keep the \n line breaks intact or signing will fail.
5. Batch requests (optional)
You can combine up to 100 calls in one multipart request to https://indexing.googleapis.com/batch. Each part carries its own status in the response. Quota is still counted per URL, so a batch of 100 costs 100 publish requests.
Error codes and what to do
| HTTP | Message (typical) | Cause | Action |
|---|---|---|---|
| 400 | "Missing attribute. 'url' attribute is required." or invalid type |
Malformed body | Send JSON with url and type, and Content-Type: application/json |
| 401 | Invalid or expired credentials | Bad token, clock skew, deleted key | Mint a new token, check server time |
| 403 | "Permission denied. Failed to verify the URL ownership." | Service account is not an Owner of a property covering the URL | Add it as Owner, check protocol and host match |
| 403 | Project is not configured to access this API | Indexing API not enabled in the project that owns the key | Enable it under APIs & Services |
| 404 | Not found (on getMetadata) |
You never sent a notification for this URL | Nothing, this is informational |
| 429 | "Insufficient tokens for quota..." | Per-minute or per-day quota hit | Per minute: back off and retry. Per day: stop until midnight Pacific |
| 5xx | Server error | Temporary problem at Google | Retry with exponential backoff |
Two debugging tips for the ownership 403. First, check you are adding the email from the same JSON key you are signing with, since people often have several projects. Second, call the Search Console API sites.list method as the service account (scope webmasters.readonly). If the property does not come back with permissionLevel: "siteOwner", the Indexing API will refuse you too.
Good practice
- Notify only on real change. New page, meaningful update, or removal. Do not resubmit unchanged URLs on a schedule.
- Check before you submit. Make sure the URL returns 200, has no
noindexand is canonical. The indexability checker does this per URL. Spending quota on redirects and noindexed pages is waste. - Spend quota where it helps. Skip URLs that are already indexed. A URL that has impressions in the Performance report is provably indexed.
- Keep a log of URL, type, time and response, so you never need
getMetadatato know what you sent. - Verify outcomes. Inspect a sample a few days later. If pages are crawled and not indexed, the bottleneck is content, and more notifications will not help.
- Keep the basics. Google recommends sitemaps for broad coverage even when you use the API.
What not to do
- Do not create dozens of Cloud projects to multiply the quota. That is the behaviour Google's abuse notice names.
- Do not send
URL_DELETEDfor pages that still exist just to "refresh" them. - Do not treat the API as a fix for pages Google has already declined to index.
- Do not hand your service account key to a tool you do not trust. The key gives whoever holds it Owner-level API access to your Search Console property.
Alternatives that are fully supported
- XML sitemaps with accurate
lastmod, submitted in Search Console. See the XML sitemap guide. - Request indexing in the URL Inspection tool for individual pages.
- IndexNow for Bing, Yandex, Naver, Seznam and others. Google does not participate, but it takes a minute to set up and you can send URLs with the IndexNow submitter. More in the IndexNow guide.
- Internal links from pages Google already crawls often.
If you run a job board or a livestream site, the Indexing API is the right tool and worth the hour of setup. For everything else, treat it as an optional extra on top of the fundamentals, with clear eyes about what Google has and has not promised.
Frequently asked questions
- Can I use the Google Indexing API for any website?
- Officially no. Google's documentation says the Indexing API can only be used for pages with JobPosting structured data or BroadcastEvent embedded in a VideoObject. The API technically accepts other URLs and many people use it that way, but that use is unsupported and Google can ignore it or revoke access.
- What is the Indexing API quota?
- The default is 200 publish requests per day per Google Cloud project, counting URL_UPDATED and URL_DELETED together, plus 180 metadata requests per minute and 380 total requests per minute. The daily quota resets at midnight Pacific Time. Google describes the 200 as a quota for onboarding and testing.
- Does the Indexing API guarantee that my page gets indexed?
- No. A successful response only means Google received the notification and will schedule a crawl. Whether the page is then indexed depends on the same quality and duplication checks as any other page. Check the actual result with the URL Inspection tool or API.
- Why do I get 403 Permission denied. Failed to verify the URL ownership?
- The service account that signed the request is not an Owner of a Search Console property that contains the URL. Add the service account email under Settings, Users and permissions with the Owner permission. Full user is not enough. Also check that the URL matches the property's protocol and host.
- Is batching a way around the daily quota?
- No. A batch request can combine up to 100 calls into one HTTP request, but each URL in the batch still counts as one publish request against the daily quota. Batching only saves HTTP round trips.