# Scrapfly's Python SDK against SpecStream

Date: 2026-11-10. Client: scrapfly-sdk 0.12.0.

## The one line a user changes

Before:

```
client = ScrapflyClient(key="...")
```

After:

```
client = ScrapflyClient(key=KEY, host="https://api.specstream.ai/scrapfly")
```

One call is one Extract API page read at $0.0008; a failed page is not charged.
A request with no credits left answers 429 ERR::SCRAPE::QUOTA_LIMIT_REACHED.

## Request parameters

Honoured means the check in the Measured column passed on the reply. Accepted, not applied means status 200 and the reply has the form of the same call without the parameter. Refused means the vendor's error body and status.

Counts: 7 honoured, 30 accepted, not applied, 2 refused.

| Parameter | Sent | Verdict | Measured |
|---|---|---|---|
| `url` | `https://en.wikipedia.org/wiki/Web_scraping` | honoured | status 200; result.content is the page's HTML |
| `key` | the key in the query, as the SDK sends it | honoured | status 200; answered with the page |
| `format` | `markdown` | honoured | status 200; result.content is markdown |
| `format` | `text` | honoured | status 200; result.content is plain text |
| `format options` | `markdown:no_links` | honoured | status 200; no links in the markdown |
| `proxified_response` | `true` | honoured | status 200; the body is the page itself |
| `url (a page the site does not have)` | a 404 page | honoured | status 404; the SDK raises UpstreamHttpClientError ERR::SCRAPE::BAD_UPSTREAM_RESPONSE |
| `url (a host that does not resolve)` | an unknown host | refused | status 422; the SDK raises ApiHttpClientError |
| `render_js` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `asp` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `country` | `de` | accepted, not applied | status 200; the reply is the baseline's page |
| `proxy_pool` | `public_residential_pool` | accepted, not applied | status 200; the reply is the baseline's page |
| `lang` | `['de']` | accepted, not applied | status 200; the reply is the baseline's page |
| `os` | `linux` | accepted, not applied | status 200; the reply is the baseline's page |
| `headers` | `{'x-test': '1'}` | accepted, not applied | status 200; the reply is the baseline's page |
| `cookies` | `{'a': '1'}` | accepted, not applied | status 200; the reply is the baseline's page |
| `timeout` | `60000` | accepted, not applied | status 200; the reply is the baseline's page |
| `retry` | `False` | accepted, not applied | status 200; the reply is the baseline's page |
| `cache` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `cache_ttl` | `600` | accepted, not applied | status 200; the reply is the baseline's page |
| `session` | `proof` | accepted, not applied | status 200; the reply is the baseline's page |
| `js` | `return 1` | accepted, not applied | status 200; the reply is the baseline's page |
| `js_scenario` | `[{'wait': 500}]` | accepted, not applied | status 200; the reply is the baseline's page |
| `rendering_wait` | `500` | accepted, not applied | status 200; the reply is the baseline's page |
| `rendering_stage` | `domcontentloaded` | accepted, not applied | status 200; the reply is the baseline's page |
| `wait_for_selector` | `#content` | accepted, not applied | status 200; the reply is the baseline's page |
| `auto_scroll` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `screenshots` | `{'all': 'fullpage'}` | accepted, not applied | status 200; the reply is the baseline's page |
| `extraction_prompt` | `who wrote it` | accepted, not applied | status 200; the reply is the baseline's page |
| `tags` | `['proof']` | accepted, not applied | status 200; the reply is the baseline's page |
| `debug` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `ssl` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `dns` | `True` | accepted, not applied | status 200; the reply is the baseline's page |
| `cost_budget` | `30` | accepted, not applied | status 200; the reply is the baseline's page |
| `format` | `clean_html` | accepted, not applied | status 200; the reply is the baseline's page |
| `format` | `json` | accepted, not applied | status 200; the reply is the baseline's page |
| `browser_brand` | `chrome` | accepted, not applied | status 200; the reply is the baseline's page |
| `correlation_id` | `proof-1` | accepted, not applied | status 200; the reply is the baseline's page |
| `key (wrong key)` | a key that is not one | refused | status 401; the SDK raises ApiHttpClientError |

## Response fields

Present means the field holds a value in the replies to the calls above. Empty means it is there with no value. Absent means it is not in the reply.

Counts: 15 present, 5 empty, 10 absent.

| Field | State | Measured |
|---|---|---|
| `uuid` | present | every reply the SDK parsed |
| `config` | present | every reply the SDK parsed |
| `context` | empty | every reply the SDK parsed |
| `result` | present | every reply the SDK parsed |
| `config.url` | present | every reply |
| `config.headers` | empty | every reply |
| `config.format` | present | every reply |
| `result.status` | present | every reply |
| `result.success` | present | every reply |
| `result.status_code` | present | every reply |
| `result.reason` | present | every reply |
| `result.format` | present | every reply |
| `result.content` | present | every reply |
| `result.content_type` | present | every reply |
| `result.url` | present | every reply |
| `result.size` | present | every reply |
| `result.duration` | present | every reply |
| `result.log_url` | empty | every reply |
| `result.error` | empty | every reply |
| `result.request_headers` | absent | every reply |
| `result.response_headers` | absent | every reply |
| `result.cookies` | absent | every reply |
| `result.screenshots` | absent | every reply |
| `result.iframes` | absent | every reply |
| `result.browser_data` | absent | every reply |
| `result.extracted_data` | absent | every reply |
| `result.dns` | absent | every reply |
| `result.ssl` | absent | every reply |
| `result.content_encoding` | absent | every reply |
| `context.*` | empty | every reply |
