This val automates IndexNow submission by:
- Crawling sitemap URLs.
- Tracking submission history in project-scoped SQLite.
- Submitting only changed URLs with explicit limits at different stages (per-site run URL limit and IndexNow POST chunk size).
- Read the IndexNow docs (indexnow.org/documentation) and make sure your key file is already hosted on your domain at a public URL like
https://www.example.org/<key>.txt. - Add at least one site file in
config/sites/*.jsonwithhost,sitemapUrl, andkeyLocation(seeSite Config Files). - Set required env vars:
INDEXNOW_RUN_TOKEN(andINDEXNOW_FORCE_TOKENif you want force-mode protection) (seeEnvironment Variables). - Open
/to confirm the dashboard loads. - Run a safe dry run first:
POST /run?dryRun=1withx-indexnow-token(seeAPI Reference). - After dry run looks correct, enable cron via
indexnow.cron.tsfor ongoing automation.
INDEXNOW_PER_SITE_RUN_URL_LIMIT(default5000): per-site cap for queued changed URLs in a single run.- IndexNow POST payload size (
10,000URLs): protocol hard limit per HTTP request to IndexNow. - Monitor query
runs(default50, max500): number of run-history records returned, not URL count.
indexnow.http.ts: primary HTTP entrypoint./serves the dashboard UI./runperforms IndexNow submission runs (JSON response)./monitorserves monitor JSON.
indexnow.cron.ts: scheduled trigger that calls the HTTP handler at/run.indexnow.monitor.http.ts: monitor implementation module used byindexnow.http.ts.
The recommended setup is a cron trigger every hour for sitemap polling, plus /run for immediate/manual control.
Run history stores why a run happened using reason:
manual_run_endpoint: run came through HTTPPOST /run.cron_entrypoint: run came throughindexnow.cron.ts.
This is entrypoint provenance. It does not distinguish scheduled cron fires from someone manually running the cron file.
To keep provenance meaningful, treat indexnow.cron.ts as scheduler-only and use /run for all manual runs.
INDEXNOW_RUN_TOKEN(required): auth token required for/run(manual and cron).INDEXNOW_FORCE_TOKEN(optional but recommended): separate auth token required when usingforce=1.INDEXNOW_ENDPOINT(optional): defaults tohttps://api.indexnow.org/indexnow.INDEXNOW_PER_SITE_RUN_URL_LIMIT(optional): defaults to5000; per-site queue cap per run before batching into IndexNow POSTs.INDEXNOW_RUN_URL_LIMIT(legacy alias): still accepted for backward compatibility; remove from val settings after migrating.INDEXNOW_MAX_URLS_PER_RUN(legacy alias): still accepted for backward compatibility; remove from val settings after migrating.INDEXNOW_ALERTS_ENABLED(optional): defaults to enabled; set to0/falseto disable alert emails.INDEXNOW_ALERT_TO_<SITE>(optional): per-site alert recipient email (for exampleINDEXNOW_ALERT_TO_HANAYOU). If unset, alerts for that site go to the val owner email.
Site configs live in config/sites/*.json with one site per file.
At least one file must exist or the val will throw on startup.
Example: config/sites/hanayou.studio.json
{ "host": "www.hanayou.studio", "sitemapUrl": "https://www.hanayou.studio/sitemap.xml", "keyLocation": "https://www.hanayou.studio/2dc12b205bfe46d19a75077e3991f7ce.txt", "alertToEnvVar": "INDEXNOW_ALERT_TO_HANAYOU", "key": "optional-explicit-key", "includePrefixes": ["/journal/", "/guides/"], "excludePrefixes": ["/tag/", "/author/"] }
Notes:
- Keep raw email addresses out of
config/sites/*.json. - Use
alertToEnvVarto point to an environment variable that contains the recipient email. - If
alertToEnvVaris omitted (or env var is empty), the val sends alerts to the default owner email.
Base URL pattern: https://<your-val>.web.val.run
Auth model:
POST /runis token-protected withINDEXNOW_RUN_TOKEN.POST /run?force=1additionally requiresINDEXNOW_FORCE_TOKEN.GET /andGET /monitorare read-only/public.
Time semantics:
- JSON timestamps are ISO-8601 UTC strings.
- The dashboard (
GET /) renders those timestamps in the viewer's local browser timezone.
Purpose: Render the HTML operator dashboard.
Auth: none.
Query parameters:
| Name | Required | Type | Description |
|---|---|---|---|
site | no | string | Restrict dashboard to one configured host. |
runs | no | 50 or 500 | Run-history depth shown in tables. |
issues | no | 1/true/yes | Issue-focused mode; when set, history depth is forced to 500. |
run | no | integer | Pre-expand a specific run row in the table. |
Response:
200 text/htmldashboard UI.- Runtime/config errors bubble as server errors.
Example:
https://<your-val>.web.val.run/?site=www.hanayou.studio&runs=50
Purpose: Execute sitemap discovery and IndexNow submission workflow.
Auth headers:
| Header | Required | Description |
|---|---|---|
Authorization: Bearer <INDEXNOW_RUN_TOKEN> | yes (or x-indexnow-token) | Primary run token. |
x-indexnow-token: <INDEXNOW_RUN_TOKEN> | yes (or Bearer) | Alternate primary run token header. |
x-indexnow-force-token: <INDEXNOW_FORCE_TOKEN> | conditional | Required only when force=1. |
x-indexnow-reason: cron_entrypoint | reserved | Used by indexnow.cron.ts to mark cron-originated runs. |
Query parameters:
| Name | Required | Type | Description |
|---|---|---|---|
dryRun | no | 1 or omitted | 1 performs a no-submit simulation. |
force | no | 1 or omitted | 1 allows intentional resubmission of currently eligible unchanged URLs. |
site | no | string | Restrict run to one configured host. |
Status codes:
| Code | Meaning |
|---|---|
200 | Run completed and hasErrors=false. |
500 | Run completed with hasErrors=true for one or more sites. |
401 | Missing/invalid run token. |
403 | force=1 requested but force token missing/invalid or not configured. |
405 | Method not allowed (must be POST). |
503 | Server misconfigured (INDEXNOW_RUN_TOKEN missing). |
Pragmatic response fields:
- Top-level:
reason(manual_run_endpointorcron_entrypoint),dryRun,force,runUrlLimit,hasErrors,startedAt,finishedAt. - Per site (
siteRuns[]):host,filteredCount,changedCount,queuedCount,submittedCount,pendingCount,deferredCount,errors. - Per site URL arrays:
filteredUrls,changedUrls,queuedUrls.
Examples:
Dry run (safe):
curl -X POST "https://<your-val>.web.val.run/run?dryRun=1&site=www.hanayou.studio" \ -H "x-indexnow-token: <INDEXNOW_RUN_TOKEN>"
Real run:
curl -X POST "https://<your-val>.web.val.run/run?site=www.hanayou.studio" \ -H "x-indexnow-token: <INDEXNOW_RUN_TOKEN>"
Force run:
curl -X POST "https://<your-val>.web.val.run/run?force=1&site=www.hanayou.studio" \ -H "x-indexnow-token: <INDEXNOW_RUN_TOKEN>" \ -H "x-indexnow-force-token: <INDEXNOW_FORCE_TOKEN>"
Purpose: Return JSON health/status data for automation, debugging, and external checks.
Auth: none.
Query parameters:
| Name | Required | Type | Description |
|---|---|---|---|
site | no | string | Limit output to one configured host. |
runs | no | 50 or 500 | Number of recent run records per site. |
issues | no | 1/true/yes | Issue-focused mode; when set, run history depth is forced to 500. |
run | no | integer | Include per-URL status rows for the selected run when available. |
Status codes:
| Code | Meaning |
|---|---|
200 | Monitor snapshot returned. |
500 | Runtime/config failure while building snapshot (error bubbles). |
Pragmatic response fields:
- Top-level:
generatedAt,runHistoryLimit,issuesOnly,selectedHost,selectedRunId. - Site aggregate status:
totals(especiallyupToDateUrls,needsSubmissionUrls,retryPendingUrls). - Run history:
sites[].latestRun,sites[].recentRuns. - Drill-down details:
sites[].selectedRunUrlStatuses(whenrunis provided and data exists).
Example:
curl "https://<your-val>.web.val.run/monitor?site=www.hanayou.studio&runs=500&issues=1"
submittedCountcounts successful IndexNow HTTP200responses only.pendingCounttracks URLs from IndexNow HTTP202responses.deferredCountmeans URLs were eligible/changed but capped byINDEXNOW_PER_SITE_RUN_URL_LIMITin that run.
SQLite tables:
indexnow_url_state_v1: per-URL state (seen/submitted timestamps, lastmod, status).indexnow_run_log_v1: per-run summaries for observability and debugging.
This val uses https://esm.town/v/std/sqlite/main.ts so data is scoped to this val project.
INDEXNOW_PER_SITE_RUN_URL_LIMITis applied per site for each run.- If more changed URLs are discovered than the limit, the remainder is deferred to future runs.
- To intentionally resubmit unchanged URLs (still within limit), use
force=1withx-indexnow-force-token.
- Daily/incident check:
- Open
/dashboard and confirm site health cards are green. - Review recent runs for
pending,deferred, orErrorstatuses.
- Open
- Manual safe run flow:
- Run dry run first:
POST /run?dryRun=1. - Review
changedCountandqueuedCount. - If expected, run real submission:
POST /run.
- Run dry run first:
- If deferred URLs accumulate:
- Confirm this is expected (large content release, migration, etc.).
- Tune
INDEXNOW_PER_SITE_RUN_URL_LIMITand/or schedule frequency. - Use
force=1only when you intentionally want to resubmit unchanged URLs.
- Alert triage:
- Submission alert with no issues: informational (URLs submitted successfully).
- Alert with
pending: retry/watch next run; investigate endpoint throttling. - Alert with
deferred: consider run URL-limit tuning or additional runs. - Alert with
errors: requires action before trusting indexing progress.
- External verification:
- Use Bing Webmaster Tools to verify ingestion trends (expect lag).