faxforagents
The Python client for Fax for Agents, the fax API built for AI agents.
Website · Quickstart · API reference · SDKs · MCP · Dashboard · Pricing · Changelog
Fax for Agents is a fax API, a hosted MCP server and SDKs built so AI agents can send and receive faxes. It is made by JSB Holdings LLC.
pip install faxforagents # the client
pip install "faxforagents[cli]" # plus the faxforagents command
Python 3.10 or later. Two dependencies, httpx and pydantic.
Quickstart, against the sandbox
A test key (ffa_test_...) reaches the sandbox provider only. No carrier, no cost, nothing leaves the
box. The destination number picks the outcome. Keys are created in the dashboard at
https://app.faxforagents.com or with POST /v1/api-keys.
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)
The same in async form, with AsyncClient and await on every call.
What the client does for you
- Every mutating call carries an
Idempotency-Key. When you pass none, a uuid4 is minted once per call and reused on every retry, so a retried send never creates a second fax. Passidempotency_key=to pick your own. - 429 and 5xx answers are retried up to three times.
Retry-Afteris honoured. Connection errors and timeouts are retried the same way. - Every list has an
itertwin that followsnext_cursor. documents.create(text=...),markdown=...orhtml=...renders on the server. HTML prints with scripts off and no network, inline CSS anddata:images only, at most 2 MiB.documents.upload(path)runs the whole upload path for a PDF or an image (PNG, JPG, TIFF): the upload session, the signed PUT with the content type read from the filename, the sha256 check, the document. An image becomes gray letter pages.faxes.wait(fax_id)andapprovals.wait(approval_id)poll with a timeout.verify_webhook(secret, headers, body)checks the Standard Webhooks signature on a delivery.- Models are pydantic v2, generated from the public OpenAPI document. Money is an integer count of
USD micro-units (
39000is $0.039). Timestamps are UTC strings.
Resources
Method names are the operationIds of the OpenAPI document, shortened by their resource. The reference at https://faxforagents.github.io/openapi/ names the scope each call needs.
| Resource | Calls |
|---|---|
ffa.identity |
me, list_api_keys, create_api_key, rotate_api_key, revoke_api_key |
ffa.documents |
create, upload, create_upload, put_upload_content, complete_upload, cancel_upload, list, iter, get, delete, get_validation, get_download, download, list_pages |
ffa.quotes |
create, get |
ffa.faxes |
create, list, iter, get, wait, list_events, list_attempts, get_receipt, cancel |
ffa.webhooks |
list, create, update, delete, rotate_secret, test, list_deliveries, replay_delivery |
ffa.events |
list, iter |
ffa.billing |
balance, list_ledger, settings, top-ups, auto top-ups, budgets, usage, exports, invoices, subscriptions, create_portal_session |
ffa.platform |
capabilities, service_status |
ffa.sandbox |
list_scenarios, create_scenario, create_inbound_fax, reset |
ffa.approvals |
request, request_help, list, iter, get, wait, approve, reject, revoke |
ffa.policies |
list, create, effective, get, update, list_versions, evaluate |
ffa.agents |
list, create, get, update, create_key, activity |
ffa.inbox |
list, iter, get, update, delete, get_download, download |
ffa.numbers |
coverage, inventory, create_order, get_order, list, get, update, release, check_portability, list_port_orders, create_port_order, get_port_order, create_port_out_request, verify_sender |
ffa.extractions |
create, list, iter, get, correct, classify, list_classifications, iter_classifications, get_classification, summarize, list_summaries, iter_summaries, get_summary, list_schemas, create_schema, get_schema |
ffa.organizations |
list, current, update_current, request_data_export, request_data_deletion |
ffa.members |
list, update, remove, list_invitations, invite, revoke_invitation |
ffa.request("GET", "/v1/anything") reaches an operation the resources do not cover yet.
States
A fax job is queued, active, partial, failed, unknown or delivered. delivered is written
only when the carrier's evidence is on the attempt, and fax.evidence holds it. partial and
unknown never retransmit on their own. Whether to resend is a human decision.
Sandbox destinations: +15550000001 delivered, +15550000002 busy then delivered, +15550000003
partial, +15550000004 no answer, +15550000005 unknown, +15550000006 provider timeout,
+15550000007 invalid number. Any other +1 number is delivered.
Errors
from faxforagents import APIError, RateLimitedError
try:
fax = ffa.faxes.create(quote_id=quote.id)
except RateLimitedError as exc:
print("retry in", exc.retry_after)
except APIError as exc:
print(exc.code, exc.message, exc.correlation_id) # quote_expired, insufficient_balance, approval_required
AuthenticationError (401), ScopeError (403), NotFoundError (404), ConflictError (409),
PaymentRequiredError (402), InvalidRequestError (400, 422), RateLimitedError (429) and
ServerError (5xx) are all APIError. APIConnectionError and APITimeoutError mean the API was
not reached.
Approvals
A live send needs either a human approval bound to the quote or a delegated policy that allows it.
req = ffa.approvals.request(quote.id) # a person is mailed
req = ffa.approvals.wait(req.id) # pending until they decide
if req.status == "approved":
fax = ffa.faxes.create(quote_id=quote.id, approval_id=req.id)
An agent's own API key can never approve. ffa.policies.evaluate(quote_id=...) is a dry run that says
whether a policy would allow the send.
Webhooks
from faxforagents import verify_webhook, parse_event, SignatureError
try:
event_id = verify_webhook(secret, request.headers, request.body)
except SignatureError:
return 400
event = parse_event(request.body) # event.type is fax.delivered, fax.failed, fax.received...
Verify on the raw bytes. Store event_id to drop a redelivery you already handled. During a secret
rotation two signatures arrive for 24 hours and either one verifies.
The command
export FAXFORAGENTS_API_KEY=ffa_test_...
faxforagents documents upload letter.pdf # or .png, .jpg, .tiff, .html, .md, .txt
faxforagents fax quote --document doc_... --to +15550000001
faxforagents fax send --quote quote_... --wait
faxforagents fax list --state failed
faxforagents inbox list
faxforagents approvals list --status pending
faxforagents balance
faxforagents sandbox scenario partial --to +12025550100
Every command takes --json for the raw answer.
Prices
Price card launch-v1 is effective from 2026-10-28. $0.039 per page sent, $0.019 per page received.
$2 per number per month, $19 per month for the Agent workspace. Sandbox pages cost 0.
ffa.platform.capabilities() returns the card in force.
Working on this repository
pip install -e ".[dev,cli]"
pytest -q # unit tests, respx fixtures, no network
FAXFORAGENTS_API_KEY=ffa_test_... pytest -q -m live # one real sandbox send
python scripts/gen_models.py --check # models.py matches spec/openapi.yaml
ruff check src tests && mypy src
sh scripts/truthcheck.sh
spec/openapi.yaml is a copy of the public document. To take a new one, copy it over and run
scripts/gen_models.py.
Licence
MIT. See LICENSE. Security reports go to hello@faxforagents.com, see SECURITY.md.
Metadata
Release files for faxforagents 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| faxforagents-0.2.0.tar.gz | 77.0 kB | With provenance | Details for faxforagents-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| faxforagents-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for faxforagents-0.2.0-py3-none-any.whl |
Total release size: 121.3 kB
Release files / faxforagents-0.2.0.tar.gz
| Download URL | faxforagents-0.2.0.tar.gz |
|---|---|
| Size | 77.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9524cb9b0713ca0d8e5aecd3b3c9bd35bd48f00a3b3863be561f2cf96f0b2441
|
|
BLAKE2b-256 checksum How to use checksums |
257708b30c0ee7e7f9931b621a5c0484a6d2511e3715d8e9b2cfbc198351d0dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logRelease files / faxforagents-0.2.0-py3-none-any.whl
| Download URL | faxforagents-0.2.0-py3-none-any.whl |
|---|---|
| Size | 44.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
70990d62cd75dde9627f86109e79451d4a950340acb4d10b5b1e507160644523
|
|
BLAKE2b-256 checksum How to use checksums |
72e4589574ef68588fbd6b18980fe29991a3fe0274f2f685fbbb15be9ebe623f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logSecurity for faxforagents 0.2.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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for faxforagents-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logA 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.