POST /v1/batch[01]

POST /v1/batch

Read, score, or extract up to 50 URLs in a single call — an explicit list or a whole site. 1 credit per URL; failed URLs are refunded.

Endpoint

[02]
POST /v1/batch[http]
POST https://api.buildonto.dev/v1/batch
Authorization: Bearer onto_sk_live_YOUR_KEY
Content-Type: application/json

Request body

[03]

Supply either urls (an explicit list) or site (a URL whose pages are auto-discovered). Discovery reads /sitemap.xml at the site root, and falls back to the links on the page you pass. Only same-host URLs are kept. There is no fresh option.

urlsstring[]

Explicit list of http(s) URLs to process (max 50). URLs past the 50th, and URLs that can't be parsed, are dropped silently. Use this OR "site".

sitestring

URL of a site whose pages are auto-discovered on the same host. Use this OR "urls".

mode'read' | 'read-and-score' | 'extract'

What to do per URL. Default: 'read-and-score'. An unknown value silently becomes 'read-and-score'. 'read' returns Markdown only; 'extract' returns JSON-LD/OpenGraph/meta plus the score fields where scoring is available.

limitnumber

Site mode only: max pages to discover, 1–50 (default 25).

Response

[04]

Success (200): a results array, one entry per URL. A page that can't be read (robots, WAF, 404) comes back with ok: false and an error — it never fails the whole batch. Per-URL fields depend on mode: markdown for read modes, structured + counts for extract. Both read-and-score and extract also carry aio_score, grade and hallucination_risk whenever scoring is available (PDFs carry text but no score). Batch markdown is cut at 12,000 characters per URL. A cut page ends with …[truncated by /v1/batch — call /v1/read on this URL for the full page]. There is no per-result truncated flag, so check for that marker. Call /v1/read on that URL for the full body.

A success item has url, ok: true, title, reduction_percent and the mode's fields above. A failed item is { url, ok: false, error: { code, message } }. The top level has status, mode, source ("urls" or "site"), requested, succeeded, results and cache.

Onto fetches 8 URLs at a time, in fixed rounds. A slow URL holds up the rest of its round.

{
  "status": "success",
  "mode": "read-and-score",
  "source": "urls",
  "requested": 3,
  "succeeded": 2,
  "results": [
    {
      "url": "https://stripe.com/pricing",
      "ok": true,
      "title": "Pricing — Stripe",
      "aio_score": 88,
      "grade": "Good",
      "hallucination_risk": "low",
      "reduction_percent": 96.4,
      "markdown": "# Stripe pricing\n\n…"
    },
    {
      "url": "https://blocked.example.com",
      "ok": false,
      "error": { "code": "ROBOTS_BLOCKED", "message": "Target site's robots.txt disallows AI crawlers." }
    }
  ],
  "cache": { "hit": false, "ttl_seconds": 3600 }
}

Errors: see error codes. Whole-request errors: INVALID_REQUEST (400, no valid urls/site), INVALID_URL (400, site could not be parsed or points at a private or internal host), UNAUTHORIZED (401), NO_RESULTS (404, site discovery found nothing — in practice discovery always returns at least the start URL), RATE_LIMITED (429), CONCURRENT_LIMIT (429), PAYMENT_REQUIRED (402). Per-URL failures are reported inline, not as a top-level error.

Billing

[05]

A batch of N URLs costs N credits (N is at most 50). The credits are taken before any URL is fetched, then every ok: false result is refunded.

The quota check is all-or-nothing. If N is more than your monthly plan credits left, all N are billed to bought credits on a paid plan. On Free, the whole batch gets RATE_LIMITED. A batch holds one concurrency slot, however many URLs it has.

Response headers: X-Onto-Cache, X-RateLimit-Remaining, X-RateLimit-Reset, X-Onto-Billed, X-Concurrent-Remaining, and X-Credits-Remaining when the batch was billed to credits. The credit headers already include the refunds.

Examples

[06]

cURL — explicit list:

curl -X POST https://api.buildonto.dev/v1/batch \
  -H "Authorization: Bearer $ONTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": ["https://stripe.com/pricing", "https://stripe.com/docs"],
    "mode": "read-and-score"
  }'

cURL — whole site, extract mode:

curl -X POST https://api.buildonto.dev/v1/batch \
  -H "Authorization: Bearer $ONTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "site": "https://stripe.com", "limit": 25, "mode": "extract" }'

Node (fetch):

const res = await fetch('https://api.buildonto.dev/v1/batch', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ONTO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    urls: ['https://stripe.com/pricing', 'https://stripe.com/docs'],
    mode: 'read-and-score',
  }),
});
const data = await res.json();
for (const r of data.results) {
  // r.markdown is capped at 12,000 chars by /v1/batch
  if (r.ok) console.log(r.url, r.aio_score, r.markdown.length);
}