# indexnow-api – IndexNow client for Rust
[](https://crates.io/crates/indexnow-api)
[](https://docs.rs/indexnow-api)
[](LICENSE)
Async Rust library for the [IndexNow](https://www.indexnow.org/) protocol. Tell Microsoft Bing, Yandex, Naver, Seznam.cz and Yep about new, updated or deleted URLs the moment they change, instead of waiting for the next crawl.
- **One call, every engine** – submit to `api.indexnow.org` and the notification is shared with all IndexNow search engines, or target a single engine directly.
- **Async / await** – built on `reqwest`; runs on Tokio or any other async runtime.
- **Small API** – one struct, four methods, one error type. No configuration files.
- **Batch friendly** – up to 10,000 URLs per request, as required by the protocol.
## Installation
```toml
[dependencies]
indexnow-api = "1"
tokio = { version = "1", features = ["full"] }
```
Requires Rust 2024 edition (Rust 1.85 or newer).
## Quick start
```rust
use indexnow_api::IndexNowApi;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// host: your site's hostname
// key: your IndexNow key, also served at https://www.example.com/<key>.txt
let api = IndexNowApi::new("www.example.com", "7be9fca90b3b4b039983fa8f06e03ee8");
api.send_urls(vec![
"https://www.example.com/new-page",
"https://www.example.com/updated-article",
])
.await?;
println!("URLs submitted to IndexNow");
Ok(())
}
```
`send_urls` accepts any `Vec<T>` where `T: ToString`, so `&str`, `String` and your own URL types all work.
## Usage
### Target one search engine
By default requests go to `https://api.indexnow.org`, which forwards the submission to every participating engine. To submit to one engine only, pass its origin. The crate appends the `/IndexNow` path for you.
```rust
use indexnow_api::IndexNowApi;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut api = IndexNowApi::new("www.example.com", "7be9fca90b3b4b039983fa8f06e03ee8");
api.set_search_engine("https://www.bing.com");
api.send_urls(vec!["https://www.example.com/important-update"]).await?;
Ok(())
}
```
| All engines (default) | `https://api.indexnow.org` |
| Microsoft Bing | `https://www.bing.com` |
| Yandex | `https://yandex.com` |
| Naver | `https://searchadvisor.naver.com` |
| Seznam.cz | `https://search.seznam.cz` |
| Yep | `https://indexnow.yep.com` |
### Key file at a custom location
If the key file is not at `https://<host>/<key>.txt`, tell the search engine where it is:
```rust
use indexnow_api::IndexNowApi;
let mut api = IndexNowApi::new("www.example.com", "7be9fca90b3b4b039983fa8f06e03ee8");
api.set_key_location("https://www.example.com/keys/7be9fca90b3b4b039983fa8f06e03ee8.txt");
```
### Error handling
`send_urls` returns `Result<(), IndexNowError>`. The error implements `std::error::Error` and `Display`, so it works with `?`, `anyhow`, `Box<dyn Error>` and friends.
```rust
use indexnow_api::{IndexNowError, IndexNowApi};
#[tokio::main]
async fn main() {
let api = IndexNowApi::new("www.example.com", "7be9fca90b3b4b039983fa8f06e03ee8");
match api.send_urls(vec!["https://www.example.com/page"]).await {
Ok(()) => println!("submitted"),
Err(IndexNowError::Connection(msg)) => eprintln!("request failed: {msg}"),
Err(IndexNowError::Status { code, body }) => eprintln!("HTTP {code}: {body}"),
}
}
```
- `IndexNowError::Connection(String)` – the request could not be sent (DNS, TLS, timeout) or the response could not be read.
- `IndexNowError::Status { code, body }` – the search engine answered with a status other than 200 or 202. `body` is the response text, which usually explains the problem (invalid key, URL not on `host`, too many requests, ...).
## API reference
Full documentation with all signatures is on [docs.rs](https://docs.rs/indexnow-api).
| `IndexNowApi::new(host, key)` | Create a client for one site and key. Endpoint defaults to `https://api.indexnow.org`. |
| `set_search_engine(origin)` | Submit to a specific engine (see table above). |
| `set_key_location(url)` | Full URL of the key file, when it is not `https://<host>/<key>.txt`. |
| `send_urls(urls).await` | Send one `POST /IndexNow` request with up to 10,000 URLs from `host`. |
| `IndexNowError` | Error type (`Connection` or `Status { code, body }`); implements `std::error::Error`. |
## Setting up an IndexNow key
1. **Generate a key.** 8 to 128 characters from `a-z`, `A-Z`, `0-9` and `-`. A 32-character hex string is common:
```bash
openssl rand -hex 16
```
2. **Host the key file.** Create a UTF-8 text file whose only content is the key and serve it at `https://www.example.com/7be9fca90b3b4b039983fa8f06e03ee8.txt`. Any other location works with `set_key_location`.
3. **Submit a URL** with the code above. HTTP 200 or 202 means the request was accepted. The key file is verified by the search engine asynchronously; an invalid key shows up later in Bing Webmaster Tools or Yandex Webmaster.
## FAQ
**What is IndexNow?**
An open protocol, started by Microsoft Bing and Yandex in 2021, that lets websites push URL changes to search engines through a single HTTP request. Participating engines share the notifications with each other.
**Does Google support IndexNow?**
No. Google uses its own Indexing API (limited to job posting and livestream pages) and sitemaps. Use IndexNow for Bing, Yandex, Naver, Seznam.cz and Yep, and keep your sitemap for Google.
**How often can I submit the same URL?**
Submit a URL when its content changes. Resubmitting unchanged URLs repeatedly gives no benefit and may be ignored.
**Can I submit URLs from a different domain?**
No. Every URL in a request must belong to the `host` the key is verified for. Use one `IndexNowApi` per site.
**Is the key a secret?**
The key must be publicly readable at the key file URL, so it is not a secret in the usual sense. It only proves control of the host; rotate it if you suspect misuse.
## Contributing
Issues and pull requests are welcome at [github.com/uiuifree/rust-indexnow-api](https://github.com/uiuifree/rust-indexnow-api).
```bash
git clone https://github.com/uiuifree/rust-indexnow-api.git
cd rust-indexnow-api
cargo test --doc
cargo clippy
```
`tests/` contains an integration test that sends a real request; run it only with a host and key you control.
## License
MIT. See [LICENSE](LICENSE).
## Related resources
- [IndexNow protocol and FAQ](https://www.indexnow.org/documentation)
- [Bing IndexNow](https://www.bing.com/indexnow)
- [Yandex IndexNow](https://yandex.com/support/webmaster/indexnow.html)
- [Naver IndexNow](https://searchadvisor.naver.com/guide/indexnow)