Skip to content

Python API reference

View as Markdown

Start with analog(url) to view a page and save its result. Use assess(url) for help choosing between a structured view and local Markdown. The Fetching and Working with results guides explain the task-level choices; this page is the compact lookup for names and parameters.

The declaration blocks below show parameter types, defaults, and return types. They describe the API; the accompanying examples show runnable calls.

On this page

analog() returns an AnalogResponse. By default it renders the page in Analog’s browser, gives you a structured view, and saves the result locally. Sign in before calling it in the default mode.

Turn a webpage into something you can understand and use.

Signature
analog(
url: str,
*,
html: str | None = None,
fetcher: Fetcher | None = None,
base_url: str | None = None,
save: bool = True,
reveal_all: bool = True,
load_all: bool = False,
expand_all: bool = False,
mode: Mode = 'auto',
pages: int = 1,
allow_private: bool = False,
) -> AnalogResponse

mode="auto" includes advice about reading the page; it still produces the structured result when that advice favors Markdown. mode="structured" omits the assessment. mode="local" produces Markdown locally without an account or backend request.

Use save=False when you do not need a saved result. For supplied HTML, custom fetchers, browser controls, and pagination, see Fetching.

Keep result for the examples in Read a response. The preview identifies the quote records and the page’s navigation. The output shows the returned preview; fetch progress is reported separately. The live page may change.

Get a response

from analog import analog
result = analog("https://quotes.toscrape.com/js/")
print(result.preview())
Output
pagination: this looks like page 1 of a paginated collection (its links reach page 2) — these records cover this page only.
3 sections extracted.
section[0] 10 records · 3 fields
fields: tags:text[], text:text, text_2:text
section[1] navigation 2 links (header) · 4 fields
fields: text:text, url:url, group:text, depth:text
section[2] navigation 2 links (footer) · 4 fields
fields: text:text, url:url, group:text, depth:text
(A single-subject record was also weighed and withheld — Analog couldn't verify enough of the page's facts for a record we'd trust.)

assess() returns a FitAssessment with advice about how to read the page. It fetches the page and assesses it locally, without calling Analog’s backend or requiring an account. It does not return page records or save an AnalogResponse.

Assess whether a page is worth a structured Analog extraction.

Signature
assess(
url: str,
*,
html: str | None = None,
fetcher: Fetcher | None = None,
reveal_all: bool = True,
load_all: bool = False,
expand_all: bool = False,
probe_feed: bool = True,
allow_private: bool = False,
) -> FitAssessment

Read guidance for the explanation and possible next steps. recommendation is the default steer for a one-off read, not a substitute for choosing based on your task. See Assess a page.

probe_feed=False skips the optional feed and sitemap probes; the page itself is still fetched. Supplying html avoids that page fetch.

Advice depends on the fetched page.

Read the advice

from analog import assess
assessment = assess("https://quotes.toscrape.com/js/")
print(assessment.guidance)
Output
This page as markdown: ~426 tokens (measured). Repeating structure: substantial.
- pagination: this looks like page 1 of a paginated collection (its links reach page 2) — these records cover this page only. Analog can follow the site's own next links and merge the pages into one result; pass pages=2 to analog(...).
Task-by-task guidance (records vs markdown vs feeds): https://getanalog.io/docs/assess/

The pagination hint uses Python arguments. Use assessment.guidance_for("shell") or assessment.guidance_for("mcp") when presenting the advice through those surfaces.

For most tasks, use analog(). Use Client when your program needs to call the service directly, and a fetcher when it needs to control how the webpage is downloaded. Fetchers return HTML; the client returns a structured response.

Client reads the credential stored after signing in. There is no API-key constructor argument. Use a context manager to close the client’s connection pool when you are finished.

Synchronous Analog client.

Signature
Client(
*,
base_url: str | None = None,
timeout: float = 60.0,
max_retries: int = 3,
http_client: httpx.Client | None = None,
)

timeout is in seconds. max_retries bounds automatic retries for eligible requests. An injected http_client must have its own base_url; your code owns and closes that HTTP client. A stored credential is sent only to the backend origin for which it was issued.

Send HTML to the backend for extraction.

Signature
Client.extract(*, html: str, url: str) -> AnalogResponse

This call sends the supplied HTML to Analog. It neither fetches the URL nor saves the returned response. Pass the address that served the HTML, including any redirect, as url.

For the complete page view with locally rendered Markdown and automatic saving, prefer analog(url, html=html). Client.extract() returns the service response without those local additions.

Fetch backend version + wire schema version. Lets the caller warn on skew.

Signature
Client.info() -> InfoResponse

info() makes an authenticated service request. Its response includes the service version, wire schema version, and minimum supported SDK version.

This fetches the static quotes page over HTTP, then sends its HTML to the service. The preview shows the returned record sections.

The output shows the full returned preview; the live page may change.

Send HTML you fetched

from analog import Client, HttpFetcher
with (
HttpFetcher() as fetcher,
Client(timeout=60) as client,
):
page = fetcher.fetch(
"https://quotes.toscrape.com/"
)
response = client.extract(
html=page.html, url=page.resolved_url
)
print(response.preview())
Output
2 sections extracted.
section[0] 10 records · 5 fields
fields: tags_2:text[], text:text, text_2:text, about_url:url, tags:url[]
section[1] navigation 2 links (footer) · 4 fields
fields: text:text, url:url, group:text, depth:text
page outline:
not extracted: "Top Ten tags" (unknown, 10 items) — read as page structure, not records
(A single-subject record was also weighed and withheld — Analog couldn't verify enough of the page's facts for a record we'd trust.)

Read service compatibility information

from analog import Client
with Client() as client:
info = client.info()
print("Schema:", info.schema_version)
print("Minimum SDK:", info.min_supported_sdk)
Output
Schema: 29
Minimum SDK: 0.19.0

These values can change as the service is updated.

Browser renders JavaScript and returns a FetchResult containing HTML, the final URL, HTTP status, and headers. Use fetch() directly when you need HTML, or pass the instance as analog(..., fetcher=browser) to get Analog’s structured response.

JS-aware analog.fetcher.Fetcher powered by Playwright.

Signature
Browser(
*,
timeout: float = 30.0,
wait_for: str | None = None,
wait_for_load_state: Literal['commit', 'domcontentloaded', 'load', 'networkidle'] = 'load',
additional_wait_ms: int = 0,
scroll: bool = True,
reveal_all: bool = True,
load_all: bool = False,
expand_all: bool = False,
page_batches: int = 1,
max_scroll_iterations: int | None = None,
headless: bool = True,
user_agent: str | None = None,
use_system_chrome: bool = False,
robots_checker: RobotsChecker | None = None,
allow_private: bool = False,
checkpoint_sink: FetchCheckpointSink | None = None,
)

Construction does not launch the browser; the first fetch does. Reuse the instance for several URLs on the same thread, and close it with a context manager or close() when finished.

wait_for waits for a CSS selector before capture. timeout applies to individual navigation waits, not the whole fetch. For scrolling, load-more controls, and per-item expansion, see Fetching. Configure those options on your Browser instance when passing an explicit fetcher.

Wait for the quotes to appear

from analog import Browser
with Browser(wait_for=".quote") as browser:
rendered_page = browser.fetch(
"https://quotes.toscrape.com/js/"
)
print(rendered_page.status)
print(rendered_page.resolved_url)
Output
200
https://quotes.toscrape.com/js/

rendered_page.html contains the rendered HTML; this example prints the response status and final address. Waiting for .quote makes the capture wait for a quote element on this particular page.

For a structured view of the same page, see View a page.

HttpFetcher downloads HTML over HTTP and follows redirects. It does not run JavaScript or interact with the page. Choose it when the initial HTML already contains the content you need.

Minimal httpx-based fetcher — single GET, follows redirects.

Signature
HttpFetcher(
*,
timeout: float = 30.0,
user_agent: str | None = None,
robots_checker: RobotsChecker | None = None,
max_response_bytes: int | None = 26214400,
allow_private: bool = False,
)

Reuse an instance across calls and close its connection pool with a context manager or close(). timeout is in seconds; max_response_bytes limits the decompressed response body.

Both built-in fetchers consult robots.txt. A rule refusal raises RobotsTxtDisallowedError; an unreachable rules file raises RobotsTxtUnreachableError. See fetching limitations.

Download HTML without rendering JavaScript

from analog import HttpFetcher
with HttpFetcher(timeout=15) as fetcher:
static_page = fetcher.fetch("https://quotes.toscrape.com/")
print(static_page.status)
print(static_page.resolved_url)
Output
200
https://quotes.toscrape.com/

static_page.html contains the downloaded HTML. Use this site’s static / page here; its /js/ counterpart needs a browser.

For the structured response from an HTTP fetch, see Choose a fetch path.

Built-in fetchers create a checker automatically. Construct one explicitly when you need to inspect rules or share their cache across fetchers. Use the same user agent for the checker and the requests it governs.

Checks URLs against per-origin robots.txt rules.

Signature
RobotsChecker(
user_agent: str,
*,
cache_ttl_seconds: float = 86400,
unreachable_retry_seconds: float = 300,
timeout: float = 10.0,
allow_private: bool = False,
)

Raise RobotsTxtDisallowedError if robots.txt disallows this URL.

Signature
RobotsChecker.check(url: str) -> None

check() returns None when access is allowed. It raises RobotsTxtDisallowedError for a rule refusal and RobotsTxtUnreachableError when the rules cannot be determined.

The Sitemap: URLs declared by the origin’s robots.txt.

Signature
RobotsChecker.sitemaps(url: str) -> list[str] | None

sitemaps() returns the declared URLs, [] when a readable rules file has no sitemaps, or None when the rules are unavailable. These methods share a per-origin cache; they may fetch robots.txt when a cached entry is absent or has expired.

Check the quotes page

from analog import RobotsChecker
from analog.fetcher import DEFAULT_USER_AGENT
checker = RobotsChecker(DEFAULT_USER_AGENT)
url = "https://quotes.toscrape.com/"
checker.check(url)
print("Allowed by robots.txt")
print("Sitemaps:", checker.sitemaps(url))
Output
Allowed by robots.txt
Sitemaps: []

The check allowed the page; no sitemap URLs were declared. You can pass this checker as robots_checker=checker to a fetcher using the same user agent.

This authenticated call sends a report to Analog and returns a receipt. For the usual reporting flow and supported quality labels, see Feedback.

Send one feedback submission and return its durable receipt.

Signature
Client.submit_feedback(payload: FeedbackRequest) -> FeedbackResponse

A feature_request requires a non-empty note and carries no URL or labels. An extraction_quality report requires a URL and at least one supported label. It shares the submitted URL and note, without attaching page HTML or records.

Replace the placeholder with your request. Running this sends it to Analog.

Send a feature request

from analog import Client, FeedbackRequest
request = FeedbackRequest(
kind="feature_request",
note="<your-request>",
)
with Client() as client:
receipt = client.submit_feedback(request)
print(receipt.report_id)

The returned report_id identifies the recorded submission.

The examples below reuse result from View a page. They read that response locally, without fetching again. In this capture, result.structured_content[0] is the section with ten quotes. On another page, search for known content or request a preview when orientation helps.

AnalogResponse keeps record sections in structured_content, combined record views in collections, and the mixed record-and-Markdown reading order in sections, described by document_section_plan. Its outline and warnings provide page context and coverage notes. handle identifies the saved result when one was created.

result.language_observations holds raw root <html lang> declarations, in retained page or batch order. Each LanguageObservation contains its capture url and exact html_lang: None means the attribute was observed absent; "" means present but empty. A None entry means that capture was unrecorded; a None trail means no trail was saved, including older results. These are page-authored declarations, not inferred or verified languages. Saved observations do not configure browsing or extraction. Saves retain them locally; they are not added to the extraction service’s wire payload.

Use result.preview() when a page overview helps, or Section.preview() to inspect one record section’s shape. Previews show field names and types, [] for multivalued fields, and exact non-null coverage when sparse. Values remain in records and field_stats() samples.

The page preview includes Markdown regions in reading order, showing their available labels, roles, and rendered sizes. Their text remains in result.sections and result.markdown. Printed section[N] identifiers always refer to result.structured_content[N], even when prose appears before that record section.

Token-optimized page overview for LLM agents.

Signature
AnalogResponse.preview(
*,
complete: bool = False,
voice: Voice = 'python',
guidance: bool = False,
) -> str

Token-optimized overview of this section.

Signature
Section.preview(
*,
complete: bool = False,
voice: Voice = 'python',
guidance: bool = False,
) -> str

complete=True includes hidden fields in the summary; it does not turn the preview into a full record export. guidance=True may add one available next action, phrased in the selected voice: Python, shell, or MCP. Guidance is off by default and changes no field metadata, coverage notes or necessary recovery. PaginationInfo.describe() and PageSweep.describe() accept the same opt-in for their pagination advice.

Per-field statistics for this section — the data behind describe.

Signature
Section.field_stats() -> list[FieldStat]

field_stats() reports each field’s coverage and number of distinct values, including fields omitted from the preview summary. Coverage is the fraction of records with a non-null value.

Use result from Get a response, which views the quotes page.

Preview the quotes

quotes = result.structured_content[0]
print(quotes.preview())
Output
10 records · 3 fields
fields: tags:text[], text:text, text_2:text

Use the quotes section defined in Preview the quotes.

Check field coverage

for field in quotes.field_stats():
print(
f"{field.name}: coverage={field.coverage:.0%}, "
f"distinct={field.cardinality}"
)
Output
tags: coverage=100%, distinct=10
text: coverage=100%, distinct=10
text_2: coverage=100%, distinct=8

All ten quotes have an author, but some authors appear more than once.

sections follows the page’s reading order and can contain both record Sections and Markdown Sections. structured_content contains only record Sections, including navigation. The two lists need not have matching indices.

Record and Markdown Sections together in document order.

Signature
AnalogResponse.sections: list[Section | MarkdownSection]

Every extracted section classified as kind, in document order.

Signature
AnalogResponse.sections_by_kind(kind: str) -> list[Section]

The extracted section whose label matches, or None.

Signature
AnalogResponse.section(label: str) -> Section | None

Kind and label matching ignore case and surrounding whitespace. sections_by_kind() returns every matching record Section, or []. section() returns the first matching record Section, or None. An empty kind or label matches nothing. Inspect the response’s actual kinds and labels before relying on either accessor.

Inspect the available sections

for index, section in enumerate(result.sections):
kind = section.kind or "(no kind)"
print(index, type(section).__name__, kind)
navigation = result.sections_by_kind("navigation")
print("Navigation sections:", len(navigation))
named = result.section("Quotes")
print("Section named Quotes:", named)
Output
0 Section (no kind)
1 Section navigation
2 Section navigation
Navigation sections: 2
Section named Quotes: None

The quote section has no label in this response, so looking it up as "Quotes" returns None. The numbered record section remains available as result.structured_content[0].

A string performs a case-insensitive substring search. Use re.compile() to supply a regular expression and choose its flags.

Search record and Markdown Sections for matching content.

Signature
AnalogResponse.find(
pattern: str | re.Pattern[str],
field: str | None = None,
) -> list[dict[str, FieldValue]]

Page-wide hits include _source to identify where they came from. Without a field, the search can also return Markdown excerpts and matching outline labels. With a field, it searches records only and raises KeyError if no section has that field.

Search this section’s records for matching values.

Signature
Section.find(
pattern: str | re.Pattern[str],
field: str | None = None,
) -> list[dict[str, FieldValue]]

A Section search returns full matching records without adding _source. It raises KeyError when the requested field is absent from that section. Both searches return [] when nothing matches.

Find an author across the page

import json
hits = result.find("Albert Einstein", field="text_2")
print(json.dumps(hits, ensure_ascii=False, indent=2))
Output
[
{
"tags": [
"change",
"deep-thoughts",
"thinking",
"world"
],
"text": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”",
"text_2": "by Albert Einstein",
"_source": "structured_content[0]"
},
{
"tags": [
"inspirational",
"life",
"live",
"miracle",
"miracles"
],
"text": "“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”",
"text_2": "by Albert Einstein",
"_source": "structured_content[0]"
},
{
"tags": [
"adulthood",
"success",
"value"
],
"text": "“Try not to become a man of success. Rather become a man of value.”",
"text_2": "by Albert Einstein",
"_source": "structured_content[0]"
}
]

Find quotes with a topic

import json
hits = quotes.find("change", field="tags")
print(json.dumps(hits, ensure_ascii=False, indent=2))
Output
[
{
"tags": [
"change",
"deep-thoughts",
"thinking",
"world"
],
"text": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”",
"text_2": "by Albert Einstein"
}
]

This searches the values in the tags field, which holds a list in each record. It returns the full quote, author, and tags for the matching record.

Choose JSON to retain the page’s sections and their context. YAML and CSV provide a flat records view and require compatible section schemas.

Self-contained JSON serialization of the result.

Signature
AnalogResponse.to_json() -> str

The JSON includes per-section records, field information, and rendered Markdown. model_dump_json() instead serializes the underlying model without adding the rendered Markdown views.

YAML serialization of the extracted records.

Signature
AnalogResponse.to_yaml() -> str

CSV serialization of the extracted records.

Signature
AnalogResponse.to_csv() -> str

YAML and CSV raise AnalogIncompatibleSectionsError when sections cannot be pooled. That includes this example page: quote records and navigation links have different fields. To work with just the quotes, use quotes.records or a section DataFrame.

Write the complete JSON view

from pathlib import Path
_ = Path("quotes-page.json").write_text(result.to_json(), encoding="utf-8")

This creates or replaces quotes-page.json in the working directory. It writes the whole result, including the quote and navigation sections; there is no terminal output. See Working with results for choosing among record and document views.

A numeric column supplies numbers in record order while leaving the original display strings in records.

The normalized numeric column for field, aligned with records.

Signature
Section.numeric(field: str) -> list[float | None]

Normalized numeric values for each canonical record, when available.

Signature
Collection.numeric(field: str) -> list[float | None]

Each value is a number or None when that record has no parsed number. A field without a numeric column returns []. A Collection combines compatible Sections into one records view, removing repeated copies of the same record. Original placements remain in structured_content. Its numeric column follows the order of collection.records.

This illustrates a section with a numeric price field, rather than the quotes example. Check section.fields to find the available fields and which have numeric=True.

Read a price column

for record, price in zip(section.records, section.numeric("price")):
print(record["price"], price)

The first value is the source display text; the second is the number to use for comparisons. For a Collection, use collection.records and collection.numeric("price") together in the same way.

Install the optional DataFrame dependency in your Python project:

Command
uv add "analog-sdk[dataframe]"

Every record on the page as one pandas DataFrame.

Signature
AnalogResponse.to_dataframe() -> pd.DataFrame

The page-level method requires compatible schemas. It raises AnalogIncompatibleSectionsError for this example’s mixture of quotes and navigation, so choose the quote section instead.

This section’s records as a pandas DataFrame.

Signature
Section.to_dataframe() -> pd.DataFrame

Numeric columns become floats, with missing values represented by NaN. Other fields keep their values, including lists. Original display strings remain in records; column units are available through frame.attrs["units"].

Work with the quote section

frame = quotes.to_dataframe()
print(frame[["text_2"]].head(3).to_string(index=False))
Output
text_2
by Albert Einstein
by J.K. Rowling
by Albert Einstein

This displays just the first three authors. frame contains every quote record and all three fields, without the page’s navigation records.

These functions operate on the local saved-result store without calling the service or fetching a webpage. The examples below reuse result from View a page, save a new working artifact, then read, edit, and remove that artifact.

These examples start with an empty store. Your handles, history, and disk usage will differ. Choose another name if quotes-work is already taken in your store.

analog() saves by default. Call save() explicitly when you used save=False or want another saved artifact from a response you already have.

Persist response as a new artifact and return its handle.

Signature
save(
response: AnalogResponse,
*,
url: str,
fetch_settings: FetchSettings | None = None,
partial: bool = False,
name: str | None = None,
) -> str

Each call creates a new artifact and sets response.handle to its handle. It stores records and Markdown, never raw HTML. Saving can evict the least-recently-opened artifacts when the store exceeds its configured caps. Previously recorded field names and ordering for the URL may be applied to the response as it is saved.

Attach a friendly name to a saved result.

Signature
rename(handle: str, name: str) -> None

A friendly name is an alternative to the handle. Names must be unique, start with a letter or digit, and contain only letters, digits, -, or _, up to 64 characters. latest and handle-shaped names are reserved. An invalid or already-used name raises InvalidResultNameError or ResultNameInUseError respectively.

Save a working result

from analog import results
working_handle = results.save(
result, url="https://quotes.toscrape.com/js/"
)
results.rename(working_handle, "quotes-work")
print(working_handle)
Output
20260912-2zult7

working_handle refers to the new save. The original saved artifact remains available under its original handle unless normal store eviction removes it. The response object’s handle now refers to this new artifact.

Use open() with a handle, friendly name, or "latest" to restore an AnalogResponse. The returned object has the same reading APIs described in Read a response.

Re-hydrate a saved result by handle, friendly name, or "latest".

Signature
open(handle: str) -> AnalogResponse

An unknown reference raises ResultNotFoundError. Unreadable saved data raises ResultSchemaDriftError; this does not establish whether an upgrade or damaged data caused the failure. The error carries the saved handle, url, and recorded fetcher kind (None when unknown).

Recovery guidance respects that source: supplied pages need their original HTML or a new capture of the intended browser state; browser-backed saves can be fetched again. Custom fetchers must be supplied again by the caller. The store retains neither the input file nor its path. A new acquisition does not restore earlier browser state or capture settings, and opening never runs recovery automatically. A successful open updates the result’s last-opened time.

Re-hydrate the most recently created result, or None if empty.

Signature
latest() -> AnalogResponse | None

The handle of the most recently created result, or None.

Signature
latest_handle() -> str | None

These refer to the most recently created result, regardless of which one was opened last. Both return None for an empty store; open("latest") raises ResultNotFoundError instead. Use latest_handle() when you need only the identifier.

Read the named save

saved = results.open("quotes-work")
print(saved.structured_content[0].records[0]["text"])
Output
“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”

This reads the full quote from the saved records, without visiting the site.

Open the newest save if one exists

newest = results.latest()
if newest is not None:
print(newest.handle)
Output
20260912-2zult7

Here, the working result is still the newest save. Creating another result would change what latest() returns; opening an older one would not.

history() returns metadata for every saved result, newest-created first. It does not open each result or download any page.

All saved results, newest first.

Signature
history() -> list[ResultMeta]

Metadata includes the handle, friendly name, URL, creation and last-opened times, section and record counts, and recorded size. partial identifies an incomplete capture. language_observations retains the raw declaration trail, also restored on open(). It is separate from fetch_settings: observations describe captured documents, not the settings that requested them. Plain supplied HTML and fetchers that record no observations remain unrecorded; verified browser-capture files retain their observed root declaration.

The store’s disk footprint: entry count, total bytes, and the caps.

Signature
store_stats() -> StoreStats

store_stats() reports the count, recorded bytes, and effective limits. The limits honor ANALOG_RESULTS_MAX_COUNT and ANALOG_RESULTS_MAX_BYTES.

Resolve the directory holding saved result artifacts.

Signature
results_dir() -> Path

results_dir() returns a Path without creating the directory. The store lives under ANALOG_CACHE_DIR when set, otherwise under the platform’s cache directory, and ends in results/. See Working with results for storage and retention behavior.

List saved results

for meta in results.history():
print(meta.name or meta.handle, meta.records, meta.url)
Output
quotes-work 14 https://quotes.toscrape.com/js/

The count includes the ten quotes and four navigation records.

Read the size and limits

print(results.store_stats().model_dump_json(indent=2))
Output
{
"count": 1,
"bytes": 11260,
"max_count": 500,
"max_bytes": 536870912
}

bytes is the store’s recorded artifact size. The limits govern eviction when another result is saved.

These functions edit the saved artifact and return the updated response. Use the returned object, or reopen the save, to read those edits.

Rename fields on a saved result, persisted in place.

Signature
rename_fields(
handle: str,
renames: dict[str, str],
*,
sticky: bool = True,
) -> AnalogResponse

Renames apply to matching fields in every record section. An unknown source field raises KeyError.

Put order’s fields first on a saved result, persisted in place.

Signature
reorder_fields(handle: str, order: list[str], *, sticky: bool = True) -> AnalogResponse

Named fields move to the front in the supplied order; remaining fields keep their order. Sections with none of those fields stay unchanged. Unknown fields raise KeyError; duplicate names in the order raise ValueError.

Both functions default to sticky=True, recording the change for future saves of the same page. Use sticky=False to edit only this saved result, as the examples do. See Working with results for choosing field names and keeping them across visits.

Give the quote fields meaningful names

import json
renamed = results.rename_fields(
working_handle,
{"text": "quote", "text_2": "author"},
sticky=False,
)
print(json.dumps(
renamed.structured_content[0].records[0],
ensure_ascii=False,
indent=2,
))
Output
{
"tags": [
"change",
"deep-thoughts",
"thinking",
"world"
],
"quote": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”",
"author": "by Albert Einstein"
}

The original values remain intact. Field names depend on the saved response; inspect its fields before adapting this example to another page.

Put the author and quote first

ordered = results.reorder_fields(
working_handle, ["author", "quote"], sticky=False
)
print(list(ordered.structured_content[0].records[0]))
Output
['author', 'quote', 'tags']

The unspecified tags field follows the two named fields.

Deletion removes local saved artifacts. Keep any exports you want before removing the corresponding save.

Remove a saved result by handle, friendly name, or "latest".

Signature
delete(handle: str) -> None

delete() accepts a handle, friendly name, or "latest", and returns None. Deleting a well-formed handle again succeeds even if its artifact is already gone. An unknown friendly name, malformed reference, or "latest" on an empty store raises ResultNotFoundError.

Delete every saved result whose meta satisfies predicate.

Signature
delete_many(predicate: Callable[[ResultMeta], bool]) -> list[str]

The predicate receives each result’s metadata. Every matching artifact is removed; the returned list contains its handle, newest first, or [] if nothing matched. Keep the predicate limited to the saves you intend to remove.

Remove the selected saves

selected = {working_handle}
deleted = results.delete_many(
lambda meta: meta.handle in selected
)
print(deleted)
Output
['20260912-2zult7']

This selects only the working result created above. Add other handles to selected when you intend to remove more than one save.

Delete one handle

results.delete(working_handle)

There is no output. The preceding example already removed this artifact; deleting it again by handle succeeds. Using the removed friendly name "quotes-work" here would raise ResultNotFoundError.

  • Primary calls: analog, assess

  • Client and fetching: Browser, BrowserRecipe, Client, Fetcher, FetchResult, HttpFetcher, RobotsChecker

  • Results and response models: AnalogResponse, BrowseAction, Collection, DocumentReference, DocumentSectionReference, ErrorResponse, ExtractRequest, FeedbackRequest, FeedbackResponse, FieldInfo, FieldStat, FieldValue, FitAssessment, InfoResponse, LanguageObservation, MarkdownSection, OutlineNode, OutlineReference, PageSweep, PaginationInfo, RateLimitInfo, ResponseWarning, Section, StructuredContentReference

  • Saved results: FetchSettings, ResultMeta, StoreStats, delete, delete_many, history, latest, latest_handle, open, rename, rename_fields, reorder_fields, results_dir, save, store_stats

  • Exceptions: AnalogAPIError, AnalogAccountDeactivatedError, AnalogAuthError, AnalogConnectionError, AnalogError, AnalogIncompatibleSectionsError, AnalogRateLimitError, AnalogVersionSkewError, FetchBlockedError, FetchError, FetchStatusError, InvalidResultNameError, ResultNameInUseError, ResultNotFoundError, ResultSchemaDriftError, RobotsTxtDisallowedError, RobotsTxtUnreachableError, UrlNotAllowedError

  • Constants and progress: DEFAULT_ROBOTS_CACHE_TTL_SECONDS, DEFAULT_UNREACHABLE_RETRY_SECONDS, FEEDBACK_LABELS, SCHEMA_VERSION, __version__, noop_progress, set_progress