Python SDK
pip install wellmarked — sync and async clients, typed responses, typed exceptions, and automatic job polling.
pip install wellmarkedThe official Python client wraps every endpoint with typed responses, sync and async clients, and HTTP status codes mapped onto typed exceptions.
Quickstart
from wellmarked import WellMarked
with WellMarked(api_key="wm_...") as wm:
result = wm.extract("https://example.com")
print(result.markdown)
print(result.metadata.title, "by", result.metadata.author)The client is a context manager so the underlying HTTP connection pool is closed
deterministically. api_key falls back to the WELLMARKED_API_KEY environment variable.
The surface
from wellmarked import WellMarked
with WellMarked(api_key="wm_...") as wm:
# Single extract
result = wm.extract("https://example.com")
# Bulk
job = wm.bulk(["https://a.example", "https://b.example"])
job = wm.wait_for_job(job.job_id)
# Crawl — the same wait_for_job works on either kind of job_id
job = wm.crawl("https://docs.example.com", depth=2)
job = wm.wait_for_job(job.job_id)
# Usage + rotate
usage = wm.get_usage()
rotated = wm.rotate_key() # the client swaps to the new key for youwait_for_job is polymorphic
It returns a BulkJob or a CrawlJob based on the API's kind discriminator. Check
isinstance(job, CrawlJob) or job.kind before reading crawl-specific fields such as
truncated or item.depth.
Async
AsyncWellMarked has the same method names.
from wellmarked import AsyncWellMarked
async with AsyncWellMarked(api_key="wm_...") as wm:
result = await wm.extract("https://example.com")Errors
Exceptions inherit from WellMarkedError. The ones you will actually catch:
| Exception | Status |
|---|---|
AuthenticationError | 401 |
PermissionDeniedError | 403 |
NotFoundError | 404 |
UnprocessableEntityError | 422 |
RateLimitError | 429 |
InternalServerError | 5xx |
APIConnectionError | transport failure, no response |
Each carries code, status_code, retry_after, retry_after_ms, and request_id.
import time
from wellmarked import WellMarked, RateLimitError
with WellMarked(api_key="wm_...") as wm:
try:
result = wm.extract("https://example.com")
except RateLimitError as e:
if e.code == "rate_limit_too_fast":
time.sleep(e.retry_after_ms / 1000)
else:
... # monthly quota spent — retrying will not helpSee Errors for every code the API can emit.
Retries and idempotency
A submission that fails on a retryable error is retried internally reusing the same
idempotency key, so an SDK-level retry can never double-submit. Tune the attempt count with
max_retries (default 2).
Webhook verification
from wellmarked import verify_webhook, WebhookVerificationError
payload = verify_webhook(
secret=os.environ["WELLMARKED_WEBHOOK_SECRET"],
headers=request.headers, # case-insensitive; dict works too
body=raw_body_bytes, # raw bytes, NOT a parsed object
)All arguments are keyword-only. See Webhooks.
Full reference
Current Stripe-backed plan prices and rate limits
Return the current price, included request allowance and overage rate for every plan. **Public — no authentication required.** Numbers come from Stripe and are cached for 24 hours, so this is the authoritative source for what the API actually charges rather than a hardcoded table. Check `source` before displaying: `stripe` or `cache` are live, while `fallback` means Stripe was unreachable and the cache was empty, so the values are built-in defaults that may be stale. All money is in **cents**, and `overage_unit_cents` is fractional — `0.35` means $0.0035 per request.
JavaScript / TypeScript SDK
npm install wellmarked — dual ESM and CJS, bundled types, runs on Node, Deno, Bun, Cloudflare Workers, and the browser.