Toll-free inventory automation platform
TF4
An authenticated full-stack platform that scans, classifies, reserves and reconciles high-volume toll-free number inventory through a provider REST API, with resumable background jobs, run history, role-based access and production safeguards.
- Context
- Professional · National Sales Partners
- Role
- Designed, built and operate it
- Period
- 2025 — Present · In daily production use
- Stack
- Python, Flask, SQLAlchemy, SQLite, Gunicorn
At a glance
- 01 · Problem
Checking and managing toll-free number inventory by hand meant repeated portal lookups, spreadsheets and decisions made on stale data.
- 02 · Solution
One authenticated Flask platform that generates candidates, checks them against the provider's API, reconciles owned inventory and records every run, with resumable background jobs.
- 03 · Impact
Replaced a manual, spreadsheet-driven workflow with one audited system in daily production use. Anything that changes real inventory sits behind two independent safety switches.
- Runtime
- Python · Flask (blueprints)
- Data
- SQLAlchemy ORM · SQLite
- Serving
- Gunicorn behind Nginx (TLS)
- Host
- AWS EC2 · Linux
- Jobs
- In-process workers · cursors · backoff
- Integration
- Provider REST API · session + polling
- Access
- Accounts · grouped permissions · audit log
- Quality
- pytest · hash-verified releases
The problem
Toll-free numbers are a working asset for a call-driven business: which numbers you hold, which ones are about to become available, and which ones match the patterns your campaigns need. Doing that by hand means spreadsheets, repeated lookups in a provider portal and decisions made on stale data.
Architecture
Engineering patterns
Approach
TF4 moves the work into one Flask application. Configurable generators produce candidate numbers from target patterns; a scan service checks them against the provider's REST API, classifies the results and writes them to a live table with full status history.
Long-running work runs as managed background jobs inside the app, each with its own run record, cursor, backoff and cooldown, so an operator can see what ran, what it found and why it stopped. The provider client handles sessions, retries and polling, and logs requests with sensitive fields stripped.
Anything that changes real inventory sits behind two switches: reservation is off by default in the database configuration, and the code path that can reserve requires an explicit caller flag as well. Accounts, grouped permissions and an activity log record who did what.
What was built
- 01Target-driven candidate generation and deterministic matching
- 02Status and history checks against the provider API
- 03Classification and opportunity review queues
- 04Reconciliation of owned inventory against provider records
- 05Run history for every scan, crawl and batch job
- 06Accounts, grouped permissions and activity logs
- 07CSV exports and operational dashboards
- 08Financial and lifecycle views (in development)
| Run | Job | Items | Result | Status |
|---|---|---|---|---|
| R-1042 | Scan · target set A | 2,400 | 37 | Completed |
| R-1041 | History check · watchlist | 860 | 12 | Completed |
| R-1040 | Reconcile · owned inventory | 5,120 | 4 diffs | Review |
| R-1039 | Scan · target set B | 1,950 | — | Running 64% |
| R-1038 | Reserve · queue | 0 | — | Disabled |
The hard parts
Safe by default
A wrong reservation costs money and is hard to undo. Reserve-capable code needs both a stored setting and an intentional flag from the caller; the dashboard's scan route always runs with execution disabled.
Rate limits and partial failure
Provider calls are retried with backoff, polled where the API is asynchronous, and resumed from a saved cursor so a failed batch picks up where it stopped instead of duplicating work.
Deploying without surprises
The server is updated with selected files, never a whole-tree copy. Each release is checked against hashes of the live files, backed up first and recorded with undo steps.
Concurrency inside one process
Background jobs share process-level locks and run state, so the app runs as a single Gunicorn worker by design until that state moves to a durable queue.
Outcome
- In daily production use on AWS EC2 behind Nginx
- Full stack owned end to end: schema, services, interface, deployment and operations
- Automated test suite with pytest