A single file with no dependencies other than requests. It is heavily commented
throughout so you can read and modify it without needing to ask an AI to explain it.
Every file record returned by the API may include a cloud_share_link field pointing at
our Cloudflare R2 CDN. When that link is present the script uses it instead of the origin server,
because Cloudflare serves the bytes from an edge location near you rather than from our server in
the United States.
The difference is substantial. In our own testing, an identical 325 KB file took under 1 second from the CDN versus 19 seconds from the origin. The gap widens on multi-gigabyte files and on connections far from our data center.
If you are running your automation from a cloud server (AWS, Azure, GCP), using the CDN route matters even more — those hosts are often routed poorly to our origin.
The CDN is not always an option. Newly published files are pushed to Cloudflare by a background
uploader, so for a short window after publication a file exists on our origin but not yet on the CDN
and has no cloud_share_link. The script handles this without any intervention:
ATTEMPT 1 -> CLOUD https://vault.dnfilevault.com/<uuid>
No Authorization header is sent. The UUID in the URL
is itself the access token, so your DNFileVault
credentials are never exposed to the CDN host.
A 404 aborts immediately (retrying cannot help).
ATTEMPT 2 -> LOCAL https://api.dnfilevault.com/download/<uuid>
Authenticated with your Bearer token. Used when there
is no cloud link yet, or when the CDN attempt failed.
401 / 403 / 410 abort without retrying, since those
mean your purchase or subscription has expired.
Both failed -> the file is reported and the script moves on to
the next one. Nothing is deleted or corrupted.
The script mirrors rather than re-downloads. On every run it checks each expected file against what is already on disk and skips anything present at the correct size, so a nightly scheduled run only transfers that day's new files.
Downloads stream to a temporary .part file and are renamed into place only after the
transfer completes and the byte count matches. An interrupted transfer can never leave behind a
truncated file that looks complete — the next run simply picks it up again.
pip install requests # See what would be downloaded, without transferring anything: python dnfilevault_sync_mirror.py --dry-run # Mirror everything into a folder of your choosing: python dnfilevault_sync_mirror.py --dir D:\MarketData\DNFileVault
You will be prompted for your email and password. For scheduled or unattended runs, set them as environment variables instead so nothing is typed and no credentials live inside the script:
Windows:
set [email protected]
set DNFILEVAULT_PASSWORD=yourpassword
set DNFILEVAULT_SYNC_DIR=D:\MarketData\DNFileVault
Linux / macOS:
export [email protected]
export DNFILEVAULT_PASSWORD=yourpassword
export DNFILEVAULT_SYNC_DIR=/data/dnfilevault
The script exits with code 1 if any file failed, so Task Scheduler or cron can detect a
bad run and alert you.
DNFileVault\
purchases\
152 - Level 2 Full History\
L2_2026_January.zip
L2_2026_February.zip
groups\
eodLevel3\
L3_20260723.zip
L3_20260724.zip
stockhistory\
StockHistory.zip
dividends.csv
Purchase folders include the purchase number because the same product can be bought more than once (for example on renewal), and each purchase is a distinct set of files. Your own code then simply copies from these folders — no API or FTP logic needs to live inside your projects.
DNFileVault has anti-scanner protection on the API. Requests that look bot-like (a blank or default User-Agent) may be deliberately slowed to protect the service. This script already sends a proper descriptive User-Agent, so you should not encounter this — but if you adapt the code into your own client, keep that header. See the Automation & API page for more detail, or the API Reference for the full endpoint documentation.