snakereach

Reference

The Document.

Every acquisition produces this same shape: a scrape, a crawled page, an ingested file. One output contract, so code that handles a scrape handles a crawl result too.

Fields

FieldTypeDetail
urlstringThe URL you asked for, canonicalized.
final_urlstringWhere it ended up after redirects.
fetched_atstringISO 8601, UTC.
statusintegerHTTP status of the final response.
content_typestringNormalized, without parameters.
depthintegerLink distance from the seed. Always 0 for a scrape.
titlestringCombined from JSON-LD, OpenGraph, meta, and the document itself.
authorstringSame precedence rules.
published_datestringSame precedence rules.
languagestringAs declared, or detected from the document.
descriptionstringMeta description or OpenGraph equivalent.
site_namestringOpenGraph site name where present.
keywordsstring[]As declared by the page.
markdownstringThe primary body representation.
textstringPlain text, derived from the same extraction.
htmlstringRaw HTML. Empty unless include_html was set.
linksLink[]url, text, internal, nofollow.
imagesImage[]url, alt, title.
tablesTable[]caption, headers, rows.
recordsobject[]Repeated structures, product cards, search results, feed entries.
metadataobjectAnything that does not fit a fixed field, such as json_ld.
traceTracestages[], cache_hit, rendered, redirects[], warnings[].

Precedence

When sources disagree about the title, author or date, the merge is deterministic rather than a judgement call: JSON-LD first, then OpenGraph, then standard meta tags, then the document itself. No model decides what the “real” title is, so the same page always produces the same answer.

Anything without a fixed field lands in metadata, json_ld payloads, feed entry data, and similar, rather than being dropped.

markdown, text, html

markdown is the primary representation and what most pipelines want. text is the same extraction without the formatting. html is the raw body and is empty unless you ask for it. It is by far the largest field, and the key is always present so the shape never changes.

records

Repeated structures found on the page, product cards, search results, feed entries. A listing page yields records; an article usually yields none. This is the field to check when you want the page's data rather than its prose.

A full response

Real output, captured against the project's own test fixture:

200 response
{
  "url": "https://example.com/article",
  "final_url": "https://example.com/article",
  "fetched_at": "2026-07-30T11:43:46.878250Z",
  "status": 200,
  "content_type": "text/html",
  "depth": 0,
  "title": "The Quiet Rise of Underwater Farming",
  "author": "Nadia Okafor",
  "published_date": "2025-11-03",
  "language": "en",
  "description": "How submerged greenhouses are reshaping coastal agriculture.",
  "keywords": ["aquaculture", "farming", "ocean"],
  "site_name": "Ocean Journal",
  "markdown": "# The Quiet Rise of Underwater Farming\n\nTwenty meters beneath …",
  "text": "The Quiet Rise of Underwater Farming\nTwenty meters beneath the Li …",
  "html": "",
  "links": [
    { "url": "https://example.com/", "text": "Home", "internal": true, "nofollow": false }
  ],
  "images": [
    { "url": "https://example.com/images/biosphere.jpg",
      "alt": "A submerged biosphere with basil plants", "title": "" }
  ],
  "tables": [
    { "caption": "Yield comparison",
      "headers": ["Crop", "Land yield", "Biosphere yield"],
      "rows": [["Basil", "1.0x", "1.4x"], ["Strawberry", "1.0x", "1.1x"]] }
  ],
  "records": [],
  "metadata": {
    "json_ld": [
      { "@context": "https://schema.org", "@type": "NewsArticle",
        "headline": "The Quiet Rise of Underwater Farming",
        "author": { "@type": "Person", "name": "Nadia Okafor" },
        "datePublished": "2025-11-03", "inLanguage": "en" }
    ]
  },
  "trace": {
    "stages": [
      { "name": "fetch",    "elapsed_ms": 22.2 },
      { "name": "parse",    "elapsed_ms": 1.3 },
      { "name": "content",  "elapsed_ms": 185.8 },
      { "name": "metadata", "elapsed_ms": 10.0 },
      { "name": "links",    "elapsed_ms": 0.1 },
      { "name": "media",    "elapsed_ms": 0.0 },
      { "name": "records",  "elapsed_ms": 0.1 },
      { "name": "combine",  "elapsed_ms": 0.0 }
    ],
    "cache_hit": false,
    "rendered": false,
    "redirects": [],
    "warnings": []
  }
}

The trace at the end gets its own page.