Python SDK
Sync and async Python clients for the ScrapingIsNotACrime API, built on httpx, with responses typed as TypedDict.
01Install
pip install scrapingisnotacrime
02Quick start
from scrapingisnotacrime import ScrapingIsNotACrime
with ScrapingIsNotACrime() as client:
profile = client.instagram.profile("nasa")
print(profile["username"], profile["followers"])Get a key at your dashboard. Keys start with sinac_, and new accounts get 100 free credits.
Both clients read SCRAPINGISNOTACRIME_API_KEY from the environment, or take it as api_key=.
03Configuration
ScrapingIsNotACrime(
api_key="sinac_...",
base_url="https://api.scrapingisnotacrime.com/v1",
timeout=30.0,
max_retries=2,
http_client=None,
)| Option | Default | Description |
|---|---|---|
api_key | SCRAPINGISNOTACRIME_API_KEY | Your API key. Raises ValueError at construction if missing. Defaults to the SCRAPINGISNOTACRIME_API_KEY environment variable. |
base_url | https://api.scrapingisnotacrime.com/v1 | API base URL. Redirects are not followed, so use the final HTTPS URL. |
timeout | 30.0 | Per attempt, in seconds. |
max_retries | 2 | Extra attempts for 429, 502 and network errors. 0 disables retries. |
http_client | httpx.Client | Bring your own httpx.Client for tests, proxies or connection pooling. Defaults to a new httpx.Client (httpx.AsyncClient for the async client). |
AsyncScrapingIsNotACrime takes the same arguments; http_client accepts an httpx.AsyncClient.
04Methods
Methods are grouped by platform under client.<platform>, in snake_case, and return the response's data as a typed dict. Optional arguments are keyword-only.
05Pagination
Paginated methods return a Page (or AsyncPage) with items, has_more, next_cursor or next_page, data and next(). Iterating it fetches further pages lazily.
Each page fetched is one billed request, so bound your loops. Breaking out early stops fetching.
page = client.github.followers("torvalds", limit=100)
for count, user in enumerate(page, start=1):
print(user["username"])
if count >= 100:
break # stop early; no further pages are fetchedpage = await client.bluesky.posts("bsky.app", limit=25)
count = 0
async for post in page:
print(post)
count += 1
if count >= 100:
break # stop early; no further pages are fetched06Errors
Every failure raises a subclass of ScrapingIsNotACrimeError with status, message and request_id. ConnectionError is the SDK's own class, not the builtin.
| Type | Status | Retried |
|---|---|---|
BadRequestError | 400 | no |
AuthenticationError | 401 | no |
QuotaExceededError | 402 | no |
NotFoundError | 404 | no |
RateLimitError | 429 | yes |
UpstreamError | 502 | yes |
ConnectionError | network failure or timeout | yes |
APIError | any other non-2xx, or a 2xx without the JSON envelope | no |
from scrapingisnotacrime import (
NotFoundError,
QuotaExceededError,
RateLimitError,
ScrapingIsNotACrime,
)
client = ScrapingIsNotACrime(max_retries=3)
try:
client.tiktok.profile("this-user-does-not-exist-123")
except NotFoundError:
print("No such profile.")
except QuotaExceededError as error:
print("Out of credits:", error.message)
except RateLimitError:
print("TikTok is rate limiting; try again later.")07Retries
Rate limits (429), upstream failures (502) and network failures or timeouts are retried automatically, up to 2 extra attempts by default. These failures don't consume credits. The wait honors the Retry-After header; otherwise it is exponential backoff with jitter, starting at 500 ms and capped at 10 seconds.
Set max_retries=0 to disable retries.
08Async client
AsyncScrapingIsNotACrime exposes the same namespaces and methods as awaitable coroutines. It requires asyncio and does not run under trio.
import asyncio
from scrapingisnotacrime import AsyncScrapingIsNotACrime
async def main() -> None:
async with AsyncScrapingIsNotACrime() as client:
profile = await client.instagram.profile("nasa")
print(profile["username"], profile["followers"])
asyncio.run(main())