Appearance
Core Concepts
A short tour of the ideas that shape every Runo request.
Schema-as-JSON
A schema is a flat array of fields. Each field declares what it is and not where to find it on the page.
json
[
{ "field": "title", "type": "string", "example": "MacBook Pro 14\"" },
{ "field": "price", "type": "float", "example": 1999.00 },
{ "field": "inStock", "type": "boolean", "example": true }
]Three pieces matter:
| Key | Required | Purpose |
|---|---|---|
field | yes | Output key in the response data object. |
type | yes | One of: string, integer, float, boolean, date, array<string>, array<integer>, array<float>. |
example | yes | A representative value. Acts as a one-shot anchor for the LLM. Examples strongly improves accuracy on ambiguous fields. |
hint | no | Free-form guidance ("Use sale price if present"). |
Runo extracts by semantic meaning, so when a site re-skins or restructures its HTML, your call keeps working.
Static vs. Dynamic Keys
Two key types, picked when you create a key in the dashboard.
| Type | Prefix | Schema Source | Why Pick It |
|---|---|---|---|
| Dynamic | sk_live_ | schema in every request body | You extract many different shapes, or are still iterating |
| Static | sk_static_ | Bound to the key at creation | One shape, lots of pages |
Rules:
- Dynamic key without
schema→422 SCHEMA_REQUIRED. - Static key with inline
schema→400 SCHEMA_NOT_ALLOWED. - Static-key schema is editable later (PATCH from the dashboard); the prompt cache is invalidated on edit.
The Null Sentinel
Runo never silently drops fields. If a value cannot be resolved, it returns null explicitly:
json
{
"data": {
"title": "Stonehenge",
"population": null,
"founder": null
}
}This means data always has the same keys as your schema. Your downstream code can be null-aware instead of undefined-aware.
Type Coercion
Declared types are enforced. Common cases:
| Declared | Page Contains | Coerced to |
|---|---|---|
integer | "35 years old" | 35 |
float | "$1.2M" | 1200000.0 |
boolean | "✓ Verified" | true |
date | "March 14, 2024" | "2024-03-14" (ISO 8601) |
If a value cannot be coerced cleanly, you'll get either null (preferred) or a TYPE_COERCION_FAILED error for that record. Coercion warnings appear in warnings: [...] on the response when present.
Render Modes
Many pages need JavaScript to render their content. Runo handles this for you with three modes via options.render_js:
render_js | Behavior |
|---|---|
"auto" (default) | Plain HTTP fetch first; auto-escalate to a stealth headless browser if the page is empty, JS-gated, or actively blocking bots. |
"always" | Skip the fetch - go straight to headless. Slower, more reliable for SPAs. |
"never" | Plain fetch only. Fastest, but fails on JS-heavy pages. |
In practice, "auto" is the right default. See JS Rendering & Bypass for details on the escalation triggers and the paid-tier bypass for hardened sites.
Three Modes For Three Jobs
| Endpoint | When to Use It |
|---|---|
POST /extract | One URL, one record back. |
POST /batch | Many URLs, same schema. Returns an array of /extract responses. |
POST /crawl | Seed URL + a follow_pattern. Runo follows links up to max_pages/max_depth, respects robots.txt, and refunds unused units. |
/batch and /crawl accept async_mode: true. Useful when you don't need results immediately and want to fire-and-forget a large batch. See Async Mode & Cancellation.
What Counts As a Request
| Endpoint | Cost |
|---|---|
/extract | 1 request |
/batch | 1 request per URL |
/crawl | Reserves max_pages upfront; refunds whatever it doesn't use |
Crawl reservations are also capped by your remaining quota, see Plans, Rate Limits & Overage.