Python SDK
Updated
The Python package is named faxforagents and has been on PyPI since 2026-10-30. Install it with pip install faxforagents. Python 3.10 or later, two dependencies, httpx and pydantic. The REST calls further down work with httpx or requests as well, so nothing here depends on the package.
Status
Published. The package is built from the same OpenAPI document as this site and released through PyPI trusted publishing. The quickstart below is the one from the package's README. The source is at github.com/faxforagents/sdk-python and the package page is pypi.org/project/faxforagents.
Install and send with the package
pip install faxforagents # the client
pip install "faxforagents[cli]" # plus the faxforagents command import os
from faxforagents import Client
os.environ["FAXFORAGENTS_API_KEY"] = "ffa_test_..." # or export it in the shell
with Client() as ffa:
doc = ffa.documents.create(text="Hello from an agent.", filename="hello")
print(doc.id, doc.page_count, doc.sha256[:12])
quote = ffa.quotes.create(document_id=doc.id, to="+15550000001")
print(quote.amount_micro, "micro, expires", quote.expires_at)
fax = ffa.faxes.create(quote_id=quote.id) # Idempotency-Key minted for you
print(fax.id, fax.state) # queued, never delivered yet
fax = ffa.faxes.wait(fax.id) # polls until a terminal state
print(fax.state, fax.pages_sent, fax.evidence) # delivered, 1, {...}
for job in ffa.faxes.iter(state="failed"): # follows next_cursor
print(job.id, job.failure_code) Send a fax with httpx
import httpx
API = "https://api.faxforagents.com/v1"
KEY = "ffa_test_..." # from the dashboard, never in source control
h = {"Authorization": f"Bearer {KEY}"}
with httpx.Client(base_url=API, headers=h, timeout=30) as c:
doc = c.post("/documents", json={"markdown": "# PO 4411\n\nPlease confirm."}).raise_for_status().json()
quote = c.post("/fax-quotes", json={"document_id": doc["id"], "to": "+15550000001"}).raise_for_status().json()
job = c.post("/faxes", json={"quote_id": quote["id"]}, headers={"Idempotency-Key": "po-4411"}).raise_for_status().json()
print(job["id"], job["state"]) # fax_..., queued Upload a PDF
size = path.stat().st_size
up = c.post("/uploads", json={"filename": path.name, "size_bytes": size}).raise_for_status().json()
httpx.put(up["upload_url"], content=path.read_bytes(), timeout=120).raise_for_status()
c.post(f"/uploads/{up['id']}/complete").raise_for_status()
doc = c.post("/documents", json={"upload_id": up["id"]}).raise_for_status().json() How do I page through jobs?
cursor = None
while True:
page = c.get("/faxes", params={"limit": 50, "cursor": cursor}).raise_for_status().json()
for job in page["items"]:
print(job["id"], job["state"])
cursor = page.get("next_cursor")
if not cursor:
break How do I verify a webhook?
The reference verifier is on the webhooks page. The package ships the same function as faxforagents.webhooks.verify.
Errors
Every non-2xx body is the error envelope. Read error.code and error.retryable. Retry a retryable call with the same Idempotency-Key. The errors page lists every code.