API quickstart
Five minutes from signup to a clean structured AIDocument response. One endpoint into the index, one shape, every URL on the web. Pick your language below; the request is identical in all of them.
No account, no key, one command
Paste this into a terminal, or put the URL straight into a browser address bar. You get the same AIDocument the paid endpoint returns for the same page, so what you see here is what you would build on.
curl "https://api.lyrenth.com/v1/public/aidocument?url=https://en.wikipedia.org/wiki/Web_indexing"
The trial allows 25 reads an hour and 50 a day per IP address, with no sign-up. It accepts GET ?url= as above, or POST with a JSON body of {"url": "..."}. Past either limit it answers 429 telling you which one you hit and how long to wait. When you want more than that, or want to use it from your own application, take a key in step 01 below: the free tier is 2,000 reads a month and still needs no card.
Get an API key
Sign up at /signup. The free tier is 2,000 AIDocuments / month, no credit card. Your raw key shows once on the dashboard right after signup; copy it into your password manager. Mint additional keys (one per environment) at /dashboard/keys.
Set it as an env var
Keys look like aiwk_ followed by hex. Treat one like any other production secret: do not commit it. Every sample below reads LYRENTH_API_KEY from the environment, which is also the variable both SDKs and the MCP server look for.
Resolve a URL with /v1/aidocument
POST a JSON body with the URL you want resolved. The response is the canonical AIDocument v2 shape. If we already have the page it is served from the index; if not, we fetch, clean, and save it once so the next agent gets it instantly too.
curl -X POST https://api.lyrenth.com/v1/aidocument \
-H "Authorization: Bearer $LYRENTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://en.wikipedia.org/wiki/Web_indexing"}'# pip install lyrenth
from lyrenth import Lyrenth
client = Lyrenth() # reads LYRENTH_API_KEY from the environment
doc = client.read("https://en.wikipedia.org/wiki/Web_indexing")
print(doc.title)
print(doc.word_count, "words")
print(doc.markdown[:400])
# doc.raw is the full AIDocument envelope shown in step 04.// npm install lyrenth
import { Lyrenth } from "lyrenth";
const lyrenth = new Lyrenth(); // reads LYRENTH_API_KEY from the environment
const doc = await lyrenth.read("https://en.wikipedia.org/wiki/Web_indexing");
console.log(doc.title);
console.log(doc.wordCount, "words");
// doc.raw is the full AIDocument envelope shown in step 04.package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type aiDocument struct {
Identity struct {
Title string `json:"title"`
} `json:"identity"`
Signals struct {
WordCount int `json:"word_count"`
} `json:"signals"`
Content struct {
Markdown string `json:"markdown"`
} `json:"content"`
}
func main() {
body, err := json.Marshal(map[string]string{"url": "https://en.wikipedia.org/wiki/Web_indexing"})
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost,
"https://api.lyrenth.com/v1/aidocument", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("LYRENTH_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var doc aiDocument
if err := json.NewDecoder(res.Body).Decode(&doc); err != nil {
panic(err)
}
fmt.Println(doc.Identity.Title)
fmt.Println(doc.Signals.WordCount, "words")
}// Java 11 or later, no dependencies.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ReadUrl {
public static void main(String[] args) throws Exception {
String body = "{\"url\":\"https://en.wikipedia.org/wiki/Web_indexing\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.lyrenth.com/v1/aidocument"))
.header("Authorization", "Bearer " + System.getenv("LYRENTH_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body()); // the envelope from step 04
}
}// .NET 6 or later, no packages.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("LYRENTH_API_KEY"));
var body = new StringContent(
"{\"url\":\"https://en.wikipedia.org/wiki/Web_indexing\"}",
Encoding.UTF8, "application/json");
var response = await http.PostAsync("https://api.lyrenth.com/v1/aidocument", body);
response.EnsureSuccessStatusCode();
using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var root = doc.RootElement;
Console.WriteLine(root.GetProperty("identity").GetProperty("title").GetString());
Console.WriteLine(root.GetProperty("signals").GetProperty("word_count").GetInt32() + " words");<?php
// ext-curl, no packages.
$ch = curl_init("https://api.lyrenth.com/v1/aidocument");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("LYRENTH_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["url" => "https://en.wikipedia.org/wiki/Web_indexing"]),
]);
$doc = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $doc["identity"]["title"], "\n";
echo $doc["signals"]["word_count"], " words\n";# Ruby standard library, no gems.
require "json"
require "net/http"
require "uri"
uri = URI("https://api.lyrenth.com/v1/aidocument")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV['LYRENTH_API_KEY']}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({ url: "https://en.wikipedia.org/wiki/Web_indexing" })
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
doc = JSON.parse(response.body)
puts doc["identity"]["title"]
puts "#{doc['signals']['word_count']} words"// Cargo.toml
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let key = std::env::var("LYRENTH_API_KEY")?;
let doc: Value = reqwest::Client::new()
.post("https://api.lyrenth.com/v1/aidocument")
.bearer_auth(key)
.json(&json!({ "url": "https://en.wikipedia.org/wiki/Web_indexing" }))
.send()
.await?
.json()
.await?;
println!("{}", doc["identity"]["title"]);
println!("{} words", doc["signals"]["word_count"]);
Ok(())
}Read the response
Every successful 2xx call returns the same grouped envelope regardless of how the source page was rendered. The cache.status field tells you whether this call hit the shared index or triggered a fresh fetch. Both SDK samples above expose the same envelope as doc.raw.
{ "schema": { "name": "AIDocument", "version": "2.0" }, "source": { "url": "…/Web_indexing", "render_mode": "static", "status_code": 200 }, "cache": { "status": "hit", "origin_contacted": false }, "identity": { "title": "Web indexing - Wikipedia", "language": "en" }, "content": { "markdown": "Web indexing or…" }, "signals": { "word_count": 1552, "reading_time": 7, "has_json_ld": true }, "economics": { "raw_html_tokens_approx": 21331, "output_tokens_approx": 2715, "token_savings_percent": 0.873 } }
(Optional) Force a fresh fetch
Default is cache-first. To bypass the lookup and crawl now, set freshness_policy on the request body. Allowed values: cache_first (default) or force_refresh. A force_refresh uses 2 credits instead of 1, since it triggers a live crawl.
curl -X POST https://api.lyrenth.com/v1/aidocument \
-H "Authorization: Bearer $LYRENTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/post","freshness_policy":"force_refresh"}'Other endpoints
/v1/aidocument handles most of what an agent needs; the rest is housekeeping.
/v1/aidocumentResolve any URL
Cache on hit within your freshness window, fresh fetch on miss, body shared with every caller. Each success counts as one request. Optional max_tokens caps the returned markdown to a context budget.
/v1/aidocument/batchResolve up to 20 URLs
One call, an array of clean AIDocuments with per-URL error isolation (a failed URL does not block the others). Billed one credit per successfully-read URL.
/v1/read?url=…The same read as Markdown
One GET, no body, text/markdown back instead of JSON: a title, a source line, then the cleaned body. Paste-and-go for a shell or an agent that just wants the text.
/v1/submitQueue a URL for indexing
Returns 202; we crawl in the background. Free, does not count toward quota.
/v1/quotaYour usage state
This month's consumed-vs-limit shape. Free. The dashboard sidebar reads this same endpoint.
What can go wrong
Errors are JSON. Branch on the error field rather than the HTTP status alone.
invalid api keyMissing or invalid Bearer token. The error field carries the plain message, either "missing session cookie or Authorization: Bearer header" or "invalid api key". Check that the key is set and has not been revoked.
upstream_blockedThe target site's WAF or CDN rejected our crawler. One of a family of typed upstream codes; the body carries message, upstream_status and host so you can tell a block from a 404 at the origin.
no_extractable_contentThe page answered 2xx but held nothing readable, which usually means an anti-bot challenge was served instead of the article.
rate_limitedTwo different cases, told apart by the window field in the body. window "1s" is your plan's per-second API rate. window "month" is your monthly quota, shared across all your keys, with an upgrade hint and the reset date.
this URL is not availableThe URL is removed from the index, or the site's robots.txt disallows it. Lyrenth respects robots.txt on every fetch, so a disallowed URL cannot be indexed on request either.
Want the full document shape?
Every field in the response, and the contract guarantees we make about backward compatibility.