NAME

WWW::Hetzner::Cloud::Costs - Current running cost of a Hetzner Cloud project

VERSION

version 0.102

SYNOPSIS

use WWW::Hetzner::Cloud;

my $cloud = WWW::Hetzner::Cloud->new(token => $ENV{HETZNER_API_TOKEN});

my $costs = $cloud->costs(label_selector => 'env=live');

for my $r (@{ $costs->{resources} }) {
    printf "%-14s %-20s %-8s %8.4f %8.2f\n",
        $r->{kind}, $r->{name}, $r->{location} // '-',
        $r->{hourly}, $r->{monthly};
}
printf "Total: %.4f %s/h, %.2f %s/month (%s)\n",
    $costs->{totals}{hourly}, $costs->{currency},
    $costs->{totals}{monthly}, $costs->{currency}, $costs->{price};

DESCRIPTION

Sums what the resources of a Hetzner Cloud project cost right now, per hour and per month. This is a calculator over existing controllers, not an API resource of its own: it reads the price list via "get" in WWW::Hetzner::Cloud::API::Pricing and the resources via the list_all method of the servers, volumes, primary IPs, floating IPs and load balancers controllers. Every request goes through the client's normal IO path; nothing is modified.

Usually reached through "costs" in WWW::Hetzner::Cloud.

What is counted

  • server -- every server that exists, whatever its status. Hetzner bills stopped servers too, so an off server is counted at full price; the row carries status so callers can see it.

  • server_backup -- for servers with backups enabled (a backup_window is set), the backup surcharge: server_backup.percentage of the server's price.

  • volume -- size GB times volume.price_per_gb_month.

  • primary_ip -- priced by type (ipv4/ipv6) and location. A type the price list does not mention at all (currently ipv6) is free and counted as 0.

  • floating_ip -- priced by type and home location.

  • load_balancer -- priced by load balancer type and location.

Not counted: traffic beyond the included volume, images/snapshots, and anything outside the five resource kinds above.

Prices: location, net/gross, hourly

Server, load balancer, primary IP and floating IP prices are taken for the resource's own location. If the price list has no entry for a resource's type and location, the row gets undef prices, is left out of the totals, and a message is added to warnings.

Prices are gross by default (what the price list calls gross, i.e. including vat_rate); pass price => 'net' for net prices. The result always says which one was used, together with vat_rate.

monthly is the full-month price, which is also the cap of hourly billing. hourly is the price per hour the resource exists. For volumes and floating IPs the price list carries no hourly price; there the hourly figure is derived as monthly / 624 and the row is flagged hourly_derived. 624 hours is the ratio Hetzner uses for every item that has both prices (e.g. cx23 5.49/month at 0.0088/h, ipv4 0.50/month at 0.0008/h), i.e. the monthly cap is reached after 624 hours.

client

The WWW::Hetzner::Cloud client used for all requests (required).

calculate

my $costs = $calculator->calculate(
    label_selector => 'env=live',   # optional, passed to every list call
    price          => 'net',        # optional, 'gross' (default) or 'net'
);

Fetches the price list and all priced resources and returns a hashref:

{
    currency        => 'EUR',
    vat_rate        => '19.00',
    price           => 'gross',         # which price column was used
    label_selector  => 'env=live',      # or undef
    hours_per_month => 624,             # divisor for derived hourly prices
    resources       => [
        {
            kind     => 'server',       # server, server_backup, volume,
                                        # primary_ip, floating_ip, load_balancer
            id       => 42,
            name     => 'web-1',
            type     => 'cx23',         # type name, IP type, or "<size> GB"
            location => 'fsn1',
            status   => 'running',      # resource status, or (un)assigned for IPs
            hourly   => 0.0088,
            monthly  => 5.49,
            hourly_derived => 0,        # 1 if hourly = monthly / hours_per_month
            size     => 10,             # volumes only
        },
        ...
    ],
    totals   => { hourly => 0.0657, monthly => 40.98 },
    warnings => [ 'no gross price for server ...' ],
}

label_selector is applied to each resource's own labels on every list request (servers, volumes, primary IPs, floating IPs, load balancers). A server backup row inherits its server's label match.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-www-hetzner/issues.

IRC

Join #kubernetes on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.