NAME

VPNDetection::Database - the licensed dataset downloads

SYNOPSIS

my $db = $client->database;

my $databases = $db->list;
my $id = $databases->[0]{versions}[0]{id};       # e.g. 'vpn_ip_v1'

my $meta = $db->metadata($id);
my $sums = $db->checksums($id, 'mmdb');

my $url = $db->download_url($id, 'mmdb');       # transfer it yourself
my $bytes = $db->download_bytes('cdn_ip_v1', 'csvgz');
my $written = $db->download($id, 'mmdb', "./$id.mmdb");

DESCRIPTION

Access is granted by contract rather than self-serve, and needs a key carrying the db.download scope. Reached through "database" in VPNDetection.

Every method has a _p twin returning a Mojo::Promise, and every method takes a per-call retries option. Every method but the two transfers also takes a per-call timeout in seconds, replacing the client's for each attempt of that call.

METHODS

FORMATS

my @formats = VPNDetection::Database::FORMATS;    # ('csvgz', 'mmdb')

The formats a dataset is published in. A method taking a $format croaks on anything else before it makes a request.

STANDINGS

my @standings = VPNDetection::Database::STANDINGS;    # ('expired', 'licensed', 'unlicensed')

Every standing "list" reports.

LICENSE_TYPES

my @types = VPNDetection::Database::LICENSE_TYPES;    # ('evaluation', 'standard', 'redistribute')

Every license_type "list" reports. A family you hold no license for carries undef instead, which is not a member.

list

An array reference of the dataset families your organization may download. A license is held against a family, and each family carries every version of itself:

{
    base => 'vpn_ip',               # what the license is held against
    name => 'VPN IP',
    summary => 'IP ranges observed as VPN infrastructure.',
    license_type => 'standard',   # evaluation/standard/redistribute, or undef
    starts => '2026-09-04T07:49:45.118Z',
    expires => undef,               # undef when the license does not expire
    renews_at => undef,             # when a rolling license next turns over
    notice_due_at => undef,         # last day to give notice for that term
    in_term => 1,                   # false once the term has ended
    standing => 'licensed',         # licensed, expired or unlicensed
    versions => [
        {
            id => 'vpn_ip_v1',      # this is what you download
            version => 1,
            summary => 'IP ranges observed as VPN infrastructure.',
            formats => [{ format => 'csvgz', bytes => 111013959 }, ...],
            sample_formats => ['csvgz', 'mmdb'],
        },
    ],
}

The id every other method takes is $version->{id}, never $family->{base}.

metadata($id)

One dataset's document: updated, entries, per-format schema, sample and size. Poll it to decide whether today's build is worth fetching, and read $meta->{size}{$format} to size a transfer before starting it.

checksums($id, $format)

The whole digest set for one published file, as a hash reference keyed by algorithm. Which algorithms appear varies by dataset, so read the one you want off the hash rather than expecting a fixed set.

downloads(%options)

Your organization's recent download attempts, newest first. limit caps the number returned.

download_url($id, $format)

A time-limited URL for one dataset file. The API answers 302 and this returns the Location; the bytes are yours to transfer however suits a file that can run to gigabytes. The link authorizes the START of a transfer, so one already running is not interrupted when it lapses.

download($id, $format, $path)

Streams one dataset file to $path and returns the bytes written. Nothing beyond one chunk is ever held, whatever the dataset weighs.

The bytes land in a neighboring .part file that is renamed on completion, so a transfer that dies half way leaves nothing behind that reads as a whole dataset. A body that stops early is raised rather than accepted: the file is never left short and silent.

download_bytes($id, $format)

Downloads one dataset file and returns its bytes.

This holds the entire file in memory, and the catalog spans five orders of magnitude, from cdn_ip_v1 at 10 KB to resproxy_ip_90d_v1 at 1.79 GB. Reach for it at the small end, where the bytes go straight into a parser, and use download for anything you have not measured.

TRANSFERS

download_url hands out a presigned link, and download and download_bytes follow it as a second request carrying no credential: the link authorizes itself, so forwarding the API key would hand it to a host with no business holding it.

retries covers that transfer only until its first byte reaches you: object storage failing before then is retried like any server error, but a transfer that dies part way is not repeated, since a second copy would append to the bytes already written. The per-request timeout that bounds a lookup is lifted for it, which is why download and download_bytes refuse a per-call timeout.