Skip to main content

Fax for Agents

faxforagents

The Python client for Fax for Agents, the fax API built for AI agents.

Website · Quickstart · API reference · SDKs · MCP · Dashboard · Pricing · Changelog

PyPI version CI MIT licence Python 3.10+

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. Pass idempotency_key= to pick your own.
  • 429 and 5xx answers are retried up to three times. Retry-After is honoured. Connection errors and timeouts are retried the same way.
  • Every list has an iter twin that follows next_cursor.
  • documents.create(text=...), markdown=... or html=... renders on the server. HTML prints with scripts off and no network, inline CSS and data: 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) and approvals.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 (39000 is $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)

Source distribution for faxforagents 0.2.0
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)

Table of built distributions (wheels) for faxforagents 0.2.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

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