The API gives you everything the web app does. Base URL:
https://indexmysite.appRequests and responses use JSON. The OpenAPI document describes every route in a machine-readable form.
Authentication
Create an API key in the app under Settings, then Connections. Send it as a bearer token on every request:
curl https://indexmysite.app/api/credits \
-H "Authorization: Bearer $INDEXMYSITE_API_KEY"Treat API keys as secrets. You can revoke a key at any time from the same settings page.
Create a job
POST /api/indexing/jobs
{
"urls": ["https://example.com/new-post", "https://example.com/pricing"],
"name": "September launch"
}| Field | Type | Notes |
|---|---|---|
urls |
string[] |
Required. 1 to 2000 public URLs. Duplicates are submitted once. |
name |
string |
Optional label shown in the app and in job lists. |
projectId |
string |
Optional. Files the job under one of your projects. Omit to leave it unassigned. |
fast |
boolean |
Optional, default false. For time-sensitive pages: faster indexing in Google, usually within 5 to 10 minutes. Adds 1 credit per sent URL. |
bing |
boolean |
Optional, default false. Also sends each URL to Bing. Adds 1 credit per sent URL. Bing results are not checked or reported. |
Each URL is checked before it is sent, and only sent URLs use credits. A sent
URL costs 1 credit, plus 1 with fast and plus 1 with bing; the job
returns this as creditsPerUrl, with the choices in options. If Google has
not indexed a URL after the final check, 1 credit comes back. Fast and
Bing credits are not returned. If Bing is unavailable when the URLs are sent,
each Bing credit is returned straight away and counted in
bingCreditsReturned. Skipped URLs (skipped, with a
skipReason) are not sent or charged by default; URLs with a content warning
wait as needs_review until you approve them (see below). If your balance
runs out part way through a job, the URLs already sent keep running. Only the
remaining URLs wait with the status awaiting_credits until you buy more
credits or, with auto reload on, we top up automatically and send them.
While new submissions are paused for maintenance the route returns 503
with { "error": "submissions_paused" }; retries with an existing
Idempotency-Key and all read routes keep working.
Send an Idempotency-Key header with a unique value to make retries safe. A
repeated request with the same key returns the original job instead of
creating and charging a new one.
curl -X POST https://indexmysite.app/api/indexing/jobs \
-H "Authorization: Bearer $INDEXMYSITE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: launch-2026-09-17" \
-d '{"urls":["https://example.com/new-post"],"name":"September launch"}'The response is 201 Created with a job object: its id, name,
createdAt, total, a counts map of URLs per status, a urls array
with the initial status of each URL, and guidance (see below) explaining
what happens next and when. A repeated idempotent request returns the same
job.
What happens next
Every job object from this route and from GET /api/indexing/jobs/:id
includes a guidance object:
{
"phase": "running",
"total": 549,
"sent": 125,
"running": 125,
"reported": 0,
"starting": 0,
"awaitingConfirmation": 0,
"waitingForCredits": 424,
"timing": {
"firstCheckAfterHours": 24,
"additionalSearchAroundDays": [3, 7],
"fullReportAroundDay": 5,
"finalChecksAroundDay": 14,
"startsWhen": "url_sent"
},
"message": "125 of 549 URLs have been sent and their checks continue. Only the 424 URLs waiting for credits need more credits. Each sent URL gets its first check 24 hours after it is sent, a full report around day 5 and final checks around day 14. Further searches around days 3 and 7 only run for URLs not already found or confirmed indexed."
}| Field | Meaning |
|---|---|
phase |
waiting_for_credits (nothing sent, every remaining URL needs credits), starting (no URL confirmed as sent yet), running (at least one URL confirmed as sent) or final (every URL has a final result). |
sent, running, reported |
URLs confirmed as sent for processing; how many of them still have checks to come; how many have at least one report. |
starting |
URLs waiting to be sent: just received, funded, or being submitted. |
awaitingConfirmation |
URLs handed to processing whose submission is not confirmed yet (submittedAt is null). Their timing has not started. |
waitingForCredits |
URLs not sent because the balance ran out. |
timing |
The check schedule: first Google search after 24 hours, additionalSearchAroundDays: [3, 7] until found or confirmed indexed, full report around day 5, final checks around day 14. It starts when each URL is sent, not when the job is created. |
message |
A plain-English summary you can show as is. It never promises a day-5 report for URLs that already have one, and says nothing about timing once every URL is final. |
The schedule describes when we check and report. It is not a promise that a page is indexed by then. Additional searches stop after an exact-URL match or a report confirming indexing. Reports and confirmed credit returns follow their own schedule; a search without a match does not trigger a refund.
List jobs
GET /api/indexing/jobs
Returns { "jobs": [...] }, newest first. Each job has its id, name,
createdAt, total, a counts map of URLs per status, and
backgroundPending, the number of URLs whose scheduled checks are still to
come. Continue polling while backgroundPending is above zero, even when every
URL already shows indexed.
Get a job
GET /api/indexing/jobs/:id
Returns { "job": {...} } with every URL and its status, plus the same
guidance object as the create route.
| Status | Meaning |
|---|---|
checking_page |
Being checked before sending. No credit is reserved. |
needs_review |
A content warning waits for your approval. Nothing is charged until you approve. |
skipped |
Not sent and not charged. skipReason says why. A crawler block can be explicitly submitted without checks. |
queued |
Checks passed; a credit is about to be reserved. |
awaiting_credits |
Not sent yet; waiting for credits. Buy more or turn on auto reload to send it. |
funded |
A credit is reserved; waiting to be sent. |
submitting |
Being sent for indexing. |
submitted |
Sent for indexing. |
checking |
Being checked for indexing. |
not_indexed |
Not indexed at the last check. Checks continue until the final check around day 14. |
indexed |
Indexed. Set by an exact match at a scheduled Google search or by a report confirming indexing. |
refunded |
Confirmed as not indexed after the final check (around day 14); the credit was returned. Final. |
failed |
Could not be processed; no credit used. Final. |
Each URL also has submittedAt, checkedAt (the time of its latest
scheduled report, null before day 5), indexedAt (when it was first
confirmed indexed, null otherwise) and searchVisibility, which is
{ "foundAt": ... } for a positive exact-URL observation and null
otherwise. foundAt dates that positive observation. searchCheckedAt is
the latest completed Google search timestamp, whether or not it found the
page, and is null until a search completes. A failed search does not replace
the previous completed timestamp. A search without a match does not mean the
page is missing from Google. A URL found at any scheduled search shows indexed while its
scheduled reports continue. A later report replaces the early match, so a
URL can return to not_indexed; refunded, failed and skipped are
always shown.
Pre-submission fields
Skipped and reviewed URLs carry a few more fields:
| Field | Meaning |
|---|---|
skipReason |
One of duplicate_in_progress, redirect, robots_disallow, noindex, expired, http_error, canonical_other, canonical_conflict, could_not_check. A skip is a sending rule of this service, not proof that Google cannot index the page. |
skipDetail |
Short detail such as the HTTP status or redirect host. Never page content. |
duplicateOf |
For duplicate_in_progress: { "jobId", "urlId" } of the row in your earlier job that holds this URL. |
preflightWarnings |
For needs_review: limited_content (fewer than 200 words of measured main text on first load), content_not_found (no main text found on first load) or may_need_javascript (the page appears to build its content in the browser, so the text was not measured). None of these blocks a URL. |
mainWordCount |
Words of main content read on first load, when measured. Context for the warning. |
warningApproved |
true once you approved the URL despite its warning. |
canSubmitUnchecked |
true when our checker was blocked and this URL can be explicitly submitted without checks. |
canRecheck |
true when this skipped URL can be checked again. false for duplicate_in_progress. |
uncheckedApproved |
true after you chose to submit without a completed page check. |
preflightCheckedAt |
When the pre-submission checks finished. Separate from checkedAt. |
The job also carries preflight ({ "checking", "needsReview", "skipped", "skipReasons" }) and input ({ "received", "unique", "duplicatesRemoved" }, null for older jobs), and guidance gains checkingPage,
needsReview and skipped counts plus the checking_pages phase.
Approve URLs that need review
POST /api/indexing/jobs/:id/approve
{ "urlIds": ["url-id-1", "url-id-2"] }or { "all": true } for every URL in the job that is waiting as
needs_review. An empty urlIds list is rejected; only the explicit all
flag selects everything. Approved URLs move on to be sent and cost the job’s creditsPerUrl
each when they are sent. Technical skips cannot be approved. If an approval
is more than an hour old when the credit would be reserved, the page is
checked again first; your approval is kept, but a new technical block still
wins. Repeating an approval changes nothing and never charges twice.
Returns { "approved", "skippedDuplicates", "job" }, where
skippedDuplicates counts URLs that were skipped instead because another of
your jobs already holds them.
Submit when the checker was blocked
POST /api/indexing/jobs/:id/submit-unchecked
Send { "urlIds": ["url-id-1"] }, or { "all": true } for every eligible
URL in the job. Only URLs blocked by HTTP 403 or a robots.txt rule for
IndexMySiteChecker move on. Other skips are ignored.
This is an explicit choice to send without confirming redirects, search
directives or canonicals. Each moved URL costs the job’s creditsPerUrl when it is sent.
Repeating the request changes nothing and never charges twice. Returns
{ "submittedUnchecked", "skippedDuplicates", "job" }.
Check skipped URLs again
POST /api/indexing/jobs/:id/recheck
Send { "urlIds": ["url-id-1"] }, or { "all": true } for every URL in the
job with canRecheck. Skipped URLs are never checked again on their own, so
use this after you allow our crawler, remove a noindex rule or fix an error
page. The URLs go back to checking_page. The check is free; a URL that
passes is sent and costs the job’s creditsPerUrl, like a new submission. A URL that
another of your jobs now holds is skipped as a duplicate instead. Returns
{ "rechecking", "skippedDuplicates", "job" }, or 503 while new submissions
are paused.
Projects
Projects group jobs for one client or site. Every job object includes
projectId, which is null for unassigned jobs.
GET /api/indexing/projects
Returns { "projects": [...], "nextCursor": null | string }, 200 projects a
page in a stable order. Pass nextCursor back as ?after=<cursor> to fetch
the next page; nextCursor is null on the last page.
POST /api/indexing/projects with { "name": "Acme Co. website" } creates a
project and returns { "project": {...} }.
GET /api/indexing/projects/:id
Returns { "project": {...}, "jobs": [...] } with the project’s 100 most
recent jobs. The project’s stats cover every URL in the project, not only
the listed jobs.
PATCH /api/indexing/projects/:id with { "name": "New name" } renames a
project and returns { "project": {...} }.
Each project has id, name, createdAt, total, a counts map of URLs
per status, backgroundPending (URLs with scheduled checks still to come),
and stats:
| Field | Meaning |
|---|---|
indexed, notIndexed, refunded, failed, pending |
Counts across every URL in the project, using the same statuses as counts. URLs found at any scheduled Google search count as indexed. pending includes pages not indexed yet until their credit is returned. |
completed |
URLs from completed batches with their final reports. |
completedIndexed |
Indexed URLs from completed batches. |
indexRate |
completedIndexed / completed * 100, rounded to two decimals, or null before the first finished result. Pending and failed URLs and early matches are excluded. |
Compute rates from completedIndexed and completed, not from indexed,
which also counts URLs whose batches are still running.
Sitemap watches
A sitemap watch reads a project’s sitemap every six hours and sends new URLs as jobs in that project. See Projects for how new URLs are chosen.
POST /api/indexing/projects/:id/sitemaps with
{ "url": "https://example.com/sitemap.xml" } starts a watch and returns
{ "sitemap": {...} }. Optional fields:
| Field | Type | Meaning |
|---|---|---|
existingUrls |
"baseline" or "review" |
baseline (default) records URLs already listed and never sends them. review lists the ones you never submitted as waiting, for you to send. |
include |
string[] |
Only send new URLs whose path matches one of these patterns. |
exclude |
string[] |
Never send new URLs matching these patterns. Exclude wins. |
autoSubmit |
boolean |
Send new URLs after each read, using credits without asking. Default false, so new URLs wait for you to submit them. |
autoSubmitLimit |
integer |
Most new URLs sent automatically from one read, 1 to 2000. Default 100. More than this wait for you. |
Patterns start with / and match the path and query. Without * a pattern
matches that prefix, so /blog/ covers every page under it. With * it must
match the whole path, and * stands for any characters: /blog/*/amp.
GET /api/indexing/projects/:id/sitemaps returns { "sitemaps": [...] }.
Each watch has its settings, lastCheckedAt, nextCheckAt, lastError,
lastWarning, lastUrlCount, baselineAt (the first complete read,
null until then), checking, and counts: tracked, baseline,
waiting, submitted, filtered, alreadySubmitted and dismissed.
PATCH /api/indexing/sitemaps/:id changes include, exclude, autoSubmit
or autoSubmitLimit. Waiting URLs are filtered again at once.
POST /api/indexing/sitemaps/:id/check reads the sitemap again now. It is
ignored while a read is running or within a minute of the last one.
GET /api/indexing/sitemaps/:id/urls?state=waiting lists up to 500 URLs,
newest first. state is waiting, submitted, skipped or baseline.
Skipped URLs carry skipReason (filtered, already_submitted or dismissed) and, for
already_submitted, the jobId that holds the URL.
POST /api/indexing/sitemaps/:id/submit sends waiting URLs as one job and
returns { "job", "submitted", "sitemap" }. Send { "urls": [...] } with
URLs from the list above to send only those, or an empty body to send every
waiting URL, up to 2000 at a time. job is null when nothing was sent.
Returns 409 while the sitemap is being read.
POST /api/indexing/sitemaps/:id/dismiss with { "urls": [...] } marks
waiting URLs as dismissed. They are skipped with skipReason: "dismissed" and
never sent. Returns { "dismissed", "sitemap" }.
DELETE /api/indexing/sitemaps/:id stops the watch. Jobs it created stay.
Credits
GET /api/credits
Returns your balance, whether you have purchased before (hasPurchased),
whether a card is saved (hasPaymentMethod), and your auto reload settings
(autoReloadEnabled, autoReloadAmountCents).
Errors
Errors return a JSON body with an error code and a human-readable
message. Common cases:
| Status | Meaning |
|---|---|
400 |
Invalid request, such as a malformed URL or more than 2000 URLs. |
401 |
Missing or invalid API key. |
404 |
Unknown job ID. |
429 |
Too many requests. Wait and retry. |
Rate limits
The API is limited per account to keep the service reliable for everyone. If
you hit a 429, back off and retry after a short delay.