Skip to main content

ironfang-render

The official Python client for the Ironfang Render API: screenshots, PDFs, QR codes, template images, clips and site previews from a URL or from HTML you send. No dependencies; Python 3.10 and later.

Formerly ironfang-renderwolf; 2.0.0 removed ironfang_renderwolf and the old Renderwolf and RenderwolfError names. Webhooks are signed with the Ironfang-* headers.

3.0.0 follows Ironfang's move from credits to metered usage on one billing account: a render reports meter and quantity instead of credits, a job reports usage instead of credits, usage() returns the month per meter, request history records meter and quantity, and billing refusals raise IronfangBillingError. Renders accept idempotency_key.

3.0.1 fixes verify_webhook for Render: it used the text of the signing secret as the HMAC key, while a Render secret is the hex of the 32-byte key deliveries are signed with, so it rejected every genuine delivery. Upgrade if you verify Render webhooks with it.

3.1.0 adds capture bundles (bundle()), stored document templates, and each render's diagnostics. Additions only; see the changelog.

pip install ironfang-render
import os
from ironfang_render import IronfangRender

with IronfangRender(api_key=os.environ["IRONFANG_API_KEY"]) as rw:
    shot = rw.screenshot(url="https://example.com", full_page=True)
    shot.save("page.png")
    print(shot.meter, shot.quantity, shot.render_ms, shot.request_id)

Every render returns the bytes plus what the API reports about them: content type, the billing meter it counted against and the quantity it counted there (zero on a cache hit), render time and the request id to quote to support. Long outputs stream: save() and iter_bytes() never hold a whole clip in memory.

Change the page before capture

rw.screenshot(
    html="<div class='chat-widget'>Chat</div><details id='details'><summary id='show-more'>Pricing</summary><p>Expanded pricing</p></details>",
    css=".chat-widget { display: none !important; }",
    script="document.querySelector('#show-more').textContent = 'Show pricing';",
    actions=[
        {"type": "click", "selector": "#show-more"},
        {"type": "wait_for_selector", "selector": "#details[open]"},
    ],
).save("expanded.png")

The same options work with rw.pdf() and URL input. After normal load/waits, Ironfang Render applies CSS, awaits the script, then executes actions in order before settling and capture. JavaScript runs in the target page context as an async function body; use await or return a promise for asynchronous work. It has a five-second limit (or the remaining render timeout, if shorter).

CSS is limited to 64 KiB of UTF-8, script to 16 KiB, and actions to 20. Actions are click, hover, wait_for_selector (presence), and delay with duration_ms; combined delays may total at most 10,000 ms. Selectors are limited to 1,024 UTF-8 bytes and share the render timeout. Invalid input returns bad_request; script/action execution errors return sanitized render_failed messages. A screenshot cache hit does not re-execute controls; use no_cache for fresh execution. Full reference.

Lazy content, success rules, language and clock

Every request field is a keyword argument, so everything the API accepts works as written in the reference:

shot = rw.screenshot(
    url="https://example.com/pricing",
    full_page=True,
    full_page_readiness="scroll",            # load lazy content first
    success_rules={"fail_on_status": ["4xx", "5xx"], "forbid_text": ["Access denied"]},
    locale="de-DE",                          # Intl, navigator.language, Accept-Language
    timezone="Europe/Berlin",                # the page's clock
)
print(shot.diagnostics)  # {'target_status': 200, 'locale': 'de-DE', 'timezone': 'Europe/Berlin', ...}

A failed success rule raises IronfangRenderError with code success_rule_failed and is not charged. PDFs take paper_width and paper_height ("210mm"), page_ranges ("1-3, 5"), prefer_css_page_size and metadata.

Capture bundles

One page load, the outputs you choose:

b = rw.bundle(url="https://example.com/blog/post", outputs=["markdown", "metadata"])
print(b.metadata["title"], b.markdown)
for o in b.manifest["outputs"]:
    if o["status"] != "included":
        print(o["name"], o["status"], o.get("reason"))

Counts one render.screenshot. Captured HTML and text are the page's own content: treat them as untrusted.

Document templates

tpl = rw.create_document_template(name="invoice", source="<h1>Invoice {{.number}}</h1>",
                                  schema={"type": "object", "required": ["number"],
                                          "properties": {"number": {"type": "string"}}})
rw.publish_document_template(tpl["id"])
rw.render_document(tpl["id"], {"number": "INV-0042"}).save("invoice.pdf")

validate_document_template checks data without printing; publish_document_revision rolls back to an earlier revision.

Durable jobs

job = rw.submit_job("pdf", {"html": invoice_html, "paper_format": "a4"}, idempotency_key=f"invoice-{invoice.id}")
done = rw.wait_for_job(job["id"])
if done["status"] == "succeeded":
    rw.job_result(job["id"]).save(f"invoice-{invoice.id}.pdf")

The job's usage says what it holds: {"meter", "reserved", "committed"}. The most the job can count is reserved when it is accepted, and what it produced is committed when it succeeds. A job that fails or is cancelled while queued counts nothing.

Usage and billing

Ironfang Render has no plans or credits. Usage is billed to your organisation's Ironfang billing account, shared by every Ironfang product and managed in Billing in the Ironfang portal. Each kind of render counts against a meter, and each meter has a monthly free allowance; beyond it, usage is pay-as-you-go once paid usage is enabled.

usage = rw.usage()
for m in usage.get("meters", []):
    print(m["id"], m["used"], m["remaining_free"])

Verifying a webhook

Run the check over the raw request body, before parsing it. Pass the signing secret exactly as it was shown when the destination was created: a Render (or Finance) secret is 64 hex characters, and the check decodes it to the 32-byte key; an Ironfang Audit secret (awsec_...) is used as it is.

from ironfang_render import verify_webhook

ok = verify_webhook(
    secret=os.environ["RENDER_WEBHOOK_SECRET"],
    timestamp=request.headers["Ironfang-Timestamp"],
    signature=request.headers["Ironfang-Signature"],
    payload=request.get_data(),
)

Errors

Failures raise IronfangRenderError with status, the API's code (render_failed, rate_limited, ...), request_id and, when the API sent one, retry_after in seconds. Branch on code rather than the status: rate limits and billing refusals can share a 429.

When the billing account refuses a render, nothing is rendered or counted and the error is an IronfangBillingError (a subclass) with product, and meter and reset_at when the API states them. The codes are free_allowance_exhausted, account_budget_exhausted, product_budget_exhausted, exemption_limit_reached, paid_usage_paused, payment_required, payment_action_required, account_restricted, product_not_eligible, account_closed, operation_too_large and billing_temporarily_unavailable. Only the last clears by itself, after retry_after; the rest are resolved in Billing or when the month rolls over.

from ironfang_render import IronfangBillingError

try:
    rw.screenshot(url="https://example.com")
except IronfangBillingError as err:
    print(err.code, err.reset_at)

Synchronous renders are never retried by the client: a render that timed out may already have counted, and retrying it blind counts it twice. Pass idempotency_key to make a render count at most once; the same key again is refused with idempotency_key_used, since a synchronous result is not kept.

Metadata

Release files for ironfang-render 3.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ironfang-render 3.1.0
File Size Uploaded Provenance
ironfang_render-3.1.0.tar.gz 21.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ironfang-render 3.1.0
File Interpreter ABI Platform
ironfang_render-3.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.3 kB

Release files / ironfang_render-3.1.0.tar.gz

Download URL ironfang_render-3.1.0.tar.gz
Size 21.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e5add6cf73e5264d23ccfd5079f51e90da95d9762fba05d2f37f9cdeb4a38625
BLAKE2b-256 checksum
How to use checksums
f054d656128d5594ec72b582b80d945c2c5b2fcb31fd103d56937ba672328466
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / ironfang_render-3.1.0-py3-none-any.whl

Download URL ironfang_render-3.1.0-py3-none-any.whl
Size 14.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2a72da058437b2a490649bd35e5f3d08a67bd0d4be3cae7e3b223ea67160a1a1
BLAKE2b-256 checksum
How to use checksums
41dab21db8f22a56d177e61e48b2718e3474a924e2d242c1b4f99740851639e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.14

Security for ironfang-render 3.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page