Google indexing API reference
Everything the bot does, over HTTP. Send URLs when a page goes live, poll the task, read what was accepted and what was filtered. Same balance, same checks, $0.01 per accepted URL.
Quick start
- Get a key. Open @IndexForgeBot and press 🔑 API. The key is shown once, so save it.
- Send URLs.
POST /v1/submitwith a JSON array of up to 10,000 URLs. You get a task id right away. - Poll the task.
GET /v1/tasks/{id}every 10 seconds or so, until the status isdone,partialorfailed. - Read the result. Accepted, rejected with reasons, charged and refunded, all in one object.
$ curl -X POST https://api.indexforge.help/v1/submit \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"urls": [ "https://site.com/page-1", "https://site.com/page-2"]}'
import requests, time API = "https://api.indexforge.help" H = {"Authorization": "Bearer YOUR_KEY"} task = requests.post(API + "/v1/submit", headers=H, json={"urls": urls}).json()["task"] while task["status"] in ("queued", "checking", "submitting"): time.sleep(10) task = requests.get(API + "/v1/tasks/" + task["id"], headers=H).json()["task"] print(task["accepted"], "accepted", task.get("rejected", 0), "rejected")
const API = "https://api.indexforge.help"; const H = { Authorization: "Bearer YOUR_KEY", "Content-Type": "application/json" }; let { task } = await (await fetch(API + "/v1/submit", { method: "POST", headers: H, body: JSON.stringify({ urls }) })).json(); while (["queued", "checking", "submitting"].includes(task.status)) { await new Promise(r => setTimeout(r, 10000)); ({ task } = await (await fetch(API + "/v1/tasks/" + task.id, { headers: H })).json()); } console.log(task.accepted, "accepted");
$ch = curl_init("https://api.indexforge.help/v1/submit"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_KEY", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["urls" => $urls]), ]); $task = json_decode(curl_exec($ch), true)["task"];
202 Accepted · 184 ms # 202 · task created { "ok": true, "task": { "id": "3f9c2a7d1b6e4c80", "status": "queued", "urls": 2 } }
Base URL and authentication
All requests go to https://api.indexforge.help. Bodies and responses are JSON in UTF-8, and every response has an ok field.
Pass your key in either header:
Authorization: Bearer YOUR_KEY
X-API-Key: YOUR_KEY
- The key is issued in the bot with the 🔑 API button and shown once. Store it like a password.
- Generating a new key immediately disables the old one. Do that if a key leaks.
- The API and the bot share one balance: top up in the bot, spend from either.
POST /v1/submit
Creates a task: checks the URLs the way Googlebot sees them, then sends the ones that pass for indexing.
| Field | Type | Description |
|---|---|---|
urls | array, required | Up to 10,000 http(s) URLs. Duplicates and non-http strings are dropped. |
check | bool, default true | Run the precheck. With false the check is skipped and every URL is charged. |
{ "urls": ["https://site.com/page-1", "https://site.com/page-2"], "check": true }
Response 202 Accepted with the new task:
{ "ok": true,
"task": { "id": "3f9c2a7d1b6e4c80", "status": "queued",
"created": 1760263200, "urls": 2 } }
With check: true you need at least 1 credit to start. After the check, credits are reserved only for URLs that passed. Accepted URLs are charged, the rest are refunded automatically. With check: false your balance must cover every URL in the request. The request body is limited to 5 MB.
GET /v1/tasks/{id}
Status and counters for one task. Tasks are visible only to the key's owner.
| Field | Description |
|---|---|
id | Task id |
status | queued, checking, submitting, done, partial or failed |
created · finished | Unix timestamps. finished appears when the task ends |
urls | URLs in the task after deduplication |
checked | URLs checked |
rejected | URLs filtered out by the check |
rejected_urls | Filtered URLs grouped by reason, up to 200 per reason |
accepted | URLs accepted for indexing |
charged · refunded | Credits charged and returned |
error | Short code when something went wrong, see statuses |
{ "ok": true, "task": {
"id": "3f9c2a7d1b6e4c80", "status": "done",
"created": 1760263200, "finished": 1760263295,
"urls": 3, "checked": 3, "rejected": 1,
"rejected_urls": { "404": ["https://site.com/old"] },
"accepted": 2, "charged": 2, "refunded": 0 } }
Reasons in rejected_urls:
| Key | Meaning |
|---|---|
404 | The page returned 404 or 410 |
4xx | Another client error, such as 401 or 403 |
5xx | Server error |
redirect | The URL redirects. Send the final address instead |
noindex | noindex in meta robots, meta googlebot or the X-Robots-Tag header |
robots | Blocked for Googlebot in robots.txt |
canonical | The canonical points to another URL |
error | The page didn't open: timeout, DNS or connection error, invalid URL |
GET /v1/tasks
Your recent tasks, newest first. Optional limit from 1 to 100, default 20.
GET /v1/tasks?limit=20
{ "ok": true, "tasks": [ { "id": "3f9c2a7d1b6e4c80", "status": "done", … }, … ] }
GET /v1/balance
Credits left on the account and the current price.
| Field | Description |
|---|---|
balance | Credits on the account. One credit is one URL |
unlimited | true for accounts without a balance limit, then balance is null |
price_per_url_usd | Price of one URL in US dollars |
{ "ok": true, "balance": 1250, "unlimited": false, "price_per_url_usd": 0.01 }
Task statuses
queuedCreated, waiting its turn.checkingEach URL is fetched like Googlebot and filtered.submittingURLs that passed are being sent.doneAll URLs that passed were accepted.A finished task ends in one of three states:
done: everything that passed the check was accepted. If nothing passed, the task is alsodonewithaccepted: 0and nothing charged.partial: some URLs were accepted, credits for the rest were refunded.failed: nothing was sent and reserved credits were refunded.
The error field explains a partial or failed result:
| error | Meaning |
|---|---|
insufficient_credits | Not enough credits for the URLs that passed the check |
service_unavailable | Submission failed temporarily. Unaccepted credits were refunded, try again later |
restarted | The task was interrupted by a service restart. Reserved credits are returned |
internal_error | Unexpected error on our side |
Errors
Errors come back with an HTTP status and a JSON body, so your code can tell a bad request from an empty balance:
{ "ok": false, "error": "insufficient_credits", "message": "Balance is 0. Top up in the bot." }
| HTTP | error | When |
|---|---|---|
| 400 | bad_json | The body isn't valid JSON |
| 400 | no_urls | urls is missing, empty or has no valid http(s) URLs |
| 400 | too_many_urls | More than 10,000 URLs in one request |
| 401 | unauthorized | The key is missing, wrong or was replaced by a new one |
| 402 | insufficient_credits | Balance is 0, or with check: false it doesn't cover all URLs |
| 403 | forbidden | Access to the API is closed for this account |
| 404 | not_found | Unknown endpoint, or a task that doesn't exist or isn't yours |
| 413 | too_large | The request body is over 5 MB |
| 429 | rate_limited | More than 60 requests per minute |
| 429 | too_many_tasks | 3 of your tasks are already running |
| 500 | internal_error | Unexpected error on our side. Retry a bit later |
Limits
Indexing usually takes 1–3 days after a task finishes. Price: $0.01 per accepted URL, the same as in the bot.
API questions
Is this the official Google Indexing API?
No. It's our own API that runs the same checks and submission as the bot, for any kind of page. Google's own Indexing API is officially meant for job posting and livestream pages. Our guide on the Indexing API vs indexing services explains the difference.
Do I need a separate balance for the API?
No. The API and the bot share one balance. Top up in the bot and use the credits from either.
Do rejected URLs cost anything?
No. They are listed in rejected_urls by reason and never charged, unless you send the task with check: false.
How often should I poll a task?
Every 10 seconds is plenty. Large tasks take longer to check, and polling more often only uses up your 60 requests per minute.
I lost my key. What now?
Press 🔑 API in the bot and generate a new key. The old one stops working at that moment.