Six endpoints, one credential, one document shape. This page lists every call an API key can make, with its request fields, its response, and the errors worth branching on.
Everything is under https://api.lyrenth.com, over HTTPS, with the key in the Authorization header.
curl -X POST https://api.lyrenth.com/v1/aidocument \
-H "Authorization: Bearer aiwk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/post"}'Keys start with aiwk_. A key can call the six endpoints on this page and nothing else: key management, billing, settings and site verification are human acts and need a signed-in dashboard session. That line is deliberate. A leaked key can spend credits, and revoking it ends the incident; it can never change your account. Point a key at an account endpoint and you get a 403 saying so.
Three ways to get a page: JSON, JSON in bulk, or Markdown.
/v1/aidocumentThe main endpoint. Serves the stored copy when the page is in the index and inside your plan's freshness window, and fetches, cleans and stores it once when it is not, so the next agent to ask gets the fast path. The response is the v2 envelope documented on the AIDocument page. Request bodies are capped at 4 KB.
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | required | The page to resolve. Must be an http or https URL. |
| freshness_policy | string | optional | cache_first (the default) serves the stored copy while it is inside your plan's freshness window and crawls on a miss. force_refresh skips the lookup and crawls now. Any other value is a 400, so a typo never silently becomes the default. |
| max_tokens | integer | optional | Caps content.markdown to roughly this many tokens, trimmed at a clean paragraph or sentence boundary. Omit it, or send 0, for the full body. |
200 the AIDocument v2 envelope. 202 with {"status":"indexing"} and a Retry-After header when the page is not in the index and the live-fetch budget is full; the URL is queued and a retry in a few seconds normally lands. A served copy that was older than the freshness window carries the header X-Lyrenth-Stale: 1.
/v1/aidocument/batchThe same read, fanned out. Every URL is isolated: one that fails is reported in its own result and never blocks the others, so the call itself succeeds with a 200 as long as the request was well formed. Billed one credit per successfully-read URL. Request bodies are capped at 16 KB.
| Field | Type | Required | Description |
|---|---|---|---|
| urls | string[] | required | One to 20 URLs. An empty array is a 400, and so is a 21st URL. |
| freshness_policy | string | optional | Same two values as the single read, applied to every URL in the call. |
| max_tokens | integer | optional | Applied to each returned document separately. |
{
"urls": [
"https://example.com/a",
"https://example.com/b"
],
"freshness_policy": "cache_first",
"max_tokens": 4000
}{
"results": [
{ "url": "https://example.com/a", "ok": true, "document": { /* AIDocument v2 */ } },
{ "url": "https://example.com/b", "ok": false, "status": "upstream_error",
"error": "origin returned 404" }
]
}Each result carries url and ok. A successful one adds document; a failed one adds error and, where the reason has a name, status: one of taken_down, robots_disallowed, upstream_error, no_content, indexing, or one of the transport codes listed further down.
/v1/read?url=…One GET, no body, no JSON parsing. The response is text/markdown: a title, the page description, a source line, then the cleaned body. Made for a shell one-liner and for agents that want text rather than structure. Errors are still JSON, so check the content type or the status before treating the body as Markdown.
| Field | Type | Required | Description |
|---|---|---|---|
| url | query | required | The page to read. A bare host with no scheme is accepted and treated as https. |
| fresh | query | optional | 1, true or yes forces a live re-fetch. This is the simple surface: for the full policy enum use POST /v1/aidocument. |
| max_tokens | query | optional | Caps the body the same way the JSON endpoint does. A missing or non-positive value returns the full body. |
# Web indexing > An overview of how search systems index the web. Source: https://en.wikipedia.org/wiki/Web_indexing --- Web indexing or...
Neither of these counts against your quota.
/v1/submitSend {"url":"..."}. The URL enters the crawl queue ahead of pages we discovered ourselves, and the call returns immediately with 202. Once it lands, reads serve it from the index at cache speed; a read made before then simply fetches the page live, so there is no need to wait before asking. queued is false when the URL is already indexed or already waiting, which means your intent is satisfied and no new work was created.
{
"status": "accepted",
"url": "https://example.com/post",
"queued": true,
"note": "queued for background indexing; once it lands, reads serve it from the index"
}/v1/quotaTakes no parameters. Reading it is what rolls your balance over at the start of a new month, so a call on the first of the month returns the fresh figure. Accounts on an unlimited plan get "unlimited": true instead of the counters.
{
"unlimited": false,
"tier": "free",
"used": 143,
"limit": 2000,
"remaining": 1857,
"period_start": "2026-08-01T00:00:00Z",
"resets_at": "2026-09-01T00:00:00Z"
}Two shapes. Branch on error, not on the status alone.
{ "error": "url must be a http or https URL" }Validation, auth and legal refusals put a plain sentence in error. Show it to the developer; it says what to fix.
{
"error": "upstream_blocked",
"message": "example.com blocks our crawler at its edge (WAF / CDN). Their
firewall or bot-protection layer rejects non-browser clients.
Try another URL on the same domain, or ask the operator to
allowlist Lyrenth.",
"upstream_status": 403,
"host": "example.com"
}Anything that reached the origin puts a stable code in error and a plain explanation in message. Transport failures add retryable, so you know whether a second attempt is worth making.
| Status | What it means |
|---|---|
| 200 | The call succeeded. JSON on every endpoint except GET /v1/read, which returns text/markdown. |
| 202 | Accepted, not finished. POST /v1/submit always answers 202. A read answers 202 when the page is not in the index and the live-fetch budget is full: the URL is queued, and a Retry-After header says when to try again. A 202 is never billed. |
| 400 | The request was malformed: a missing url, a URL that is not http or https, an unknown freshness_policy, a negative max_tokens, an empty or oversized urls array, or an unknown name in fields. |
| 401 | No credential, or a key that is not valid. The error field carries the plain message: "missing session cookie or Authorization: Bearer header" or "invalid api key". |
| 403 | Either the URL is adult content, which Lyrenth does not index or serve, or the key was pointed at an account endpoint. Keys call the product endpoints on this page; account operations need a signed-in dashboard session. |
| 422 | The request was fine but no document could be produced. This covers the whole upstream and fetch family below. It is deliberately not a 5xx, so the typed message survives every proxy in between. |
| 429 | Two different limits, told apart by the window field: "1s" is your plan's per-second API rate, "month" is your monthly credit quota. Both carry Retry-After. |
| 451 | The 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. |
| 500 | The page was fetched but could not be turned into an AIDocument. The body says extract_failed and the failure is logged on our side. |
| 502 | The origin never answered: a DNS failure, a timeout, a refused or reset connection, a TLS failure, or a redirect loop. Each has its own code and a retryable flag. |
| Code | Cause |
|---|---|
| upstream_blocked | The origin's firewall or bot-protection layer rejected our crawler at its edge. |
| upstream_bot_filtered | A host that serves the page to a browser but blanket-refuses identified crawlers. |
| upstream_unauthorized | The origin demands authentication. |
| upstream_forbidden | A plain 403 with no auth prompt and no protection-layer signal. |
| upstream_not_found | The URL does not exist on the origin (404). |
| upstream_gone | The origin says the URL is permanently removed (410). |
| upstream_rate_limited | The origin throttled us (429). |
| upstream_unavailable_legal | A legal restriction at the origin (451). |
| upstream_server_error | The origin failed with a 5xx. |
| no_extractable_content | A 2xx with nothing readable on the page, usually an anti-bot challenge served instead of the article. |
| origin_dns_not_found | The domain name does not exist. Retrying will not help. |
| origin_timeout | The origin did not answer inside the fetch window. Retryable. |
| origin_unreachable | The origin refused the connection. Retryable. |
| origin_tls_failure | The origin's certificate or handshake failed. |
| origin_redirect_loop | More than ten redirects without a page. |
| target_not_public | The address resolves to a private or internal network, which Lyrenth does not fetch. |
Both are per account, not per key, and both are on the pricing page.
The API rate is a per-second ceiling on the read and submit endpoints, fixed by your plan. Exceeding it answers 429 with "window": "1s", the limit that applied, and Retry-After: 1. It is a pacing limit, not a penalty: waiting a second is the whole fix.
The quota is your monthly credit balance, shared across every key on the account. Running out answers 429 with "window": "month", the date it resets, and an upgrade hint. Credits are reserved before the work and settled after it, so a call that fails, or that comes back 202, costs you nothing.
The v2 envelope has a JSON Schema. Point your validator or code generator at it.