Skip to content

Errors & Retries

Runo always returns JSON. The shape is consistent, the codes are machine-readable, and the retryable flag tells you whether to try again.

Error Envelope

json
{
  "status": "error",
  "error": {
    "code": "FETCH_BLOCKED",
    "message": "Both plain fetch and stealth headless were blocked.",
    "retryable": true
  }
}

Top-level status: "error" means the whole call failed. Inside /batch and /crawl responses, individual URL entries can carry the same envelope while the overall response is still 200 OK.

Codes

Schema & Request Shape

CodeHTTPRetryableMeaning
SCHEMA_INVALID400noSchema is malformed (missing field, unknown type, etc.)
SCHEMA_REQUIRED422noDynamic key called without a schema
SCHEMA_NOT_ALLOWED400noStatic key included an inline schema
TYPE_COERCION_FAILED200noA specific field couldn't be coerced to its declared type

Quotas & Access

CodeHTTPRetryableMeaning
RATE_LIMITED429yes (after Retry-After)Per-minute limit hit
QUOTA_EXCEEDED429no this monthMonthly quota exhausted
TIER_REQUIRED402noURL requires T4 (CAPTCHA) or T5 (residential proxy) - Pro/Scale only
KEY_LIMIT_REACHED429noActive-key cap hit on your plan
ACCOUNT_SUSPENDED403noAccount is suspended

Fetch & Rendering

CodeHTTPRetryableMeaning
FETCH_BLOCKED200yesAnti-bot defeated both fetch and headless
URL_UNREACHABLE200yesDNS or network failure
TIMEOUT200yesPage exceeded options.timeout_ms

Crawl

CodeHTTPRetryableMeaning
CRAWL_LIMIT_REACHED200n/amax_pages hit

LLM

CodeHTTPRetryableMeaning
LLM_ERROR200yesModel returned unusable response
LLM_UNAVAILABLE200yes (back off)Model 503 / overloaded after 4 internal attempts
LLM_RATE_LIMITED200yes (back off)Model 429 after internal key rotation
LLM_TIMEOUT200yesDeadlineExceeded / 504 after retry
LLM_TRUNCATED200yesJSON still truncated after output-budget bumps
LLM_BLOCKED200noSafety/policy block
LLM_EMPTY200yesEmpty candidates from the model
LLM_BAD_REQUEST200noInvalid argument / prompt too long

Jobs

CodeHTTPRetryableMeaning
JOB_CANCELLEDn/an/aPer-URL marker in batch/crawl results for units that didn't run
JOB_NOT_FOUND404noDELETE on an unknown or completed job
JOB_FORBIDDEN403noDELETE on someone else's job

For top-level errors flagged retryable: true:

  • Honor Retry-After on 429. Otherwise back off exponentially: 1 s, 2 s, 4 s, 8 s.
  • Cap at 4 attempts.
  • Treat LLM_BLOCKED, LLM_BAD_REQUEST, SCHEMA_*, TIER_REQUIRED, QUOTA_EXCEEDED, ACCOUNT_SUSPENDED as terminal. Retrying will not help.
py
import time, requests

def call_with_retry(url, body, headers, max_attempts=4):
    delay = 1.0
    for attempt in range(max_attempts):
        r = requests.post(url, json=body, headers=headers, timeout=60)
        data = r.json()
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", delay)))
            delay *= 2
            continue
        if data.get("status") == "error" and data["error"].get("retryable"):
            time.sleep(delay)
            delay *= 2
            continue
        return data
    return data  # last attempt's payload

Warnings (Non-Fatal)

When the call succeeds but something looked off, the response may include a warnings array:

json
{
  "status": "success",
  "data": { "price": 19.99 },
  "warnings": ["coerced 'price' from '$19.99 USD' to 19.99"]
}

warnings is omitted when empty.

Released under the terms of Runo’s Terms of Use.