Skip to main content
For high-volume use cases (>1 M lookups/day) or warehouse loads, pull the full dataset on a schedule instead of hammering the API.

Format choice

Pro and Scale get the ranges table: the VPN / proxy / hosting / CDN / Tor / relay CIDR ranges. The points table, every residential-proxy exit IP as a /32, needs Business in every format, CSV included. MMDB is for local lookups: load the two files into memory once, and refresh them daily.

Scheduling

The MMDB and Parquet files are rebuilt once a day, at no fixed time. The crontab below checks twice a day; pulling more often downloads the same files again. The MMDB ships as two files. artifact=ranges (the default) is wxa_vpn.mmdb — the VPN / proxy / hosting / CDN / Tor / relay CIDR-ranges layer. artifact=points is wxa_vpn-points.mmdb — the residential-proxy per-IP (/32) layer. Download both for full coverage. GET /export/mmdb does not return the database itself. It returns JSON with a signed S3 download URL that expires after an hour:
So each refresh makes two requests: one for the JSON, then one for the file at its download_url. Save this script as /usr/local/bin/wxa-vpn-refresh-mmdb and make it executable (chmod +x). It needs curl and jq, and /var/lib/wxa-vpn must exist and be writable by the user cron runs it as.
Any failure stops the script before the rename. A failed request, an expired link, a stalled download or an error page leaves your current database in place, and the script exits non-zero with the reason on stderr. Cron mails that output to the crontab’s owner and discards it on a host with no mail set up; to keep it, append >> /var/lib/wxa-vpn/refresh.log 2>&1 to each entry. Then schedule both layers. Cron does not read your shell profile, so set the key in the crontab. Keep each entry on one line: cron does not continue a line that ends in \.
The rename is atomic, so readers never see a partial file. A process that already has the database open keeps reading the old copy until it opens the file again.

MMDB lookup (Python)

For a complete verdict, check the points DB first (an exact /32 residential-proxy detection wins), then fall back to the ranges DB:
Both IPv4 and IPv6 keys are accepted. The data covers IPv4 today: IPv6 addresses currently return little or no classification, and IPv6 coverage is on the roadmap. An empty result (None) means an unknown IP, not an error. wxa_vpn.mmdb alone does NOT include the residential-proxy points — pull wxa_vpn-points.mmdb for that layer.

Parquet to Snowflake

GET /export/parquet answers the same way as /export/mmdb: JSON whose download_url points at the file (artifact=ranges, the default, or artifact=points). Save the ranges file in your stage as wxa_vpn_ranges.parquet, the name the COPY below reads; the download’s own name carries the build date. Snowflake reads a Parquet file as one VARIANT value per row, so the COPY selects each column out of it and casts it. USE_LOGICAL_TYPE = TRUE makes Snowflake read first_seen and last_seen as the UTC instants they are; without it they load as TIMESTAMP_NTZ, and the cast to TIMESTAMP_TZ applies your session’s time zone to them:
Each Parquet file carries its own schema: A file with no rows declares the same double and timestamp columns, and every other column as a string. Until October 2026, confidence, first_seen, last_seen and hits_days_pct were strings in Parquet. A table that declares them as strings, such as an Athena or Hive external table, a BigQuery table or a Spark schema, has to declare them as double and timestamp before it reads a newer file. A points file loads the same way, with its own columns. Load its two list columns, providers_list and geo_targets_seen, into VARIANT columns.

Which window file to use

Each daily build also cuts the points table into four shorter files. Like every points file, they need Business or Enterprise. data_as_of is the time of the most recent observation in the build. It is in the build’s manifest.json and on the Data as of line of its README. The windows have no upper edge, so a row seen after data_as_of is in every window file. Each window holds every row of the shorter ones: 1-day, then 7-day, 30-day, 90-day, and the full points file. All five have the same columns, so one loader reads them all. GET /api/v1/export/dataset lists them, with table set to points_1d, points_7d, points_30d, points_90d or points; format=csv-zstd or format=parquet narrows the list. A build without a data_as_of ships no window files: filter the full points file on last_seen instead. A shorter window holds fewer addresses that have stopped being proxy exits since we saw them, and it leaves out exits we last saw before it starts. Pick the file by what your rule does when it matches:
  • 1-day file: for your strictest action, such as a block, where acting on an address that is no longer an exit costs more than missing one.
  • 7-day file: for a step-up, such as a challenge or a review, where a sighting from earlier in the week is still worth acting on.
  • 30-day file: for a softer response, such as a lower rate limit or a flag for later review, where a sighting from the last month is still worth noting.
  • 90-day file: for lookbacks and investigations over the last quarter.
  • Full points file: for older history, or to apply your own cut-off on last_seen or freshness_class. It also includes frozen records, last seen more than 90 days ago.
These are starting points: test them on your own traffic before you enforce them.

File size

Approximate sizes of one daily build in October 2026; they change with every build. Plan storage and bandwidth accordingly. The CSV files in the table are gzip-compressed and Parquet compresses internally; the MMDB files download uncompressed.