Google Search Console Indexing API — Submit URLs and Check Index Status
REST API to submit URLs to Google for indexing and check their current crawl/index status. Wraps the GSC Indexing API with auth handling, rate-limit smoothing, and auto-mirroring to IndexNow.
Submit URLs to Google for indexing and check their current status. Auto-fires after every publish if your workspace has it enabled — but also callable directly for any URL you want indexed.
Conceptual overview
Two things:
- Submit a URL — tell Google "please look at this." Useful for new posts, refreshed posts, or pages Google hasn't picked up.
- Check current status — is this URL indexed? Crawled? Discovered? Excluded?
You connect your Google Search Console account once (per workspace, per verified domain) and then call the two endpoints. CitationBench handles the auth, rotation, and rate-limit smoothing under the hood.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/indexing/gsc-index | Submit one or more URLs |
| GET | /v1/indexing/gsc-index | Get index status for URL(s); list recent submissions |
| CRUD | /v1/indexing/gsc-index/config | Connect, configure, disconnect GSC |
POST /v1/indexing/gsc-index
Submit one URL or many. The shape mirrors IndexNow for consistency — pass urls either as a single string or an array.
Request
POST /v1/indexing/gsc-index HTTP/1.1
Host: api.citationbench.com
Authorization: Bearer sk_live_***
X-Workspace-Id: ws_acme
Content-Type: application/json
Idempotency-Key: 7f3a1b18-7d8b-4f3e-9c4b-2c1a3e0a9b8f
{
"urls": [
"https://acme.com/blog/engineering-team-capacity-tracking",
"https://acme.com/blog/sprint-planning-patterns"
],
"alsoIndexNow": true
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
urls | string | string[] | yes | — | One or many URLs on your verified domain |
alsoIndexNow | boolean | no | true | Mirror the submission to IndexNow (Bing, Yandex, partners) |
Response
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"submitted": [
{
"url": "https://acme.com/blog/engineering-team-capacity-tracking",
"submissionId": "sub_01HVZ...",
"state": "ACCEPTED"
},
{
"url": "https://acme.com/blog/sprint-planning-patterns",
"submissionId": "sub_01HVZ...",
"state": "ACCEPTED"
}
],
"alsoSubmittedToIndexNow": true
}state on submission can be ACCEPTED (Google received the request) or REJECTED (URL not on a verified domain, malformed, etc.). The eventual index status comes back from the GET endpoint.
GET /v1/indexing/gsc-index
Use this endpoint two ways:
1. Check the status of a specific URL
curl -G "https://api.citationbench.com/v1/indexing/gsc-index" \
-H "Authorization: Bearer sk_live_***" \
--data-urlencode "url=https://acme.com/blog/engineering-team-capacity-tracking"{
"url": "https://acme.com/blog/engineering-team-capacity-tracking",
"indexState": "INDEXED",
"canonicalUrl": "https://acme.com/blog/engineering-team-capacity-tracking",
"lastSubmittedAt": "2026-05-24T09:14:23Z",
"lastCheckedAt": "2026-05-25T03:42:11Z",
"submissions": [
{
"submissionId": "sub_***",
"submittedAt": "2026-05-24T09:14:23Z",
"outcome": "INDEXED"
}
]
}indexState values: INDEXED, NOT_INDEXED, DISCOVERED_NOT_INDEXED, CRAWLED_NOT_INDEXED, EXCLUDED, UNKNOWN.
2. List recent submissions
curl -G "https://api.citationbench.com/v1/indexing/gsc-index" \
-H "Authorization: Bearer sk_live_***" \
--data-urlencode "since=2026-05-01T00:00:00Z" \
--data-urlencode "indexState=INDEXED,NOT_INDEXED" \
--data-urlencode "limit=50"{
"data": [
{
"url": "https://acme.com/blog/post-a",
"indexState": "INDEXED",
"lastSubmittedAt": "...",
"lastCheckedAt": "..."
},
{
"url": "https://acme.com/blog/post-b",
"indexState": "NOT_INDEXED",
"lastSubmittedAt": "...",
"lastCheckedAt": "..."
}
],
"nextCursor": null,
"total": 47
}| Param | Notes |
|---|---|
url | Exact URL match |
urlContains | Substring match |
indexState | csv: INDEXED, NOT_INDEXED, DISCOVERED_NOT_INDEXED, CRAWLED_NOT_INDEXED, EXCLUDED, UNKNOWN |
since / until | ISO timestamps for lastSubmittedAt |
limit, cursor | Pagination |
CRUD: /v1/indexing/gsc-index/config
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/indexing/gsc-index/config | Get current config |
| POST | /v1/indexing/gsc-index/config | Connect GSC (OAuth flow returns a redirect URL) |
| PATCH | /v1/indexing/gsc-index/config | Update settings |
| DELETE | /v1/indexing/gsc-index/config | Disconnect GSC |
Config shape
{
"id": "gscc_***",
"verifiedDomains": ["acme.com", "blog.acme.com"],
"autoQueueOnPublish": true,
"alsoIndexNow": true,
"paused": false,
"connectedAt": "2026-02-01T...",
"updatedAt": "2026-05-24T00:00:00Z"
}autoQueueOnPublish: true— everyproduce.publishautomatically submits to GSC.alsoIndexNow: true— the default behavior ofPOST /v1/indexing/gsc-indexmirrors submissions to IndexNow.paused: true— pause submissions without disconnecting (useful during reorgs).
MCP
> Submit my latest 5 blog posts to Google for indexing.Claude calls produce.blog_post.list then indexing.gsc.submit with the URLs.
> Is acme.com/blog/foo indexed?Claude calls indexing.gsc.status with the URL.
> Show me all my pages from May that are CRAWLED_NOT_INDEXED.Claude calls indexing.gsc.list filtered by indexState and since.
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | URL malformed or empty |
| 403 | gsc_not_connected | The workspace has no GSC connection — POST /config to connect |
| 403 | domain_not_verified | URL is on a domain that isn't in the connected GSC's verifiedDomains |
| 503 | gsc_unavailable | Temporary upstream failure; retry with exponential backoff |
Cost
| Action | Credits |
|---|---|
POST /v1/indexing/gsc-index (per URL) | 1 |
GET /v1/indexing/gsc-index | free |
| Mirrored IndexNow submission | included |
Auto-submit from publish doesn't double-charge; the publish credit covers the indexing request.
Use cases (string things together)
A. Auto-index everything you publish
# One-time setup
curl -X PATCH .../v1/indexing/gsc-index/config \
-d '{ "autoQueueOnPublish": true, "alsoIndexNow": true }'
# Every publish now also submits to GSC and IndexNow
curl -X POST .../v1/produce/blog-post/bp_***/publish \
-d '{ "platformConfigId": "pfm_wordpress-main" }'B. Backfill indexing across your existing content library
# Find all published posts
URLS=$(curl -sf -G .../v1/produce/blog-post \
--data-urlencode "status=PUBLISHED" \
--data-urlencode "limit=500" \
| jq -c '.data[].publishedUrl' | jq -s .)
# One bulk submission
curl -X POST .../v1/indexing/gsc-index -d "{ \"urls\": $URLS }"C. Alert me when something fails to index
# Register a webhook
curl -X POST .../v1/webhooks -d '{
"url": "https://hooks.our-portal.com/gsc",
"events": ["indexing.url.not_indexed", "indexing.url.excluded"]
}'D. Agency-wide indexing dashboard
curl -X POST .../v1/workspaces/bulk-action \
-H "Authorization: Bearer sk_live_agency_***" \
-d '{
"action": "indexing.gsc.list",
"workspaces": "all",
"config": { "indexState": "NOT_INDEXED,CRAWLED_NOT_INDEXED", "since": "2026-05-01T00:00:00Z" }
}'Returns one block per workspace — surfaced in the agency's portfolio dashboard.
Related
- API: Indexing · indexnow (peer endpoint; same shape)
- API: Production · publish (auto-triggers this on success when enabled)
- Playbook: Generate 100 landing pages overnight (auto-indexes each one)
- Playbook: Connect GSC + Ahrefs + DataForSEO
Publish
REST API for publishing blog posts and landing pages to connected CMS platforms. Supports WordPress, Wisp, Ghost, Webflow, and custom REST hooks with approval gating and auto-indexing on success.
IndexNow
REST API for submitting URLs via the IndexNow protocol to Bing, Yandex, Naver, and other partner search engines. Faster than waiting for crawlers, with key.txt publication handled automatically.