Error codes
Every error returns JSON with a stable code and a human-readable message.
Error shape
[02]All Read API errors share the same body. The HTTP status code mirrors the standard meaning; the code field is what you should branch on in code (messages can change wording without notice).
{
"status": "error",
"code": "RATE_LIMITED",
"message": "Monthly quota exceeded",
"retry_after": "2026-06-01T00:00:00.000Z"
}Extra details sit at the top level of the body, next to code — not in a nested object:
CONCURRENT_LIMITin_flight, limit, retry_after (always 1, in seconds)
RATE_LIMITEDretry_after — an ISO date for the end of the UTC month
PAYMENT_REQUIREDplan_limit, credits_remaining
Codes
[03]Every code links to its dedicated reference page with full handling guidance, an example response body, retry semantics, and a TypeScript snippet.
INVALID_URL[400]The url was missing or unparseable, used a scheme other than http(s), or pointed at a private or internal host.
INVALID_REQUEST[400]The /v1/batch body wasn't JSON, had neither urls nor site, or listed no valid URLs.
NO_RESULTS[404]Defined for an empty discovery result, but /v1/map always returns at least the start URL.
UNAUTHORIZED[401]The Authorization header is missing, malformed, or holds a key that isn't active.
PAYMENT_REQUIRED[402]A paid plan used up its monthly credits and has no bought credits left.
WAF_BLOCKED[403]The target site refused Onto's crawler (the origin returned 401 or 403).
ROBOTS_BLOCKED[403]The site's robots.txt has Disallow: / for GPTBot or for every user agent.
PLAN_RESTRICTED[403]The action needs a higher plan, such as buying credits on the Free plan.
UNSUPPORTED_TYPE[415]The URL returned something Onto can't read as a document, like an image, video or archive.
IMAGE_PDF[422]The URL is a scanned PDF with no text layer, so there's no text to extract.
RATE_LIMITED[429]Free plan: this month's credits are used up. They reset at the start of the next UTC month.
CONCURRENT_LIMIT[429]Too many requests running at once for your plan. Wait a second and retry.
EXTRACTION_FAILED[500]The fetch or the parse failed: an origin error, an unreachable host, a redirect problem, or a page or PDF Onto couldn't parse.
TLS_ERROR[502]The target site's TLS certificate or handshake failed, so Onto couldn't connect securely.
Handling
[04]Suggested branching in a Node client:
const r = await fetch(url, opts);
const data = await r.json();
if (!r.ok) {
switch (data.code) {
case 'CONCURRENT_LIMIT':
// Slot is full; retry_after is always 1 (second).
await sleep((data.retry_after ?? 1) * 1000);
return retry();
case 'RATE_LIMITED':
case 'PAYMENT_REQUIRED':
// Out of monthly credits (Free), or out of bought credits too (paid).
// Don't retry blindly — escalate.
notifyOps(`Onto quota exhausted: ${data.message}`);
throw new Error(data.message);
case 'ROBOTS_BLOCKED':
// The site asked us not to crawl. Honor it; skip silently.
return null;
case 'WAF_BLOCKED':
case 'URL_NOT_FOUND':
// Origin issue; skip this URL.
return null;
case 'TIMEOUT':
case 'EXTRACTION_FAILED':
// Transient; errors are never cached, so just retry once.
return retry();
default:
throw new Error(`Onto: ${data.code} — ${data.message}`);
}
}