Fax is not the first workflow most developers want to modernize.
It is also not going away in a lot of industries. Healthcare, insurance, logistics, legal, finance, and back-office operations still receive documents by fax. The problem is not only receiving the document. The problem is what happens next.
Someone usually has to read the fax, identify what kind of document it is, copy fields into another system, and route it to the right team.
The fax-to-structured-data-pipeline-python example shows how to turn that workflow into a small Python API:
- Receive inbound fax webhook events
- Verify the Telnyx webhook signature
- Queue fax metadata
- Extract structured JSON from document text with Telnyx AI Inference
- List recent faxes and extracted data
The app is intentionally compact so builders can understand the workflow and adapt it to their own document pipeline.
What the Example Builds
The Flask app exposes four routes:
POST /webhooks/fax- receives Telnyx fax webhook eventsPOST /extract- extracts structured JSON from document textGET /faxes- lists recently queued fax metadataGET /extracted- lists recent extraction resultsGET /health- checks app health
It uses Telnyx AI Inference through:
POST /v2/ai/chat/completions
The current default model in .env.example is:
AI_MODEL=moonshotai/Kimi-K2.6
The fax webhook route is responsible for receiving events. The extraction route is responsible for the document intelligence step.
Why Fax Extraction Is a Good AI App Pattern
Faxed documents are often semi-structured.
An invoice has predictable concepts: vendor, invoice number, due date, line items, subtotal, tax, and total. A purchase order has a PO number, vendor, ship-to address, items, and delivery date. A prescription has patient, prescriber, medication, dosage, frequency, quantity, refills, and date.
The exact layout can vary from sender to sender, but the fields your application needs are usually consistent.
That is where AI extraction is useful. Instead of building a pile of brittle parsers for every possible fax layout, you can ask the model to return a strict JSON shape for the document type you care about.
The app includes prompt templates for:
invoiceorderprescriptionauto
The auto mode asks the model to identify the document type and return relevant fields.
Running the Example
Clone the examples repo and enter the project folder:
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/fax-to-structured-data-pipeline-python
Create your environment file:
cp .env.example .env
Set your Telnyx values:
TELNYX_API_KEY=your_telnyx_api_key
TELNYX_PUBLIC_KEY=your_telnyx_public_key
AI_MODEL=moonshotai/Kimi-K2.6
HOST=127.0.0.1
Install dependencies and run the app:
pip install -r requirements.txt
python app.py
The app starts locally on http://localhost:5000.
Extract an Invoice
You can test the extraction workflow directly without sending a real fax:
curl -X POST http://localhost:5000/extract \
-H "Content-Type: application/json" \
-d '{
"type": "invoice",
"text": "Invoice #INV-1042 from Acme Medical Supplies dated 2026-07-01. Due 2026-07-31. Bill to North Clinic. Item: Nitrile gloves, quantity 10, unit price 12.50, total 125.00. Item: Face masks, quantity 5, unit price 20.00, total 100.00. Subtotal 225.00. Tax 18.00. Total 243.00. Payment terms Net 30."
}'
The model is asked to return JSON like:
{
"vendor": "Acme Medical Supplies",
"invoice_number": "INV-1042",
"date": "2026-07-01",
"due_date": "2026-07-31",
"line_items": [
{
"description": "Nitrile gloves",
"quantity": 10,
"unit_price": 12.5,
"total": 125
}
],
"subtotal": 225,
"tax": 18,
"total": 243,
"payment_terms": "Net 30"
}
The app stores successful extraction results in memory and adds an extracted_at timestamp.
Extract a Purchase Order
The same endpoint can use a different prompt:
curl -X POST http://localhost:5000/extract \
-H "Content-Type: application/json" \
-d '{
"type": "order",
"text": "Purchase Order PO-7781. Vendor: Harbor Office Supply. Ship to: 500 Market St, San Francisco, CA. SKU CHAIR-22, ergonomic chair, quantity 12, unit price 199.00. SKU DESK-10, standing desk, quantity 4, unit price 450.00. Total 4188.00. Delivery requested 2026-07-20."
}'
That produces a purchase-order-shaped JSON response instead of an invoice-shaped one.
The Fax Webhook Route
The webhook route is:
POST /webhooks/fax
The app uses the Telnyx Python SDK to verify the inbound webhook signature before trusting the event:
client.webhooks.unwrap(request.get_data(as_text=True), headers=dict(request.headers))
When the event type is fax.received, the app queues metadata like fax ID, sender, recipient, page count, media URL, status, and timestamp.
That route is designed for a live Telnyx Fax Application. During local development, expose your server:
ngrok http 5000
Then configure your Fax Application webhook URL in the Telnyx Portal:
https://<id>.ngrok.io/webhooks/fax
Inspect Queued and Extracted Data
List queued fax metadata:
curl http://localhost:5000/faxes
List extracted data:
curl http://localhost:5000/extracted
Check service health:
curl http://localhost:5000/health
Making It Production-Ready
This sample keeps state in memory and focuses on the workflow. For production, you would likely add:
- Persistent storage for fax metadata and extraction results
- Media download and OCR before the
/extractstep - Authentication for internal API routes
- Strong JSON schema validation per document type
- Retry handling for inference calls
- Human review for sensitive documents
- Audit logs for document processing
- Redaction and retention policies for regulated data
- Queue processing for high-volume inbound fax traffic
The repo is intentionally agent-readable. The README, API reference, guide, environment template, and app code are structured so coding agents can inspect the project and help extend it. You can ask an agent to add OCR, storage, retries, stricter schema validation, or workflow integrations.