Approximate U.S. planning cost (indicative)

There is usually little direct cost at very low volume, but billing can appear in the connected Google Cloud project and automation environment when testing scales or when additional services are enabled.

  • Early testing: often low-to-no explicit API spend, but still run under an account with clear billing ownership.
  • Small production use: monitoring and scheduled jobs may begin to dominate cost over API calls.
  • Larger volume use: quotas and support tooling should be reviewed before any high-frequency notification process.

Cost breakdown

  • API usage and quota handling in Google Cloud projects.
  • BigQuery or data lake usage if you persist notification metadata or audit logs there.
  • Monitoring and alerting for failed submissions.
  • Engineering cost for dedupe, retry policy, and rollback handling.

Before you start

Prepare:

  • A verified Search Console property
  • Owner access to that property
  • A Google Cloud project
  • Node.js or Python installed locally
  • A secure place to store the service account JSON file
  • A small test URL that matches the supported page type

Also confirm the URL you submit is canonical, indexable, live, and accessible to Googlebot.

Check:

  • HTTP 200 response
  • No noindex
  • Not blocked by robots.txt
  • Canonical tag points to itself or the intended canonical
  • Structured data is valid for the supported type
  • Search Console property matches the URL host

Step 1: Enable the Indexing API

In Google Cloud Console:

  1. Select or create the project.
  2. Open APIs & Services.
  3. Open Library.
  4. Search for Indexing API.
  5. Enable it.

Make sure you do this in the same Cloud project where you create the service account.

Step 2: Create a service account

In Google Cloud Console:

  1. Open IAM & Admin.
  2. Open Service Accounts.
  3. Create a service account.
  4. Give it a clear name, such as indexing-api-local.
  5. Copy the service account email.

The email looks like:

indexing-api-local@your-project-id.iam.gserviceaccount.com

Create a JSON key for local development:

  1. Open the service account.
  2. Go to Keys.
  3. Add a key.
  4. Choose JSON.
  5. Save it outside the project repo.

Example safe local path:

D:\secrets\google-indexing-service-account.json

Do not commit this file.

Step 3: Add the service account as a Search Console owner

This is the step most people miss.

The service account must be authorized in Search Console for the property you submit URLs from. For the Indexing API, Google requires owner-level access for the property.

In Search Console:

  1. Open the correct property.
  2. Go to Settings.
  3. Open Users and permissions.
  4. Add the service account email.
  5. Grant owner access.

Use the same property type that matches your URL:

  • Domain property for broad host coverage
  • URL-prefix property for a specific protocol and host

If your site uses https://www.example.com/, do not add access only to http://example.com/.

Step 4: Run a Node.js indexing script

Create a new local folder:

mkdir google-indexing-test
cd google-indexing-test
npm init -y
npm install googleapis

Create index-url.js:

const { google } = require("googleapis");

const keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
const targetUrl = process.argv[2];
const type = process.argv[3] || "URL_UPDATED";

if (!keyFile) {
  console.error("Set GOOGLE_APPLICATION_CREDENTIALS to your service account JSON path.");
  process.exit(1);
}

if (!targetUrl) {
  console.error("Usage: node index-url.js https://example.com/job/123 URL_UPDATED");
  process.exit(1);
}

async function main() {
  const auth = new google.auth.GoogleAuth({
    keyFile,
    scopes: ["https://www.googleapis.com/auth/indexing"],
  });

  const indexing = google.indexing({
    version: "v3",
    auth,
  });

  const response = await indexing.urlNotifications.publish({
    requestBody: {
      url: targetUrl,
      type,
    },
  });

  console.log(JSON.stringify(response.data, null, 2));
}

main().catch((error) => {
  console.error(error.response?.data || error.message);
  process.exit(1);
});

Set credentials and run:

export GOOGLE_APPLICATION_CREDENTIALS="/absolute/path/google-indexing-service-account.json"
node index-url.js "https://example.com/job/123" URL_UPDATED

Windows PowerShell:

$env:GOOGLE_APPLICATION_CREDENTIALS="D:\secrets\google-indexing-service-account.json"
node index-url.js "https://example.com/job/123" URL_UPDATED

Use URL_DELETED only when a page is removed or should be treated as removed:

node index-url.js "https://example.com/job/123" URL_DELETED

Step 5: Run a Python indexing script

Create a virtual environment:

python -m venv .venv

Activate it.

macOS or Linux:

source .venv/bin/activate

Windows PowerShell:

.\.venv\Scripts\Activate.ps1

Install packages:

pip install google-api-python-client google-auth

Create index_url.py:

import json
import os
import sys

from google.oauth2 import service_account
from googleapiclient.discovery import build

SCOPES = ["https://www.googleapis.com/auth/indexing"]

key_file = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
target_url = sys.argv[1] if len(sys.argv) > 1 else None
notification_type = sys.argv[2] if len(sys.argv) > 2 else "URL_UPDATED"

if not key_file:
    raise SystemExit("Set GOOGLE_APPLICATION_CREDENTIALS to your service account JSON path.")

if not target_url:
    raise SystemExit("Usage: python index_url.py https://example.com/job/123 URL_UPDATED")

credentials = service_account.Credentials.from_service_account_file(
    key_file,
    scopes=SCOPES,
)

service = build("indexing", "v3", credentials=credentials)

body = {
    "url": target_url,
    "type": notification_type,
}

response = service.urlNotifications().publish(body=body).execute()
print(json.dumps(response, indent=2))

Run:

export GOOGLE_APPLICATION_CREDENTIALS="/absolute/path/google-indexing-service-account.json"
python index_url.py "https://example.com/job/123" URL_UPDATED

Step 6: Check notification status

The Indexing API also supports checking URL notification metadata.

Node.js:

const response = await indexing.urlNotifications.getMetadata({
  url: targetUrl,
});
console.log(response.data);

Python:

response = service.urlNotifications().getMetadata(url=target_url).execute()
print(response)

This confirms notification metadata. It does not prove the URL is indexed or ranking.

Validation and rollback guidance

Validation sequence:

  1. Confirm API is enabled in one project and credentials are created in that same project.
  2. Verify Search Console owner access for the same exact property.
  3. Run one supported URL as URL_UPDATED.
  4. Confirm metadata exists and logs show expected request IDs.
  5. Run a reverse URL_DELETED only for truly removed items.

Rollback plan:

  • Stop scheduled jobs before changing payload format or URLs.
  • Keep a list of submitted URLs and their types so you can reverse decisions in a controlled maintenance window.
  • Disable the service account key or delete key material if an unauthorized call pattern appears.
  • Restore last good config and credential mapping before rerunning.

Quotas and batching

Google applies Indexing API quotas. Typical default projects have limited daily publish quota, and the API is not designed as a bulk crawler replacement.

Good local script behavior:

  • Submit only supported URLs.
  • Deduplicate URLs before calling the API.
  • Retry only on temporary errors.
  • Do not loop indefinitely.
  • Log every submitted URL.
  • Separate URL_UPDATED and URL_DELETED.
  • Keep a local submission history.

If your business case requires higher quota, request it through Google Cloud only after the implementation is clean and the page types are supported.

Common errors

403 Permission denied

Check:

  • Indexing API is enabled in the Cloud project.
  • The script uses the correct JSON key.
  • The service account email is added to Search Console.
  • The service account has owner access for the exact property.
  • The URL belongs to that property.

400 Bad request

Check:

  • URL is fully qualified with https://.
  • Notification type is URL_UPDATED or URL_DELETED.
  • JSON body is valid.
  • The URL is not malformed.

429 Quota exceeded

Stop the script and inspect submission volume. Add deduplication, backoff, and limits before trying again.

API enabled but still failing

Wait a short period after enabling the API, then retry. Also confirm the credentials were created in the same Cloud project where the API was enabled.

Security checklist

  • Store the JSON key outside the repo.
  • Add credential filenames to .gitignore.
  • Do not paste the JSON into issue trackers or chat.
  • Do not ship the key to browsers.
  • Rotate keys if they are exposed.
  • Use one service account per automation purpose.
  • Keep local logs free of access tokens.

Questions to ask

  • Which exact page templates are supported by policy, and who validates that they remain valid after template changes?
  • What happens if a publish call succeeds but Google does not update quickly?
  • How many URLs can we send per day with current quota, and what is the escalation path?
  • Which rollback action is expected after a burst of accidental URL_UPDATED calls?

Red flags

  • Using the API as a bulk discovery tool or a ranking substitute.
  • Missing Search Console owner grant for the exact target property.
  • Sending URL_DELETED for temporary redirects or transient slugs.
  • No dedupe history, causing noisy repeated notifications.
  • Running scripts against different projects between local tests and deployment.

Bottom line

The Google Indexing API is powerful, but narrow. Use it for the page types Google officially supports, not as a universal SEO shortcut.

For supported pages, the flow is straightforward: enable the API, create a service account, add it as a Search Console owner, and send URL_UPDATED or URL_DELETED notifications from a local script.

Sources