bytekit-sdk
Official Python SDK for the ByteKit API.
The PyPI distribution is bytekit-sdk; the import name is bytekit.
Generated from the OpenAPI spec using openapi-python-client, filtered to the stable v0.1 operations.
Installation
pip install bytekit-sdk
The installed version is available as bytekit.__version__.
Quick start
from bytekit import AuthenticatedClient
from bytekit.api.scrape import create_scrape
from bytekit.models.scrape_request import ScrapeRequest
from bytekit.models.scrape_request_formats_item import ScrapeRequestFormatsItem
from bytekit.models.scrape_success_envelope import ScrapeSuccessEnvelope
# base_url defaults to https://api.bytekit.com, so only the token is required.
client = AuthenticatedClient(token="sk_live_your_api_key_here")
result = create_scrape.sync(
client=client,
body=ScrapeRequest(
url="https://example.com",
# Ask for markdown explicitly. `formats` is a list of enum members, not plain
# strings. Omit it and the server returns raw HTML instead, leaving
# `formats.markdown` unset.
formats=[ScrapeRequestFormatsItem.MARKDOWN],
),
)
if isinstance(result, ScrapeSuccessEnvelope):
print(result.formats.markdown)
See Error handling for what result can be when the request fails.
Error handling
The SDK has a two-tier error contract. Both tiers are safe: an operation never raises
a bare json.JSONDecodeError or a bare KeyError, even when a server returns an HTML error
page or a payload whose shape has drifted from the spec.
| Situation | What you get |
|---|---|
A status the OpenAPI spec documents for that operation (e.g. a 422 or 500 on create_scrape), with a JSON body of the documented shape |
The typed Error model is returned, not raised. Check with isinstance(result, Error) and read result.error.code / result.error.message. |
| An undocumented status, or a non-JSON body on a documented status (HTML error page, load-balancer text, empty body) | errors.UnexpectedStatus is raised, carrying .status_code and the raw .content. When the body is the documented Error envelope, .code / .message are populated too. |
| A documented status whose JSON body does not match the documented schema — a field the server renamed, dropped or retyped | errors.UnexpectedStatus is raised, same shape as the row above. The underlying parse failure (e.g. KeyError: 'schema_version') is attached as __cause__ for diagnosis. |
Any of the above, with raise_on_unexpected_status=False |
Nothing is raised; the operation returns None. |
The table describes the generated operations (create_scrape.sync, get_usage.sync, …),
whose return types are Optional[...] accordingly. The hand-written
AuthenticatedClient.search(...) convenience method is deliberately outside it: it returns a
non-Optional CreateSearchResponse200 and raises errors.UnexpectedStatus on anything else
— including documented error statuses, and including a malformed 200 — in both raise
modes. raise_on_unexpected_status does not apply to it.
from bytekit import AuthenticatedClient
from bytekit.api.scrape import create_scrape
from bytekit.errors import UnexpectedStatus
from bytekit.models.error import Error
from bytekit.models.scrape_request import ScrapeRequest
from bytekit.models.scrape_request_formats_item import ScrapeRequestFormatsItem
from bytekit.models.scrape_success_envelope import ScrapeSuccessEnvelope
client = AuthenticatedClient(token="sk_live_your_api_key_here")
try:
result = create_scrape.sync(
client=client,
body=ScrapeRequest(
url="https://example.com",
# Ask for markdown explicitly — omit `formats` and the server returns raw HTML,
# leaving `formats.markdown` unset.
formats=[ScrapeRequestFormatsItem.MARKDOWN],
),
)
except UnexpectedStatus as err:
# Undocumented status, or a non-JSON body on a documented one. Never a JSONDecodeError.
print(f"request failed with status {err.status_code}: {err.code} {err.message}")
else:
if isinstance(result, Error):
# Documented 4xx/5xx with a JSON body: RETURNED as a typed model, not raised.
print(f"api error {result.error.code}: {result.error.message}")
elif isinstance(result, ScrapeSuccessEnvelope):
print(result.formats.markdown)
else:
# 202 ScrapeQueuedEnvelope — poll get_scrape with this id until it completes.
print(f"queued as {result.id}")
Defaults
The client ships with production-ready defaults so AuthenticatedClient(token=...) works out of the box:
| Setting | Default | Notes |
|---|---|---|
base_url |
https://api.bytekit.com |
Pass base_url= to target staging or a proxy. |
timeout |
120s (httpx.Timeout(120.0)) |
Finite by default — requests no longer hang indefinitely. Pass timeout= to override. |
raise_on_unexpected_status |
True |
Undocumented statuses raise errors.UnexpectedStatus instead of silently returning None. Pass raise_on_unexpected_status=False to restore the old opt-out. |
Explicit constructor arguments always win over these defaults (explicit arg > default).
Every constructor argument is keyword-only
AuthenticatedClient accepts no positional arguments. token, base_url, prefix,
auth_header_name and the rest are all passed by name:
client = AuthenticatedClient(base_url="https://api.bytekit.com", token="sk_live_your_api_key_here")
Migrating from a pre-0.3.0 positional call. base_url used to be the first positional
argument, so AuthenticatedClient("https://…", "sk_live_…") was a documented call. Once
base_url became a defaulted keyword argument, that same call silently bound the URL to
token and the API key to prefix — producing an Authorization: sk_live_… https://…
header sent to the default host, i.e. a wrong credential against production with nothing
raised. Since 0.3.5 it raises TypeError instead. Add the keywords; nothing else changes.
Async usage
import asyncio
from bytekit import AuthenticatedClient
from bytekit.api.screenshots import create_screenshot
from bytekit.models.screenshot_request import ScreenshotRequest
async def main():
client = AuthenticatedClient(token="sk_live_your_api_key_here")
body = ScreenshotRequest(url="https://example.com")
response = await create_screenshot.asyncio(client=client, body=body)
print(response)
asyncio.run(main())
Clients and event loops
An httpx.AsyncClient — and therefore its connection pool — belongs to the event loop that
created it. The recommended shape is a context manager, which scopes the client to exactly one
loop:
async def main():
async with AuthenticatedClient(token="sk_live_your_api_key_here") as client:
...
Two rules cover everything else:
- A client the SDK builds for you is rebuilt automatically. If you reuse one client across
several
asyncio.run(...)calls, the SDK notices the running loop has changed and transparently replaces its internalAsyncClient. Yourbase_url, headers, timeout,httpx_argsand authentication are all re-applied, so this is invisible apart from a new connection. Callingget_async_httpx_client()outside any running loop returns the cached client unchanged. - A client you pass to
set_async_httpx_client(...)is yours. The SDK never rebuilds or closes it, so a client you supply must be created and used on the same loop — that is the one case where crossing loops is still your responsibility.
Available operations
| Module | Method | Description |
|---|---|---|
api.scrape |
create_scrape, get_scrape |
Web content extraction |
api.screenshots |
create_screenshot, get_screenshot |
Page screenshots |
api.bulk |
create_bulk, get_bulk, delete_bulk, list_bulk_screenshots |
Bulk screenshot jobs |
api.scrape_bulk |
create_scrape_bulk, get_scrape_bulk |
Bulk scrape jobs |
api.fetch |
get_fetch, post_fetch |
Raw HTTP fetch |
api.fetch_bulk |
create_fetch_bulk, get_fetch_bulk |
Bulk fetch jobs |
api.monitors |
create_monitor, list_monitors, get_monitor, update_monitor, delete_monitor, list_monitor_captures |
Page-change monitors (screenshot + scrape) |
api.sitemap |
create_sitemap, get_sitemap |
Sitemap crawl |
api.search |
create_search (or the AuthenticatedClient.search(...) convenience method) |
Web search |
api.usage |
get_usage, get_usage_daily, get_usage_by_endpoint |
Account usage & billing |
api.webhooks |
list_webhook_deliveries, retry_webhook_delivery |
Webhook delivery log & retry |
api.account |
get_account |
Account details |
License
MIT — see LICENSE.
Release files for bytekit-sdk 0.3.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bytekit_sdk-0.3.8.tar.gz | 107.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bytekit_sdk-0.3.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 337.1 kB
Release files / bytekit_sdk-0.3.8.tar.gz
| Download URL | bytekit_sdk-0.3.8.tar.gz |
|---|---|
| Size | 107.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b4efc19348e8868e11bdc670a41c9d528053639fc56bb5ce580e1c1de4fdf1ee
|
|
BLAKE2b-256 checksum How to use checksums |
19f41f1322d1f4e94c5d811e9057ec95189cc2f10b422e26ce5370efe6403cf8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / bytekit_sdk-0.3.8-py3-none-any.whl
| Download URL | bytekit_sdk-0.3.8-py3-none-any.whl |
|---|---|
| Size | 229.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77ede6a72c3e8ad42b562d0cc6daba89079ba222382fca9a128aa7594b885748
|
|
BLAKE2b-256 checksum How to use checksums |
41679b271371058b8a70353ebf1edccd80311445183004320f0ce33301ba1721
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|