diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
index 6ab2f1095dac74773ec05f93e6cb31fba321566d..7943a8c15499e18f1ba06ed708d63f0a7ad32709 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -54,9 +55,21 @@ const (
 type ActionError struct {
 	Code    string
 	Message string
+
+	action *Action
+}
+
+// Action returns the [Action] that triggered the error if available.
+func (e ActionError) Action() *Action {
+	return e.action
 }
 
 func (e ActionError) Error() string {
+	action := e.Action()
+	if action != nil {
+		// For easier debugging, the error string contains the Action ID.
+		return fmt.Sprintf("%s (%s, %d)", e.Message, e.Code, action.ID)
+	}
 	return fmt.Sprintf("%s (%s)", e.Message, e.Code)
 }
 
@@ -65,6 +78,7 @@ func (a *Action) Error() error {
 		return ActionError{
 			Code:    a.ErrorCode,
 			Message: a.ErrorMessage,
+			action:  a,
 		}
 	}
 	return nil
@@ -111,11 +125,15 @@ func (c *ActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action
 }
 
 // All returns all actions.
+//
+// Deprecated: It is required to pass in a list of IDs since 30 January 2025. Please use [ActionClient.AllWithOpts] instead.
 func (c *ActionClient) All(ctx context.Context) ([]*Action, error) {
 	return c.action.All(ctx, ActionListOpts{ListOpts: ListOpts{PerPage: 50}})
 }
 
 // AllWithOpts returns all actions for the given options.
+//
+// It is required to set [ActionListOpts.ID]. Any other fields set in the opts are ignored.
 func (c *ActionClient) AllWithOpts(ctx context.Context, opts ActionListOpts) ([]*Action, error) {
 	return c.action.All(ctx, opts)
 }
@@ -136,20 +154,19 @@ func (c *ResourceActionClient) getBaseURL() string {
 
 // GetByID retrieves an action by its ID. If the action does not exist, nil is returned.
 func (c *ResourceActionClient) GetByID(ctx context.Context, id int64) (*Action, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("%s/actions/%d", c.getBaseURL(), id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	opPath := c.getBaseURL() + "/actions/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.ActionGetResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, id)
+
+	respBody, resp, err := getRequest[schema.ActionGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return ActionFromSchema(body.Action), resp, nil
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // List returns a list of actions for a specific page.
@@ -157,44 +174,23 @@ func (c *ResourceActionClient) GetByID(ctx context.Context, id int64) (*Action,
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *ResourceActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) {
-	req, err := c.client.NewRequest(
-		ctx,
-		"GET",
-		fmt.Sprintf("%s/actions?%s", c.getBaseURL(), opts.values().Encode()),
-		nil,
-	)
-	if err != nil {
-		return nil, nil, err
-	}
+	opPath := c.getBaseURL() + "/actions?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.ActionListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ActionListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	actions := make([]*Action, 0, len(body.Actions))
-	for _, i := range body.Actions {
-		actions = append(actions, ActionFromSchema(i))
+		return nil, resp, err
 	}
-	return actions, resp, nil
+
+	return allFromSchemaFunc(respBody.Actions, ActionFromSchema), resp, nil
 }
 
 // All returns all actions for the given options.
 func (c *ResourceActionClient) All(ctx context.Context, opts ActionListOpts) ([]*Action, error) {
-	allActions := []*Action{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Action, *Response, error) {
 		opts.Page = page
-		actions, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allActions = append(allActions, actions...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allActions, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go
index ebfe8ef4eb3e73a5a0424a6118777f9e9d5b25e9..50f5bba7723402e0fe5d6b0d3277e46eedb92657 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go
@@ -16,11 +16,14 @@ type ActionWaiter interface {
 var _ ActionWaiter = (*ActionClient)(nil)
 
 // WaitForFunc waits until all actions are completed by polling the API at the interval
-// defined by [WithPollBackoffFunc]. An action is considered as complete when its status is
+// defined by [WithPollOpts]. An action is considered as complete when its status is
 // either [ActionStatusSuccess] or [ActionStatusError].
 //
 // The handleUpdate callback is called every time an action is updated.
 func (c *ActionClient) WaitForFunc(ctx context.Context, handleUpdate func(update *Action) error, actions ...*Action) error {
+	// Filter out nil actions
+	actions = slices.DeleteFunc(actions, func(a *Action) bool { return a == nil })
+
 	running := make(map[int64]struct{}, len(actions))
 	for _, action := range actions {
 		if action.Status == ActionStatusRunning {
@@ -48,18 +51,19 @@ func (c *ActionClient) WaitForFunc(ctx context.Context, handleUpdate func(update
 			retries++
 		}
 
-		opts := ActionListOpts{
-			Sort: []string{"status", "id"},
-			ID:   make([]int64, 0, len(running)),
-		}
-		for actionID := range running {
-			opts.ID = append(opts.ID, actionID)
-		}
-		slices.Sort(opts.ID)
+		updates := make([]*Action, 0, len(running))
+		for runningIDsChunk := range slices.Chunk(slices.Sorted(maps.Keys(running)), 25) {
+			opts := ActionListOpts{
+				Sort: []string{"status", "id"},
+				ID:   runningIDsChunk,
+			}
+
+			updatesChunk, err := c.AllWithOpts(ctx, opts)
+			if err != nil {
+				return err
+			}
 
-		updates, err := c.AllWithOpts(ctx, opts)
-		if err != nil {
-			return err
+			updates = append(updates, updatesChunk...)
 		}
 
 		if len(updates) != len(running) {
@@ -95,7 +99,7 @@ func (c *ActionClient) WaitForFunc(ctx context.Context, handleUpdate func(update
 }
 
 // WaitFor waits until all actions succeed by polling the API at the interval defined by
-// [WithPollBackoffFunc]. An action is considered as succeeded when its status is either
+// [WithPollOpts]. An action is considered as succeeded when its status is either
 // [ActionStatusSuccess].
 //
 // If a single action fails, the function will stop waiting and the error set in the
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go
index db3464f11cacc69fdd696681d25e508c094b9ed7..58c2eb878d584d8df7b0c19f26bb564ea848f5ba 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go
@@ -21,7 +21,7 @@ import (
 // timeout, use the [context.Context]. Once the method has stopped watching,
 // both returned channels are closed.
 //
-// WatchOverallProgress uses the [WithPollBackoffFunc] of the [Client] to wait
+// WatchOverallProgress uses the [WithPollOpts] of the [Client] to wait
 // until sending the next request.
 //
 // Deprecated: WatchOverallProgress is deprecated, use [WaitForFunc] instead.
@@ -86,7 +86,7 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
 // timeout, use the [context.Context]. Once the method has stopped watching,
 // both returned channels are closed.
 //
-// WatchProgress uses the [WithPollBackoffFunc] of the [Client] to wait until
+// WatchProgress uses the [WithPollOpts] of the [Client] to wait until
 // sending the next request.
 //
 // Deprecated: WatchProgress is deprecated, use [WaitForFunc] instead.
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go
index e6ffa50cb62a5ed03823ac5e9275317fb6c8a016..97bb572882b6496b7345a6d1c2a19d580f10333e 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go
@@ -1,15 +1,12 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -98,41 +95,32 @@ type CertificateClient struct {
 
 // GetByID retrieves a Certificate by its ID. If the Certificate does not exist, nil is returned.
 func (c *CertificateClient) GetByID(ctx context.Context, id int64) (*Certificate, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/certificates/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/certificates/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.CertificateGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.CertificateGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return CertificateFromSchema(body.Certificate), resp, nil
+	return CertificateFromSchema(respBody.Certificate), resp, nil
 }
 
 // GetByName retrieves a Certificate by its name. If the Certificate does not exist, nil is returned.
 func (c *CertificateClient) GetByName(ctx context.Context, name string) (*Certificate, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	Certificate, response, err := c.List(ctx, CertificateListOpts{Name: name})
-	if len(Certificate) == 0 {
-		return nil, response, err
-	}
-	return Certificate[0], response, err
+	return firstByName(name, func() ([]*Certificate, *Response, error) {
+		return c.List(ctx, CertificateListOpts{Name: name})
+	})
 }
 
 // Get retrieves a Certificate by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a Certificate by its name. If the Certificate does not exist, nil is returned.
 func (c *CertificateClient) Get(ctx context.Context, idOrName string) (*Certificate, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // CertificateListOpts specifies options for listing Certificates.
@@ -158,22 +146,17 @@ func (l CertificateListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *CertificateClient) List(ctx context.Context, opts CertificateListOpts) ([]*Certificate, *Response, error) {
-	path := "/certificates?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/certificates?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.CertificateListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.CertificateListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	Certificates := make([]*Certificate, 0, len(body.Certificates))
-	for _, s := range body.Certificates {
-		Certificates = append(Certificates, CertificateFromSchema(s))
+		return nil, resp, err
 	}
-	return Certificates, resp, nil
+
+	return allFromSchemaFunc(respBody.Certificates, CertificateFromSchema), resp, nil
 }
 
 // All returns all Certificates.
@@ -183,22 +166,10 @@ func (c *CertificateClient) All(ctx context.Context) ([]*Certificate, error) {
 
 // AllWithOpts returns all Certificates for the given options.
 func (c *CertificateClient) AllWithOpts(ctx context.Context, opts CertificateListOpts) ([]*Certificate, error) {
-	allCertificates := []*Certificate{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Certificate, *Response, error) {
 		opts.Page = page
-		Certificates, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allCertificates = append(allCertificates, Certificates...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allCertificates, nil
 }
 
 // CertificateCreateOpts specifies options for creating a new Certificate.
@@ -214,7 +185,7 @@ type CertificateCreateOpts struct {
 // Validate checks if options are valid.
 func (o CertificateCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	switch o.Type {
 	case "", CertificateTypeUploaded:
@@ -222,23 +193,23 @@ func (o CertificateCreateOpts) Validate() error {
 	case CertificateTypeManaged:
 		return o.validateManaged()
 	default:
-		return fmt.Errorf("invalid type: %s", o.Type)
+		return invalidFieldValue(o, "Type", o.Type)
 	}
 }
 
 func (o CertificateCreateOpts) validateManaged() error {
 	if len(o.DomainNames) == 0 {
-		return errors.New("no domain names")
+		return missingField(o, "DomainNames")
 	}
 	return nil
 }
 
 func (o CertificateCreateOpts) validateUploaded() error {
 	if o.Certificate == "" {
-		return errors.New("missing certificate")
+		return missingField(o, "Certificate")
 	}
 	if o.PrivateKey == "" {
-		return errors.New("missing private key")
+		return missingField(o, "PrivateKey")
 	}
 	return nil
 }
@@ -249,7 +220,7 @@ func (o CertificateCreateOpts) validateUploaded() error {
 // CreateCertificate to create such certificates.
 func (c *CertificateClient) Create(ctx context.Context, opts CertificateCreateOpts) (*Certificate, *Response, error) {
 	if !(opts.Type == "" || opts.Type == CertificateTypeUploaded) {
-		return nil, nil, fmt.Errorf("invalid certificate type: %s", opts.Type)
+		return nil, nil, invalidFieldValue(opts, "Type", opts.Type)
 	}
 	result, resp, err := c.CreateCertificate(ctx, opts)
 	if err != nil {
@@ -262,16 +233,20 @@ func (c *CertificateClient) Create(ctx context.Context, opts CertificateCreateOp
 func (c *CertificateClient) CreateCertificate(
 	ctx context.Context, opts CertificateCreateOpts,
 ) (CertificateCreateResult, *Response, error) {
-	var (
-		action  *Action
-		reqBody schema.CertificateCreateRequest
-	)
+	const opPath = "/certificates"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := opPath
+
+	result := CertificateCreateResult{}
 
 	if err := opts.Validate(); err != nil {
-		return CertificateCreateResult{}, nil, err
+		return result, nil, err
 	}
 
-	reqBody.Name = opts.Name
+	reqBody := schema.CertificateCreateRequest{
+		Name: opts.Name,
+	}
 
 	switch opts.Type {
 	case "", CertificateTypeUploaded:
@@ -282,32 +257,24 @@ func (c *CertificateClient) CreateCertificate(
 		reqBody.Type = string(CertificateTypeManaged)
 		reqBody.DomainNames = opts.DomainNames
 	default:
-		return CertificateCreateResult{}, nil, fmt.Errorf("invalid certificate type: %v", opts.Type)
+		return result, nil, invalidFieldValue(opts, "Type", opts.Type)
 	}
 
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return CertificateCreateResult{}, nil, err
-	}
-	req, err := c.client.NewRequest(ctx, "POST", "/certificates", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return CertificateCreateResult{}, nil, err
-	}
 
-	respBody := schema.CertificateCreateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.CertificateCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return CertificateCreateResult{}, resp, err
+		return result, resp, err
 	}
-	cert := CertificateFromSchema(respBody.Certificate)
+
+	result.Certificate = CertificateFromSchema(respBody.Certificate)
 	if respBody.Action != nil {
-		action = ActionFromSchema(*respBody.Action)
+		result.Action = ActionFromSchema(*respBody.Action)
 	}
 
-	return CertificateCreateResult{Certificate: cert, Action: action}, resp, nil
+	return result, resp, nil
 }
 
 // CertificateUpdateOpts specifies options for updating a Certificate.
@@ -318,6 +285,11 @@ type CertificateUpdateOpts struct {
 
 // Update updates a Certificate.
 func (c *CertificateClient) Update(ctx context.Context, certificate *Certificate, opts CertificateUpdateOpts) (*Certificate, *Response, error) {
+	const opPath = "/certificates/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, certificate.ID)
+
 	reqBody := schema.CertificateUpdateRequest{}
 	if opts.Name != "" {
 		reqBody.Name = &opts.Name
@@ -325,46 +297,36 @@ func (c *CertificateClient) Update(ctx context.Context, certificate *Certificate
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/certificates/%d", certificate.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.CertificateUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.CertificateUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return CertificateFromSchema(respBody.Certificate), resp, nil
 }
 
 // Delete deletes a certificate.
 func (c *CertificateClient) Delete(ctx context.Context, certificate *Certificate) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/certificates/%d", certificate.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/certificates/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, certificate.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // RetryIssuance retries the issuance of a failed managed certificate.
 func (c *CertificateClient) RetryIssuance(ctx context.Context, certificate *Certificate) (*Action, *Response, error) {
-	var respBody schema.CertificateIssuanceRetryResponse
+	const opPath = "/certificates/%d/actions/retry"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/certificates/%d/actions/retry", certificate.ID), nil)
-	if err != nil {
-		return nil, nil, err
-	}
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := fmt.Sprintf(opPath, certificate.ID)
+
+	respBody, resp, err := postRequest[schema.CertificateIssuanceRetryResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
-		return nil, nil, err
+		return nil, resp, err
 	}
-	action := ActionFromSchema(respBody.Action)
-	return action, resp, nil
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go
index 9a7da731f1afb568a74c1ea738eb15d8d5fdb9ad..3a1a66263a293d3e08c01cd881f0568976bffefc 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go
@@ -3,13 +3,12 @@ package hcloud
 import (
 	"bytes"
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
 	"math"
+	"math/rand"
 	"net/http"
-	"net/http/httputil"
 	"net/url"
 	"strconv"
 	"strings"
@@ -19,7 +18,6 @@ import (
 	"golang.org/x/net/http/httpguts"
 
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation"
-	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
 // Endpoint is the base URL of the API.
@@ -43,13 +41,43 @@ func ConstantBackoff(d time.Duration) BackoffFunc {
 }
 
 // ExponentialBackoff returns a BackoffFunc which implements an exponential
-// backoff.
-// It uses the formula:
+// backoff, truncated to 60 seconds.
+// See [ExponentialBackoffWithOpts] for more details.
+func ExponentialBackoff(multiplier float64, base time.Duration) BackoffFunc {
+	return ExponentialBackoffWithOpts(ExponentialBackoffOpts{
+		Base:       base,
+		Multiplier: multiplier,
+		Cap:        time.Minute,
+	})
+}
+
+// ExponentialBackoffOpts defines the options used by [ExponentialBackoffWithOpts].
+type ExponentialBackoffOpts struct {
+	Base       time.Duration
+	Multiplier float64
+	Cap        time.Duration
+	Jitter     bool
+}
+
+// ExponentialBackoffWithOpts returns a BackoffFunc which implements an exponential
+// backoff, truncated to a maximum, and an optional full jitter.
 //
-//	b^retries * d
-func ExponentialBackoff(b float64, d time.Duration) BackoffFunc {
+// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
+func ExponentialBackoffWithOpts(opts ExponentialBackoffOpts) BackoffFunc {
+	baseSeconds := opts.Base.Seconds()
+	capSeconds := opts.Cap.Seconds()
+
 	return func(retries int) time.Duration {
-		return time.Duration(math.Pow(b, float64(retries))) * d
+		// Exponential backoff
+		backoff := baseSeconds * math.Pow(opts.Multiplier, float64(retries))
+		// Cap backoff
+		backoff = math.Min(capSeconds, backoff)
+		// Add jitter
+		if opts.Jitter {
+			backoff = ((backoff - baseSeconds) * rand.Float64()) + baseSeconds // #nosec G404
+		}
+
+		return time.Duration(backoff * float64(time.Second))
 	}
 }
 
@@ -58,7 +86,8 @@ type Client struct {
 	endpoint                string
 	token                   string
 	tokenValid              bool
-	backoffFunc             BackoffFunc
+	retryBackoffFunc        BackoffFunc
+	retryMaxRetries         int
 	pollBackoffFunc         BackoffFunc
 	httpClient              *http.Client
 	applicationName         string
@@ -66,6 +95,7 @@ type Client struct {
 	userAgent               string
 	debugWriter             io.Writer
 	instrumentationRegistry prometheus.Registerer
+	handler                 handler
 
 	Action           ActionClient
 	Certificate      CertificateClient
@@ -110,30 +140,73 @@ func WithToken(token string) ClientOption {
 // polling from the API.
 //
 // Deprecated: Setting the poll interval is deprecated, you can now configure
-// [WithPollBackoffFunc] with a [ConstantBackoff] to get the same results. To
+// [WithPollOpts] with a [ConstantBackoff] to get the same results. To
 // migrate your code, replace your usage like this:
 //
 //	// before
 //	hcloud.WithPollInterval(2 * time.Second)
 //	// now
-//	hcloud.WithPollBackoffFunc(hcloud.ConstantBackoff(2 * time.Second))
+//	hcloud.WithPollOpts(hcloud.PollOpts{
+//		BackoffFunc: hcloud.ConstantBackoff(2 * time.Second),
+//	})
 func WithPollInterval(pollInterval time.Duration) ClientOption {
-	return WithPollBackoffFunc(ConstantBackoff(pollInterval))
+	return WithPollOpts(PollOpts{
+		BackoffFunc: ConstantBackoff(pollInterval),
+	})
 }
 
 // WithPollBackoffFunc configures a Client to use the specified backoff
 // function when polling from the API.
+//
+// Deprecated: WithPollBackoffFunc is deprecated, use [WithPollOpts] instead.
 func WithPollBackoffFunc(f BackoffFunc) ClientOption {
+	return WithPollOpts(PollOpts{
+		BackoffFunc: f,
+	})
+}
+
+// PollOpts defines the options used by [WithPollOpts].
+type PollOpts struct {
+	BackoffFunc BackoffFunc
+}
+
+// WithPollOpts configures a Client to use the specified options when polling from the API.
+//
+// If [PollOpts.BackoffFunc] is nil, the existing backoff function will be preserved.
+func WithPollOpts(opts PollOpts) ClientOption {
 	return func(client *Client) {
-		client.pollBackoffFunc = f
+		if opts.BackoffFunc != nil {
+			client.pollBackoffFunc = opts.BackoffFunc
+		}
 	}
 }
 
 // WithBackoffFunc configures a Client to use the specified backoff function.
 // The backoff function is used for retrying HTTP requests.
+//
+// Deprecated: WithBackoffFunc is deprecated, use [WithRetryOpts] instead.
 func WithBackoffFunc(f BackoffFunc) ClientOption {
 	return func(client *Client) {
-		client.backoffFunc = f
+		client.retryBackoffFunc = f
+	}
+}
+
+// RetryOpts defines the options used by [WithRetryOpts].
+type RetryOpts struct {
+	BackoffFunc BackoffFunc
+	MaxRetries  int
+}
+
+// WithRetryOpts configures a Client to use the specified options when retrying API
+// requests.
+//
+// If [RetryOpts.BackoffFunc] is nil, the existing backoff function will be preserved.
+func WithRetryOpts(opts RetryOpts) ClientOption {
+	return func(client *Client) {
+		if opts.BackoffFunc != nil {
+			client.retryBackoffFunc = opts.BackoffFunc
+		}
+		client.retryMaxRetries = opts.MaxRetries
 	}
 }
 
@@ -172,10 +245,18 @@ func WithInstrumentation(registry prometheus.Registerer) ClientOption {
 // NewClient creates a new client.
 func NewClient(options ...ClientOption) *Client {
 	client := &Client{
-		endpoint:        Endpoint,
-		tokenValid:      true,
-		httpClient:      &http.Client{},
-		backoffFunc:     ExponentialBackoff(2, 500*time.Millisecond),
+		endpoint:   Endpoint,
+		tokenValid: true,
+		httpClient: &http.Client{},
+
+		retryBackoffFunc: ExponentialBackoffWithOpts(ExponentialBackoffOpts{
+			Base:       time.Second,
+			Multiplier: 2,
+			Cap:        time.Minute,
+			Jitter:     true,
+		}),
+		retryMaxRetries: 5,
+
 		pollBackoffFunc: ConstantBackoff(500 * time.Millisecond),
 	}
 
@@ -186,9 +267,11 @@ func NewClient(options ...ClientOption) *Client {
 	client.buildUserAgent()
 	if client.instrumentationRegistry != nil {
 		i := instrumentation.New("api", client.instrumentationRegistry)
-		client.httpClient.Transport = i.InstrumentedRoundTripper()
+		client.httpClient.Transport = i.InstrumentedRoundTripper(client.httpClient.Transport)
 	}
 
+	client.handler = assembleHandlerChain(client)
+
 	client.Action = ActionClient{action: &ResourceActionClient{client: client}}
 	client.Datacenter = DatacenterClient{client: client}
 	client.FloatingIP = FloatingIPClient{client: client, Action: &ResourceActionClient{client: client, resource: "floating_ips"}}
@@ -238,97 +321,8 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
 // Do performs an HTTP request against the API.
 // v can be nil, an io.Writer to write the response body to or a pointer to
 // a struct to json.Unmarshal the response to.
-func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
-	var retries int
-	var body []byte
-	var err error
-	if r.ContentLength > 0 {
-		body, err = io.ReadAll(r.Body)
-		if err != nil {
-			r.Body.Close()
-			return nil, err
-		}
-		r.Body.Close()
-	}
-	for {
-		if r.ContentLength > 0 {
-			r.Body = io.NopCloser(bytes.NewReader(body))
-		}
-
-		if c.debugWriter != nil {
-			dumpReq, err := dumpRequest(r)
-			if err != nil {
-				return nil, err
-			}
-			fmt.Fprintf(c.debugWriter, "--- Request:\n%s\n\n", dumpReq)
-		}
-
-		resp, err := c.httpClient.Do(r)
-		if err != nil {
-			return nil, err
-		}
-		response := &Response{Response: resp}
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			resp.Body.Close()
-			return response, err
-		}
-		resp.Body.Close()
-		resp.Body = io.NopCloser(bytes.NewReader(body))
-
-		if c.debugWriter != nil {
-			dumpResp, err := httputil.DumpResponse(resp, true)
-			if err != nil {
-				return nil, err
-			}
-			fmt.Fprintf(c.debugWriter, "--- Response:\n%s\n\n", dumpResp)
-		}
-
-		if err = response.readMeta(body); err != nil {
-			return response, fmt.Errorf("hcloud: error reading response meta data: %s", err)
-		}
-
-		if response.StatusCode >= 400 && response.StatusCode <= 599 {
-			err = errorFromResponse(response, body)
-			if err == nil {
-				err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
-			} else if IsError(err, ErrorCodeConflict) {
-				c.backoff(retries)
-				retries++
-				continue
-			}
-			return response, err
-		}
-		if v != nil {
-			if w, ok := v.(io.Writer); ok {
-				_, err = io.Copy(w, bytes.NewReader(body))
-			} else {
-				err = json.Unmarshal(body, v)
-			}
-		}
-
-		return response, err
-	}
-}
-
-func (c *Client) backoff(retries int) {
-	time.Sleep(c.backoffFunc(retries))
-}
-
-func (c *Client) all(f func(int) (*Response, error)) error {
-	var (
-		page = 1
-	)
-	for {
-		resp, err := f(page)
-		if err != nil {
-			return err
-		}
-		if resp.Meta.Pagination == nil || resp.Meta.Pagination.NextPage == 0 {
-			return nil
-		}
-		page = resp.Meta.Pagination.NextPage
-	}
+func (c *Client) Do(req *http.Request, v any) (*Response, error) {
+	return c.handler.Do(req, v)
 }
 
 func (c *Client) buildUserAgent() {
@@ -342,43 +336,6 @@ func (c *Client) buildUserAgent() {
 	}
 }
 
-func dumpRequest(r *http.Request) ([]byte, error) {
-	// Duplicate the request, so we can redact the auth header
-	rDuplicate := r.Clone(context.Background())
-	rDuplicate.Header.Set("Authorization", "REDACTED")
-
-	// To get the request body we need to read it before the request was actually sent.
-	// See https://github.com/golang/go/issues/29792
-	dumpReq, err := httputil.DumpRequestOut(rDuplicate, true)
-	if err != nil {
-		return nil, err
-	}
-
-	// Set original request body to the duplicate created by DumpRequestOut. The request body is not duplicated
-	// by .Clone() and instead just referenced, so it would be completely read otherwise.
-	r.Body = rDuplicate.Body
-
-	return dumpReq, nil
-}
-
-func errorFromResponse(resp *Response, body []byte) error {
-	if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
-		return nil
-	}
-
-	var respBody schema.ErrorResponse
-	if err := json.Unmarshal(body, &respBody); err != nil {
-		return nil
-	}
-	if respBody.Error.Code == "" && respBody.Error.Message == "" {
-		return nil
-	}
-
-	hcErr := ErrorFromSchema(respBody.Error)
-	hcErr.response = resp
-	return hcErr
-}
-
 const (
 	headerCorrelationID = "X-Correlation-Id"
 )
@@ -387,35 +344,34 @@ const (
 type Response struct {
 	*http.Response
 	Meta Meta
+
+	// body holds a copy of the http.Response body that must be used within the handler
+	// chain. The http.Response.Body is reserved for external users.
+	body []byte
 }
 
-func (r *Response) readMeta(body []byte) error {
-	if h := r.Header.Get("RateLimit-Limit"); h != "" {
-		r.Meta.Ratelimit.Limit, _ = strconv.Atoi(h)
-	}
-	if h := r.Header.Get("RateLimit-Remaining"); h != "" {
-		r.Meta.Ratelimit.Remaining, _ = strconv.Atoi(h)
-	}
-	if h := r.Header.Get("RateLimit-Reset"); h != "" {
-		if ts, err := strconv.ParseInt(h, 10, 64); err == nil {
-			r.Meta.Ratelimit.Reset = time.Unix(ts, 0)
-		}
+// populateBody copies the original [http.Response] body into the internal [Response] body
+// property, and restore the original [http.Response] body as if it was untouched.
+func (r *Response) populateBody() error {
+	// Read full response body and save it for later use
+	body, err := io.ReadAll(r.Body)
+	r.Body.Close()
+	if err != nil {
+		return err
 	}
+	r.body = body
 
-	if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
-		var s schema.MetaResponse
-		if err := json.Unmarshal(body, &s); err != nil {
-			return err
-		}
-		if s.Meta.Pagination != nil {
-			p := PaginationFromSchema(*s.Meta.Pagination)
-			r.Meta.Pagination = &p
-		}
-	}
+	// Restore the body as if it was untouched, as it might be read by external users
+	r.Body = io.NopCloser(bytes.NewReader(body))
 
 	return nil
 }
 
+// hasJSONBody returns whether the response has a JSON body.
+func (r *Response) hasJSONBody() bool {
+	return len(r.body) > 0 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/json")
+}
+
 // internalCorrelationID returns the unique ID of the request as set by the API. This ID can help with support requests,
 // as it allows the people working on identify this request in particular.
 func (r *Response) internalCorrelationID() string {
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_generic.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_generic.go
new file mode 100644
index 0000000000000000000000000000000000000000..dea12ad6358649d098d5ec2c2e33da7eb4d64185
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_generic.go
@@ -0,0 +1,101 @@
+package hcloud
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io"
+)
+
+func getRequest[Schema any](ctx context.Context, client *Client, url string) (Schema, *Response, error) {
+	var respBody Schema
+
+	req, err := client.NewRequest(ctx, "GET", url, nil)
+	if err != nil {
+		return respBody, nil, err
+	}
+
+	resp, err := client.Do(req, &respBody)
+	if err != nil {
+		return respBody, resp, err
+	}
+
+	return respBody, resp, nil
+}
+
+func postRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (Schema, *Response, error) {
+	var respBody Schema
+
+	var reqBodyReader io.Reader
+	if reqBody != nil {
+		reqBodyBytes, err := json.Marshal(reqBody)
+		if err != nil {
+			return respBody, nil, err
+		}
+
+		reqBodyReader = bytes.NewReader(reqBodyBytes)
+	}
+
+	req, err := client.NewRequest(ctx, "POST", url, reqBodyReader)
+	if err != nil {
+		return respBody, nil, err
+	}
+
+	resp, err := client.Do(req, &respBody)
+	if err != nil {
+		return respBody, resp, err
+	}
+
+	return respBody, resp, nil
+}
+
+func putRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (Schema, *Response, error) {
+	var respBody Schema
+
+	var reqBodyReader io.Reader
+	if reqBody != nil {
+		reqBodyBytes, err := json.Marshal(reqBody)
+		if err != nil {
+			return respBody, nil, err
+		}
+
+		reqBodyReader = bytes.NewReader(reqBodyBytes)
+	}
+
+	req, err := client.NewRequest(ctx, "PUT", url, reqBodyReader)
+	if err != nil {
+		return respBody, nil, err
+	}
+
+	resp, err := client.Do(req, &respBody)
+	if err != nil {
+		return respBody, resp, err
+	}
+
+	return respBody, resp, nil
+}
+
+func deleteRequest[Schema any](ctx context.Context, client *Client, url string) (Schema, *Response, error) {
+	var respBody Schema
+
+	req, err := client.NewRequest(ctx, "DELETE", url, nil)
+	if err != nil {
+		return respBody, nil, err
+	}
+
+	resp, err := client.Do(req, &respBody)
+	if err != nil {
+		return respBody, resp, err
+	}
+
+	return respBody, resp, nil
+}
+
+func deleteRequestNoResult(ctx context.Context, client *Client, url string) (*Response, error) {
+	req, err := client.NewRequest(ctx, "DELETE", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return client.Do(req, nil)
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..29a9376d6bf9aa3e3f951ba81fcca24518e26dd5
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler.go
@@ -0,0 +1,56 @@
+package hcloud
+
+import (
+	"context"
+	"net/http"
+)
+
+// handler is an interface representing a client request transaction. The handler are
+// meant to be chained, similarly to the [http.RoundTripper] interface.
+//
+// The handler chain is placed between the [Client] API operations and the
+// [http.Client].
+type handler interface {
+	Do(req *http.Request, v any) (resp *Response, err error)
+}
+
+// assembleHandlerChain assembles the chain of handlers used to make API requests.
+//
+// The order of the handlers is important.
+func assembleHandlerChain(client *Client) handler {
+	// Start down the chain: sending the http request
+	h := newHTTPHandler(client.httpClient)
+
+	// Insert debug writer if enabled
+	if client.debugWriter != nil {
+		h = wrapDebugHandler(h, client.debugWriter)
+	}
+
+	// Read rate limit headers
+	h = wrapRateLimitHandler(h)
+
+	// Build error from response
+	h = wrapErrorHandler(h)
+
+	// Retry request if condition are met
+	h = wrapRetryHandler(h, client.retryBackoffFunc, client.retryMaxRetries)
+
+	// Finally parse the response body into the provided schema
+	h = wrapParseHandler(h)
+
+	return h
+}
+
+// cloneRequest clones both the request and the request body.
+func cloneRequest(req *http.Request, ctx context.Context) (cloned *http.Request, err error) { //revive:disable:context-as-argument
+	cloned = req.Clone(ctx)
+
+	if req.ContentLength > 0 {
+		cloned.Body, err = req.GetBody()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return cloned, nil
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_debug.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_debug.go
new file mode 100644
index 0000000000000000000000000000000000000000..4aa867db44ed28bd05a544dee3fbc93b2b1d772c
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_debug.go
@@ -0,0 +1,50 @@
+package hcloud
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httputil"
+)
+
+func wrapDebugHandler(wrapped handler, output io.Writer) handler {
+	return &debugHandler{wrapped, output}
+}
+
+type debugHandler struct {
+	handler handler
+	output  io.Writer
+}
+
+func (h *debugHandler) Do(req *http.Request, v any) (resp *Response, err error) {
+	// Clone the request, so we can redact the auth header, read the body
+	// and use a new context.
+	cloned, err := cloneRequest(req, context.Background())
+	if err != nil {
+		return nil, err
+	}
+
+	cloned.Header.Set("Authorization", "REDACTED")
+
+	dumpReq, err := httputil.DumpRequestOut(cloned, true)
+	if err != nil {
+		return nil, err
+	}
+
+	fmt.Fprintf(h.output, "--- Request:\n%s\n\n", dumpReq)
+
+	resp, err = h.handler.Do(req, v)
+	if err != nil {
+		return resp, err
+	}
+
+	dumpResp, err := httputil.DumpResponse(resp.Response, true)
+	if err != nil {
+		return nil, err
+	}
+
+	fmt.Fprintf(h.output, "--- Response:\n%s\n\n", dumpResp)
+
+	return resp, err
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_error.go
new file mode 100644
index 0000000000000000000000000000000000000000..30c893fa9384fec2379eb9639502dcdbd0e151d0
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_error.go
@@ -0,0 +1,53 @@
+package hcloud
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
+)
+
+var ErrStatusCode = errors.New("server responded with status code")
+
+func wrapErrorHandler(wrapped handler) handler {
+	return &errorHandler{wrapped}
+}
+
+type errorHandler struct {
+	handler handler
+}
+
+func (h *errorHandler) Do(req *http.Request, v any) (resp *Response, err error) {
+	resp, err = h.handler.Do(req, v)
+	if err != nil {
+		return resp, err
+	}
+
+	if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
+		err = errorFromBody(resp)
+		if err == nil {
+			err = fmt.Errorf("hcloud: %w %d", ErrStatusCode, resp.StatusCode)
+		}
+	}
+	return resp, err
+}
+
+func errorFromBody(resp *Response) error {
+	if !resp.hasJSONBody() {
+		return nil
+	}
+
+	var s schema.ErrorResponse
+	if err := json.Unmarshal(resp.body, &s); err != nil {
+		return nil // nolint: nilerr
+	}
+	if s.Error.Code == "" && s.Error.Message == "" {
+		return nil
+	}
+
+	hcErr := ErrorFromSchema(s.Error)
+	hcErr.response = resp
+	return hcErr
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_http.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_http.go
new file mode 100644
index 0000000000000000000000000000000000000000..c0a02fe3986818fa0ad485028e98969718cd93ef
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_http.go
@@ -0,0 +1,28 @@
+package hcloud
+
+import (
+	"net/http"
+)
+
+func newHTTPHandler(httpClient *http.Client) handler {
+	return &httpHandler{httpClient}
+}
+
+type httpHandler struct {
+	httpClient *http.Client
+}
+
+func (h *httpHandler) Do(req *http.Request, _ interface{}) (*Response, error) {
+	httpResponse, err := h.httpClient.Do(req) //nolint: bodyclose
+	resp := &Response{Response: httpResponse}
+	if err != nil {
+		return resp, err
+	}
+
+	err = resp.populateBody()
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, err
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_parse.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_parse.go
new file mode 100644
index 0000000000000000000000000000000000000000..65e075b219dbcd84447f763c4d9e8d73bb4d39c9
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_parse.go
@@ -0,0 +1,50 @@
+package hcloud
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
+)
+
+func wrapParseHandler(wrapped handler) handler {
+	return &parseHandler{wrapped}
+}
+
+type parseHandler struct {
+	handler handler
+}
+
+func (h *parseHandler) Do(req *http.Request, v any) (resp *Response, err error) {
+	// respBody is not needed down the handler chain
+	resp, err = h.handler.Do(req, nil)
+	if err != nil {
+		return resp, err
+	}
+
+	if resp.hasJSONBody() {
+		// Parse the response meta
+		var s schema.MetaResponse
+		if err := json.Unmarshal(resp.body, &s); err != nil {
+			return resp, fmt.Errorf("hcloud: error reading response meta data: %w", err)
+		}
+		if s.Meta.Pagination != nil {
+			p := PaginationFromSchema(*s.Meta.Pagination)
+			resp.Meta.Pagination = &p
+		}
+	}
+
+	// Parse the response schema
+	if v != nil {
+		if w, ok := v.(io.Writer); ok {
+			_, err = io.Copy(w, bytes.NewReader(resp.body))
+		} else {
+			err = json.Unmarshal(resp.body, v)
+		}
+	}
+
+	return resp, err
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_rate_limit.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_rate_limit.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c53ba9e9a13fcf00e9e2daadba568c5e97c6bda
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_rate_limit.go
@@ -0,0 +1,36 @@
+package hcloud
+
+import (
+	"net/http"
+	"strconv"
+	"time"
+)
+
+func wrapRateLimitHandler(wrapped handler) handler {
+	return &rateLimitHandler{wrapped}
+}
+
+type rateLimitHandler struct {
+	handler handler
+}
+
+func (h *rateLimitHandler) Do(req *http.Request, v any) (resp *Response, err error) {
+	resp, err = h.handler.Do(req, v)
+
+	// Ensure the embedded [*http.Response] is not nil, e.g. on canceled context
+	if resp != nil && resp.Response != nil && resp.Response.Header != nil {
+		if h := resp.Header.Get("RateLimit-Limit"); h != "" {
+			resp.Meta.Ratelimit.Limit, _ = strconv.Atoi(h)
+		}
+		if h := resp.Header.Get("RateLimit-Remaining"); h != "" {
+			resp.Meta.Ratelimit.Remaining, _ = strconv.Atoi(h)
+		}
+		if h := resp.Header.Get("RateLimit-Reset"); h != "" {
+			if ts, err := strconv.ParseInt(h, 10, 64); err == nil {
+				resp.Meta.Ratelimit.Reset = time.Unix(ts, 0)
+			}
+		}
+	}
+
+	return resp, err
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_retry.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_retry.go
new file mode 100644
index 0000000000000000000000000000000000000000..b0983c9699486c29e4d51bde699be071770bc9aa
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_retry.go
@@ -0,0 +1,84 @@
+package hcloud
+
+import (
+	"errors"
+	"net"
+	"net/http"
+	"time"
+)
+
+func wrapRetryHandler(wrapped handler, backoffFunc BackoffFunc, maxRetries int) handler {
+	return &retryHandler{wrapped, backoffFunc, maxRetries}
+}
+
+type retryHandler struct {
+	handler     handler
+	backoffFunc BackoffFunc
+	maxRetries  int
+}
+
+func (h *retryHandler) Do(req *http.Request, v any) (resp *Response, err error) {
+	retries := 0
+	ctx := req.Context()
+
+	for {
+		// Clone the request using the original context
+		cloned, err := cloneRequest(req, ctx)
+		if err != nil {
+			return nil, err
+		}
+
+		resp, err = h.handler.Do(cloned, v)
+		if err != nil {
+			// Beware the diversity of the errors:
+			// - request preparation
+			// - network connectivity
+			// - http status code (see [errorHandler])
+			if ctx.Err() != nil {
+				// early return if the context was canceled or timed out
+				return resp, err
+			}
+
+			if retries < h.maxRetries && retryPolicy(resp, err) {
+				select {
+				case <-ctx.Done():
+					return resp, err
+				case <-time.After(h.backoffFunc(retries)):
+					retries++
+					continue
+				}
+			}
+		}
+
+		return resp, err
+	}
+}
+
+func retryPolicy(resp *Response, err error) bool {
+	if err != nil {
+		var apiErr Error
+		var netErr net.Error
+
+		switch {
+		case errors.As(err, &apiErr):
+			switch apiErr.Code { //nolint:exhaustive
+			case ErrorCodeConflict:
+				return true
+			case ErrorCodeRateLimitExceeded:
+				return true
+			}
+		case errors.Is(err, ErrStatusCode):
+			switch resp.Response.StatusCode {
+			// 5xx errors
+			case http.StatusBadGateway, http.StatusGatewayTimeout:
+				return true
+			}
+		case errors.As(err, &netErr):
+			if netErr.Timeout() {
+				return true
+			}
+		}
+	}
+
+	return false
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_helper.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_helper.go
new file mode 100644
index 0000000000000000000000000000000000000000..819ae2460ebd8038f6e02a9ab7415a3c260c7b1b
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_helper.go
@@ -0,0 +1,85 @@
+package hcloud
+
+import (
+	"context"
+	"strconv"
+)
+
+// allFromSchemaFunc transform each item in the list using the FromSchema function, and
+// returns the result.
+func allFromSchemaFunc[T, V any](all []T, fn func(T) V) []V {
+	result := make([]V, len(all))
+	for i, t := range all {
+		result[i] = fn(t)
+	}
+
+	return result
+}
+
+// iterPages fetches each pages using the list function, and returns the result.
+func iterPages[T any](listFn func(int) ([]*T, *Response, error)) ([]*T, error) {
+	page := 1
+	result := []*T{}
+
+	for {
+		pageResult, resp, err := listFn(page)
+		if err != nil {
+			return nil, err
+		}
+
+		result = append(result, pageResult...)
+
+		if resp.Meta.Pagination == nil || resp.Meta.Pagination.NextPage == 0 {
+			return result, nil
+		}
+		page = resp.Meta.Pagination.NextPage
+	}
+}
+
+// firstBy fetches a list of items using the list function, and returns the first item
+// of the list if present otherwise nil.
+func firstBy[T any](listFn func() ([]*T, *Response, error)) (*T, *Response, error) {
+	items, resp, err := listFn()
+	if len(items) == 0 {
+		return nil, resp, err
+	}
+
+	return items[0], resp, err
+}
+
+// firstByName is a wrapper around [firstBy], that checks if the provided name is not
+// empty.
+func firstByName[T any](name string, listFn func() ([]*T, *Response, error)) (*T, *Response, error) {
+	if name == "" {
+		return nil, nil, nil
+	}
+
+	return firstBy(listFn)
+}
+
+// getByIDOrName fetches the resource by ID when the identifier is an integer, otherwise
+// by Name. To support resources that have a integer as Name, an additional attempt is
+// made to fetch the resource by Name using the ID.
+//
+// Since API managed resources (locations, server types, ...) do not have integers as
+// names, this function is only meaningful for user managed resources (ssh keys,
+// servers).
+func getByIDOrName[T any](
+	ctx context.Context,
+	getByIDFn func(ctx context.Context, id int64) (*T, *Response, error),
+	getByNameFn func(ctx context.Context, name string) (*T, *Response, error),
+	idOrName string,
+) (*T, *Response, error) {
+	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
+		result, resp, err := getByIDFn(ctx, id)
+		if err != nil {
+			return result, resp, err
+		}
+		if result != nil {
+			return result, resp, err
+		}
+		// Fallback to get by Name if the resource was not found
+	}
+
+	return getByNameFn(ctx, idOrName)
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go
index f7e9283ed6d7153e340781605205ecc34571d4c4..f821c90eb9e8bdcc3d4661f43edfeda4a23b3cad 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"strconv"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -32,32 +33,27 @@ type DatacenterClient struct {
 
 // GetByID retrieves a datacenter by its ID. If the datacenter does not exist, nil is returned.
 func (c *DatacenterClient) GetByID(ctx context.Context, id int64) (*Datacenter, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/datacenters/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/datacenters/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.DatacenterGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.DatacenterGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
 		return nil, resp, err
 	}
-	return DatacenterFromSchema(body.Datacenter), resp, nil
+
+	return DatacenterFromSchema(respBody.Datacenter), resp, nil
 }
 
 // GetByName retrieves a datacenter by its name. If the datacenter does not exist, nil is returned.
 func (c *DatacenterClient) GetByName(ctx context.Context, name string) (*Datacenter, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	datacenters, response, err := c.List(ctx, DatacenterListOpts{Name: name})
-	if len(datacenters) == 0 {
-		return nil, response, err
-	}
-	return datacenters[0], response, err
+	return firstByName(name, func() ([]*Datacenter, *Response, error) {
+		return c.List(ctx, DatacenterListOpts{Name: name})
+	})
 }
 
 // Get retrieves a datacenter by its ID if the input can be parsed as an integer, otherwise it
@@ -92,22 +88,17 @@ func (l DatacenterListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *DatacenterClient) List(ctx context.Context, opts DatacenterListOpts) ([]*Datacenter, *Response, error) {
-	path := "/datacenters?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/datacenters?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.DatacenterListResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.DatacenterListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	datacenters := make([]*Datacenter, 0, len(body.Datacenters))
-	for _, i := range body.Datacenters {
-		datacenters = append(datacenters, DatacenterFromSchema(i))
+		return nil, resp, err
 	}
-	return datacenters, resp, nil
+
+	return allFromSchemaFunc(respBody.Datacenters, DatacenterFromSchema), resp, nil
 }
 
 // All returns all datacenters.
@@ -117,20 +108,8 @@ func (c *DatacenterClient) All(ctx context.Context) ([]*Datacenter, error) {
 
 // AllWithOpts returns all datacenters for the given options.
 func (c *DatacenterClient) AllWithOpts(ctx context.Context, opts DatacenterListOpts) ([]*Datacenter, error) {
-	allDatacenters := []*Datacenter{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Datacenter, *Response, error) {
 		opts.Page = page
-		datacenters, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allDatacenters = append(allDatacenters, datacenters...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allDatacenters, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go
index 371a92e31edbeed9832ac5cdab95bf914907747d..0e6f556b9bb9fa1a5f06140c1384135350a6d7d8 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go
@@ -4,6 +4,8 @@ import (
 	"errors"
 	"fmt"
 	"net"
+	"slices"
+	"strings"
 )
 
 // ErrorCode represents an error code returned from the API.
@@ -29,6 +31,7 @@ const (
 	ErrorCodeRobotUnavailable      ErrorCode = "robot_unavailable"       // Robot was not available. The caller may retry the operation after a short delay
 	ErrorCodeResourceLocked        ErrorCode = "resource_locked"         // The resource is locked. The caller should contact support
 	ErrorUnsupportedError          ErrorCode = "unsupported_error"       // The given resource does not support this
+	ErrorDeprecatedAPIEndpoint     ErrorCode = "deprecated_api_endpoint" // The request can not be answered because the API functionality was removed
 
 	// Server related error codes.
 
@@ -126,11 +129,16 @@ type ErrorDetailsInvalidInputField struct {
 	Messages []string
 }
 
-// IsError returns whether err is an API error with the given error code.
-func IsError(err error, code ErrorCode) bool {
+// ErrorDetailsDeprecatedAPIEndpoint contains the details of a 'deprecated_api_endpoint' error.
+type ErrorDetailsDeprecatedAPIEndpoint struct {
+	Announcement string
+}
+
+// IsError returns whether err is an API error with one of the given error codes.
+func IsError(err error, code ...ErrorCode) bool {
 	var apiErr Error
 	ok := errors.As(err, &apiErr)
-	return ok && apiErr.Code == code
+	return ok && slices.Index(code, apiErr.Code) > -1
 }
 
 type InvalidIPError struct {
@@ -148,3 +156,40 @@ type DNSNotFoundError struct {
 func (e DNSNotFoundError) Error() string {
 	return fmt.Sprintf("dns for ip %s not found", e.IP.String())
 }
+
+// ArgumentError is a type of error returned when validating arguments.
+type ArgumentError string
+
+func (e ArgumentError) Error() string { return string(e) }
+
+func newArgumentErrorf(format string, args ...any) ArgumentError {
+	return ArgumentError(fmt.Sprintf(format, args...))
+}
+
+func missingArgument(name string, obj any) error {
+	return newArgumentErrorf("missing argument '%s' [%T]", name, obj)
+}
+
+func invalidArgument(name string, obj any) error {
+	return newArgumentErrorf("invalid value '%v' for argument '%s' [%T]", obj, name, obj)
+}
+
+func missingField(obj any, field string) error {
+	return newArgumentErrorf("missing field [%s] in [%T]", field, obj)
+}
+
+func invalidFieldValue(obj any, field string, value any) error {
+	return newArgumentErrorf("invalid value '%v' for field [%s] in [%T]", value, field, obj)
+}
+
+func missingOneOfFields(obj any, fields ...string) error {
+	return newArgumentErrorf("missing one of fields [%s] in [%T]", strings.Join(fields, ", "), obj)
+}
+
+func mutuallyExclusiveFields(obj any, fields ...string) error {
+	return newArgumentErrorf("found mutually exclusive fields [%s] in [%T]", strings.Join(fields, ", "), obj)
+}
+
+func missingRequiredTogetherFields(obj any, fields ...string) error {
+	return newArgumentErrorf("missing required together fields [%s] in [%T]", strings.Join(fields, ", "), obj)
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/actionutil/actions.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/actionutil/actions.go
new file mode 100644
index 0000000000000000000000000000000000000000..b645d2bb0233922a0f46e937f0b53bfc51991083
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/actionutil/actions.go
@@ -0,0 +1,11 @@
+package actionutil
+
+import "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud"
+
+// AppendNext return the action and the next actions in a new slice.
+func AppendNext(action *hcloud.Action, nextActions []*hcloud.Action) []*hcloud.Action {
+	all := make([]*hcloud.Action, 0, 1+len(nextActions))
+	all = append(all, action)
+	all = append(all, nextActions...)
+	return all
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil/ctxutil.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil/ctxutil.go
new file mode 100644
index 0000000000000000000000000000000000000000..de0ce95f5cce05ca5484ef2d351092f88b9fe308
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil/ctxutil.go
@@ -0,0 +1,30 @@
+package ctxutil
+
+import (
+	"context"
+	"strings"
+)
+
+// key is an unexported type to prevents collisions with keys defined in other packages.
+type key struct{}
+
+// opPathKey is the key for operation path in Contexts.
+var opPathKey = key{}
+
+// SetOpPath processes the operation path and save it in the context before returning it.
+func SetOpPath(ctx context.Context, path string) context.Context {
+	path, _, _ = strings.Cut(path, "?")
+	path = strings.ReplaceAll(path, "%d", "-")
+	path = strings.ReplaceAll(path, "%s", "-")
+
+	return context.WithValue(ctx, opPathKey, path)
+}
+
+// OpPath returns the operation path from the context.
+func OpPath(ctx context.Context) string {
+	result, ok := ctx.Value(opPathKey).(string)
+	if !ok {
+		return ""
+	}
+	return result
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/doc.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..96506ae2557f2fbf3a1e552da986d6afa36c7c89
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/doc.go
@@ -0,0 +1,4 @@
+// Package exp is a namespace that holds experimental features for the `hcloud-go` library.
+//
+// Breaking changes may occur without notice. Do not use in production!
+package exp
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/envutil/env.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/envutil/env.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e6aa6ee6ff9bae11f9b9600ddcbd6bf31880f56
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/envutil/env.go
@@ -0,0 +1,40 @@
+package envutil
+
+import (
+	"fmt"
+	"os"
+	"strings"
+)
+
+// LookupEnvWithFile retrieves the value of the environment variable named by the key (e.g.
+// HCLOUD_TOKEN). If the previous environment variable is not set, it retrieves the
+// content of the file located by a second environment variable named by the key +
+// '_FILE' (.e.g HCLOUD_TOKEN_FILE).
+//
+// For both cases, the returned value may be empty.
+//
+// The value from the environment takes precedence over the value from the file.
+func LookupEnvWithFile(key string) (string, error) {
+	// Check if the value is set in the environment (e.g. HCLOUD_TOKEN)
+	value, ok := os.LookupEnv(key)
+	if ok {
+		return value, nil
+	}
+
+	key += "_FILE"
+
+	// Check if the value is set via a file (e.g. HCLOUD_TOKEN_FILE)
+	valueFile, ok := os.LookupEnv(key)
+	if !ok {
+		// Validation of the value happens outside of this function
+		return "", nil
+	}
+
+	// Read the content of the file
+	valueBytes, err := os.ReadFile(valueFile)
+	if err != nil {
+		return "", fmt.Errorf("failed to read %s: %w", key, err)
+	}
+
+	return strings.TrimSpace(string(valueBytes)), nil
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/randutil/id.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/randutil/id.go
new file mode 100644
index 0000000000000000000000000000000000000000..75dab5f400c132309854b28292e55601f3cb969d
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/randutil/id.go
@@ -0,0 +1,19 @@
+package randutil
+
+import (
+	"crypto/rand"
+	"encoding/hex"
+	"fmt"
+)
+
+// GenerateID returns a hex encoded random string with a len of 8 chars similar to
+// "2873fce7".
+func GenerateID() string {
+	b := make([]byte, 4)
+	_, err := rand.Read(b)
+	if err != nil {
+		// Should never happen as of go1.24: https://github.com/golang/go/issues/66821
+		panic(fmt.Errorf("failed to generate random string: %w", err))
+	}
+	return hex.EncodeToString(b)
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/sshutil/ssh_key.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/sshutil/ssh_key.go
new file mode 100644
index 0000000000000000000000000000000000000000..4465d5c70fbed9d419a18ca7ed6a5dd4a622e98a
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/sshutil/ssh_key.go
@@ -0,0 +1,86 @@
+package sshutil
+
+import (
+	"crypto"
+	"crypto/ed25519"
+	"encoding/pem"
+	"fmt"
+
+	"golang.org/x/crypto/ssh"
+)
+
+// GenerateKeyPair generates a new ed25519 ssh key pair, and returns the private key and
+// the public key respectively.
+func GenerateKeyPair() ([]byte, []byte, error) {
+	pub, priv, err := ed25519.GenerateKey(nil)
+	if err != nil {
+		return nil, nil, fmt.Errorf("could not generate key pair: %w", err)
+	}
+
+	privBytes, err := encodePrivateKey(priv)
+	if err != nil {
+		return nil, nil, fmt.Errorf("could not encode private key: %w", err)
+	}
+
+	pubBytes, err := encodePublicKey(pub)
+	if err != nil {
+		return nil, nil, fmt.Errorf("could not encode public key: %w", err)
+	}
+
+	return privBytes, pubBytes, nil
+}
+
+func encodePrivateKey(priv crypto.PrivateKey) ([]byte, error) {
+	privPem, err := ssh.MarshalPrivateKey(priv, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return pem.EncodeToMemory(privPem), nil
+}
+
+func encodePublicKey(pub crypto.PublicKey) ([]byte, error) {
+	sshPub, err := ssh.NewPublicKey(pub)
+	if err != nil {
+		return nil, err
+	}
+
+	return ssh.MarshalAuthorizedKey(sshPub), nil
+}
+
+type privateKeyWithPublicKey interface {
+	crypto.PrivateKey
+	Public() crypto.PublicKey
+}
+
+// GeneratePublicKey generate a public key from the provided private key.
+func GeneratePublicKey(privBytes []byte) ([]byte, error) {
+	priv, err := ssh.ParseRawPrivateKey(privBytes)
+	if err != nil {
+		return nil, fmt.Errorf("could not decode private key: %w", err)
+	}
+
+	key, ok := priv.(privateKeyWithPublicKey)
+	if !ok {
+		return nil, fmt.Errorf("private key doesn't export Public() crypto.PublicKey")
+	}
+
+	pubBytes, err := encodePublicKey(key.Public())
+	if err != nil {
+		return nil, fmt.Errorf("could not encode public key: %w", err)
+	}
+
+	return pubBytes, nil
+}
+
+// GetPublicKeyFingerprint generate the finger print for the provided public key.
+func GetPublicKeyFingerprint(pubBytes []byte) (string, error) {
+	pub, _, _, _, err := ssh.ParseAuthorizedKey(pubBytes)
+	if err != nil {
+		return "", fmt.Errorf("could not decode public key: %w", err)
+	}
+
+	fingerprint := ssh.FingerprintLegacyMD5(pub)
+
+	return fingerprint, nil
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/labelutil/selector.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/labelutil/selector.go
new file mode 100644
index 0000000000000000000000000000000000000000..c5e84f08a2e547a83b03b6df14f43cb8b5498ce9
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/labelutil/selector.go
@@ -0,0 +1,24 @@
+package labelutil
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// Selector combines the label set into a [label selector](https://docs.hetzner.cloud/#label-selector) that only selects
+// resources have all specified labels set.
+//
+// The selector string can be used to filter resources when listing, for example with [hcloud.ServerClient.AllWithOpts()].
+func Selector(labels map[string]string) string {
+	selectors := make([]string, 0, len(labels))
+
+	for k, v := range labels {
+		selectors = append(selectors, fmt.Sprintf("%s=%s", k, v))
+	}
+
+	// Reproducible result for tests
+	sort.Strings(selectors)
+
+	return strings.Join(selectors, ",")
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/mockutil/http.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/mockutil/http.go
new file mode 100644
index 0000000000000000000000000000000000000000..26ff4874022758fab75672c2698a8536f23629ee
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/mockutil/http.go
@@ -0,0 +1,123 @@
+package mockutil
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// Request describes a http request that a [httptest.Server] should receive, and the
+// corresponding response to return.
+//
+// Additional checks on the request (e.g. request body) may be added using the
+// [Request.Want] function.
+//
+// The response body is populated from either a JSON struct, or a JSON string.
+type Request struct {
+	Method string
+	Path   string
+	Want   func(t *testing.T, r *http.Request)
+
+	Status  int
+	JSON    any
+	JSONRaw string
+}
+
+// Handler is using a [Server] to mock http requests provided by the user.
+func Handler(t *testing.T, requests []Request) http.HandlerFunc {
+	t.Helper()
+
+	server := NewServer(t, requests)
+	t.Cleanup(server.close)
+
+	return server.handler
+}
+
+// NewServer returns a new mock server that closes itself at the end of the test.
+func NewServer(t *testing.T, requests []Request) *Server {
+	t.Helper()
+
+	o := &Server{t: t}
+	o.Server = httptest.NewServer(http.HandlerFunc(o.handler))
+	t.Cleanup(o.close)
+
+	o.Expect(requests)
+
+	return o
+}
+
+// Server embeds a [httptest.Server] that answers HTTP calls with a list of expected [Request].
+//
+// Request matching is based on the request count, and the user provided request will be
+// iterated over.
+//
+// A Server must be created using the [NewServer] function.
+type Server struct {
+	*httptest.Server
+
+	t *testing.T
+
+	requests []Request
+	index    int
+}
+
+// Expect adds requests to the list of requests expected by the [Server].
+func (m *Server) Expect(requests []Request) {
+	m.requests = append(m.requests, requests...)
+}
+
+func (m *Server) close() {
+	m.t.Helper()
+
+	m.Server.Close()
+
+	assert.EqualValues(m.t, len(m.requests), m.index, "expected more calls")
+}
+
+func (m *Server) handler(w http.ResponseWriter, r *http.Request) {
+	if testing.Verbose() {
+		m.t.Logf("call %d: %s %s\n", m.index, r.Method, r.RequestURI)
+	}
+
+	if m.index >= len(m.requests) {
+		m.t.Fatalf("received unknown call %d", m.index)
+	}
+
+	expected := m.requests[m.index]
+
+	expectedCall := expected.Method
+	foundCall := r.Method
+	if expected.Path != "" {
+		expectedCall += " " + expected.Path
+		foundCall += " " + r.RequestURI
+	}
+	require.Equal(m.t, expectedCall, foundCall) // nolint: testifylint
+
+	if expected.Want != nil {
+		expected.Want(m.t, r)
+	}
+
+	switch {
+	case expected.JSON != nil:
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(expected.Status)
+		if err := json.NewEncoder(w).Encode(expected.JSON); err != nil {
+			m.t.Fatal(err)
+		}
+	case expected.JSONRaw != "":
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(expected.Status)
+		_, err := w.Write([]byte(expected.JSONRaw))
+		if err != nil {
+			m.t.Fatal(err)
+		}
+	default:
+		w.WriteHeader(expected.Status)
+	}
+
+	m.index++
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go
index f046e7344f91f0d52272c787093f49c3c14192ab..11b28064cd26d999f045956516c6834ef9762f33 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go
@@ -1,16 +1,13 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -96,41 +93,33 @@ type FirewallClient struct {
 
 // GetByID retrieves a Firewall by its ID. If the Firewall does not exist, nil is returned.
 func (c *FirewallClient) GetByID(ctx context.Context, id int64) (*Firewall, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/firewalls/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/firewalls/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.FirewallGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.FirewallGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return FirewallFromSchema(body.Firewall), resp, nil
+
+	return FirewallFromSchema(respBody.Firewall), resp, nil
 }
 
 // GetByName retrieves a Firewall by its name. If the Firewall does not exist, nil is returned.
 func (c *FirewallClient) GetByName(ctx context.Context, name string) (*Firewall, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	firewalls, response, err := c.List(ctx, FirewallListOpts{Name: name})
-	if len(firewalls) == 0 {
-		return nil, response, err
-	}
-	return firewalls[0], response, err
+	return firstByName(name, func() ([]*Firewall, *Response, error) {
+		return c.List(ctx, FirewallListOpts{Name: name})
+	})
 }
 
 // Get retrieves a Firewall by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a Firewall by its name. If the Firewall does not exist, nil is returned.
 func (c *FirewallClient) Get(ctx context.Context, idOrName string) (*Firewall, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // FirewallListOpts specifies options for listing Firewalls.
@@ -156,22 +145,17 @@ func (l FirewallListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *FirewallClient) List(ctx context.Context, opts FirewallListOpts) ([]*Firewall, *Response, error) {
-	path := "/firewalls?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/firewalls?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.FirewallListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.FirewallListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	firewalls := make([]*Firewall, 0, len(body.Firewalls))
-	for _, s := range body.Firewalls {
-		firewalls = append(firewalls, FirewallFromSchema(s))
+		return nil, resp, err
 	}
-	return firewalls, resp, nil
+
+	return allFromSchemaFunc(respBody.Firewalls, FirewallFromSchema), resp, nil
 }
 
 // All returns all Firewalls.
@@ -181,22 +165,10 @@ func (c *FirewallClient) All(ctx context.Context) ([]*Firewall, error) {
 
 // AllWithOpts returns all Firewalls for the given options.
 func (c *FirewallClient) AllWithOpts(ctx context.Context, opts FirewallListOpts) ([]*Firewall, error) {
-	allFirewalls := []*Firewall{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Firewall, *Response, error) {
 		opts.Page = page
-		firewalls, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allFirewalls = append(allFirewalls, firewalls...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allFirewalls, nil
 }
 
 // FirewallCreateOpts specifies options for creating a new Firewall.
@@ -210,7 +182,7 @@ type FirewallCreateOpts struct {
 // Validate checks if options are valid.
 func (o FirewallCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	return nil
 }
@@ -223,28 +195,27 @@ type FirewallCreateResult struct {
 
 // Create creates a new Firewall.
 func (c *FirewallClient) Create(ctx context.Context, opts FirewallCreateOpts) (FirewallCreateResult, *Response, error) {
+	const opPath = "/firewalls"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := FirewallCreateResult{}
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
-		return FirewallCreateResult{}, nil, err
+		return result, nil, err
 	}
+
 	reqBody := firewallCreateOptsToSchema(opts)
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return FirewallCreateResult{}, nil, err
-	}
-	req, err := c.client.NewRequest(ctx, "POST", "/firewalls", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return FirewallCreateResult{}, nil, err
-	}
 
-	respBody := schema.FirewallCreateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FirewallCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return FirewallCreateResult{}, resp, err
-	}
-	result := FirewallCreateResult{
-		Firewall: FirewallFromSchema(respBody.Firewall),
-		Actions:  ActionsFromSchema(respBody.Actions),
+		return result, resp, err
 	}
+
+	result.Firewall = FirewallFromSchema(respBody.Firewall)
+	result.Actions = ActionsFromSchema(respBody.Actions)
+
 	return result, resp, nil
 }
 
@@ -256,6 +227,11 @@ type FirewallUpdateOpts struct {
 
 // Update updates a Firewall.
 func (c *FirewallClient) Update(ctx context.Context, firewall *Firewall, opts FirewallUpdateOpts) (*Firewall, *Response, error) {
+	const opPath = "/firewalls/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, firewall.ID)
+
 	reqBody := schema.FirewallUpdateRequest{}
 	if opts.Name != "" {
 		reqBody.Name = &opts.Name
@@ -263,32 +239,23 @@ func (c *FirewallClient) Update(ctx context.Context, firewall *Firewall, opts Fi
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/firewalls/%d", firewall.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.FirewallUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.FirewallUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return FirewallFromSchema(respBody.Firewall), resp, nil
 }
 
 // Delete deletes a Firewall.
 func (c *FirewallClient) Delete(ctx context.Context, firewall *Firewall) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/firewalls/%d", firewall.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/firewalls/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, firewall.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // FirewallSetRulesOpts specifies options for setting rules of a Firewall.
@@ -298,75 +265,59 @@ type FirewallSetRulesOpts struct {
 
 // SetRules sets the rules of a Firewall.
 func (c *FirewallClient) SetRules(ctx context.Context, firewall *Firewall, opts FirewallSetRulesOpts) ([]*Action, *Response, error) {
-	reqBody := firewallSetRulesOptsToSchema(opts)
+	const opPath = "/firewalls/%d/actions/set_rules"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, firewall.ID)
 
-	path := fmt.Sprintf("/firewalls/%d/actions/set_rules", firewall.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqBody := firewallSetRulesOptsToSchema(opts)
 
-	var respBody schema.FirewallActionSetRulesResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FirewallActionSetRulesResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionsFromSchema(respBody.Actions), resp, nil
 }
 
 func (c *FirewallClient) ApplyResources(ctx context.Context, firewall *Firewall, resources []FirewallResource) ([]*Action, *Response, error) {
+	const opPath = "/firewalls/%d/actions/apply_to_resources"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, firewall.ID)
+
 	applyTo := make([]schema.FirewallResource, len(resources))
 	for i, r := range resources {
 		applyTo[i] = firewallResourceToSchema(r)
 	}
 
 	reqBody := schema.FirewallActionApplyToResourcesRequest{ApplyTo: applyTo}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/firewalls/%d/actions/apply_to_resources", firewall.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody schema.FirewallActionApplyToResourcesResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FirewallActionApplyToResourcesResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionsFromSchema(respBody.Actions), resp, nil
 }
 
 func (c *FirewallClient) RemoveResources(ctx context.Context, firewall *Firewall, resources []FirewallResource) ([]*Action, *Response, error) {
+	const opPath = "/firewalls/%d/actions/remove_from_resources"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, firewall.ID)
+
 	removeFrom := make([]schema.FirewallResource, len(resources))
 	for i, r := range resources {
 		removeFrom[i] = firewallResourceToSchema(r)
 	}
 
 	reqBody := schema.FirewallActionRemoveFromResourcesRequest{RemoveFrom: removeFrom}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/firewalls/%d/actions/remove_from_resources", firewall.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody schema.FirewallActionRemoveFromResourcesResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FirewallActionRemoveFromResourcesResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionsFromSchema(respBody.Actions), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go
index 569576c0209fa3051ca76219b75688a16ad100a4..e200b48dca3f4513f2b1cfc98f2d718ee051321e 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go
@@ -1,16 +1,13 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -54,26 +51,21 @@ const (
 // changeDNSPtr changes or resets the reverse DNS pointer for an IP address.
 // Pass a nil ptr to reset the reverse DNS pointer to its default value.
 func (f *FloatingIP) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) {
+	const opPath = "/floating_ips/%d/actions/change_dns_ptr"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, f.ID)
+
 	reqBody := schema.FloatingIPActionChangeDNSPtrRequest{
 		IP:     ip.String(),
 		DNSPtr: ptr,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/floating_ips/%d/actions/change_dns_ptr", f.ID)
-	req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.FloatingIPActionChangeDNSPtrResponse{}
-	resp, err := client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FloatingIPActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -97,41 +89,33 @@ type FloatingIPClient struct {
 // GetByID retrieves a Floating IP by its ID. If the Floating IP does not exist,
 // nil is returned.
 func (c *FloatingIPClient) GetByID(ctx context.Context, id int64) (*FloatingIP, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/floating_ips/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/floating_ips/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.FloatingIPGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.FloatingIPGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
 		return nil, resp, err
 	}
-	return FloatingIPFromSchema(body.FloatingIP), resp, nil
+
+	return FloatingIPFromSchema(respBody.FloatingIP), resp, nil
 }
 
 // GetByName retrieves a Floating IP by its name. If the Floating IP does not exist, nil is returned.
 func (c *FloatingIPClient) GetByName(ctx context.Context, name string) (*FloatingIP, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	floatingIPs, response, err := c.List(ctx, FloatingIPListOpts{Name: name})
-	if len(floatingIPs) == 0 {
-		return nil, response, err
-	}
-	return floatingIPs[0], response, err
+	return firstByName(name, func() ([]*FloatingIP, *Response, error) {
+		return c.List(ctx, FloatingIPListOpts{Name: name})
+	})
 }
 
 // Get retrieves a Floating IP by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a Floating IP by its name. If the Floating IP does not exist, nil is returned.
 func (c *FloatingIPClient) Get(ctx context.Context, idOrName string) (*FloatingIP, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // FloatingIPListOpts specifies options for listing Floating IPs.
@@ -157,22 +141,17 @@ func (l FloatingIPListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *FloatingIPClient) List(ctx context.Context, opts FloatingIPListOpts) ([]*FloatingIP, *Response, error) {
-	path := "/floating_ips?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/floating_ips?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.FloatingIPListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.FloatingIPListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	floatingIPs := make([]*FloatingIP, 0, len(body.FloatingIPs))
-	for _, s := range body.FloatingIPs {
-		floatingIPs = append(floatingIPs, FloatingIPFromSchema(s))
+		return nil, resp, err
 	}
-	return floatingIPs, resp, nil
+
+	return allFromSchemaFunc(respBody.FloatingIPs, FloatingIPFromSchema), resp, nil
 }
 
 // All returns all Floating IPs.
@@ -182,22 +161,10 @@ func (c *FloatingIPClient) All(ctx context.Context) ([]*FloatingIP, error) {
 
 // AllWithOpts returns all Floating IPs for the given options.
 func (c *FloatingIPClient) AllWithOpts(ctx context.Context, opts FloatingIPListOpts) ([]*FloatingIP, error) {
-	allFloatingIPs := []*FloatingIP{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*FloatingIP, *Response, error) {
 		opts.Page = page
-		floatingIPs, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allFloatingIPs = append(allFloatingIPs, floatingIPs...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allFloatingIPs, nil
 }
 
 // FloatingIPCreateOpts specifies options for creating a Floating IP.
@@ -216,10 +183,10 @@ func (o FloatingIPCreateOpts) Validate() error {
 	case FloatingIPTypeIPv4, FloatingIPTypeIPv6:
 		break
 	default:
-		return errors.New("missing or invalid type")
+		return invalidFieldValue(o, "Type", o.Type)
 	}
 	if o.HomeLocation == nil && o.Server == nil {
-		return errors.New("one of home location or server is required")
+		return missingOneOfFields(o, "HomeLocation", "Server")
 	}
 	return nil
 }
@@ -232,8 +199,15 @@ type FloatingIPCreateResult struct {
 
 // Create creates a Floating IP.
 func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts) (FloatingIPCreateResult, *Response, error) {
+	result := FloatingIPCreateResult{}
+
+	const opPath = "/floating_ips"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
-		return FloatingIPCreateResult{}, nil, err
+		return result, nil, err
 	}
 
 	reqBody := schema.FloatingIPCreateRequest{
@@ -250,38 +224,28 @@ func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return FloatingIPCreateResult{}, nil, err
-	}
 
-	req, err := c.client.NewRequest(ctx, "POST", "/floating_ips", bytes.NewReader(reqBodyData))
+	respBody, resp, err := postRequest[schema.FloatingIPCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return FloatingIPCreateResult{}, nil, err
+		return result, resp, err
 	}
 
-	var respBody schema.FloatingIPCreateResponse
-	resp, err := c.client.Do(req, &respBody)
-	if err != nil {
-		return FloatingIPCreateResult{}, resp, err
-	}
-	var action *Action
+	result.FloatingIP = FloatingIPFromSchema(respBody.FloatingIP)
 	if respBody.Action != nil {
-		action = ActionFromSchema(*respBody.Action)
+		result.Action = ActionFromSchema(*respBody.Action)
 	}
-	return FloatingIPCreateResult{
-		FloatingIP: FloatingIPFromSchema(respBody.FloatingIP),
-		Action:     action,
-	}, resp, nil
+
+	return result, resp, nil
 }
 
 // Delete deletes a Floating IP.
 func (c *FloatingIPClient) Delete(ctx context.Context, floatingIP *FloatingIP) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/floating_ips/%d", floatingIP.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/floating_ips/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, floatingIP.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // FloatingIPUpdateOpts specifies options for updating a Floating IP.
@@ -293,6 +257,11 @@ type FloatingIPUpdateOpts struct {
 
 // Update updates a Floating IP.
 func (c *FloatingIPClient) Update(ctx context.Context, floatingIP *FloatingIP, opts FloatingIPUpdateOpts) (*FloatingIP, *Response, error) {
+	const opPath = "/floating_ips/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, floatingIP.ID)
+
 	reqBody := schema.FloatingIPUpdateRequest{
 		Description: opts.Description,
 		Name:        opts.Name,
@@ -300,68 +269,48 @@ func (c *FloatingIPClient) Update(ctx context.Context, floatingIP *FloatingIP, o
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/floating_ips/%d", floatingIP.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.FloatingIPUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.FloatingIPUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return FloatingIPFromSchema(respBody.FloatingIP), resp, nil
 }
 
 // Assign assigns a Floating IP to a server.
 func (c *FloatingIPClient) Assign(ctx context.Context, floatingIP *FloatingIP, server *Server) (*Action, *Response, error) {
+	const opPath = "/floating_ips/%d/actions/assign"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, floatingIP.ID)
+
 	reqBody := schema.FloatingIPActionAssignRequest{
 		Server: server.ID,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/floating_ips/%d/actions/assign", floatingIP.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody schema.FloatingIPActionAssignResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FloatingIPActionAssignResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Unassign unassigns a Floating IP from the currently assigned server.
 func (c *FloatingIPClient) Unassign(ctx context.Context, floatingIP *FloatingIP) (*Action, *Response, error) {
-	var reqBody schema.FloatingIPActionUnassignRequest
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/floating_ips/%d/actions/unassign"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/floating_ips/%d/actions/unassign", floatingIP.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, floatingIP.ID)
+
+	reqBody := schema.FloatingIPActionUnassignRequest{}
 
-	var respBody schema.FloatingIPActionUnassignResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FloatingIPActionUnassignResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -382,24 +331,19 @@ type FloatingIPChangeProtectionOpts struct {
 
 // ChangeProtection changes the resource protection level of a Floating IP.
 func (c *FloatingIPClient) ChangeProtection(ctx context.Context, floatingIP *FloatingIP, opts FloatingIPChangeProtectionOpts) (*Action, *Response, error) {
+	const opPath = "/floating_ips/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, floatingIP.ID)
+
 	reqBody := schema.FloatingIPActionChangeProtectionRequest{
 		Delete: opts.Delete,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/floating_ips/%d/actions/change_protection", floatingIP.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.FloatingIPActionChangeProtectionResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.FloatingIPActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go
index 2c6745c2e85e49d994a450cf6ca44236b902cd37..6b1d6fde0156716f3a41adaf0e5b87a67ee1f138 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go
@@ -1,5 +1,34 @@
-// Package hcloud is a library for the Hetzner Cloud API.
+/*
+Package hcloud is a library for the Hetzner Cloud API.
+
+The Hetzner Cloud API reference is available at https://docs.hetzner.cloud.
+
+Make sure to follow our API changelog available at https://docs.hetzner.cloud/changelog
+(or the RRS feed available at https://docs.hetzner.cloud/changelog/feed.rss) to be
+notified about additions, deprecations and removals.
+
+# Retry mechanism
+
+The [Client.Do] method will retry failed requests that match certain criteria. The
+default retry interval is defined by an exponential backoff algorithm truncated to 60s
+with jitter. The default maximal number of retries is 5.
+
+The following rules defines when a request can be retried:
+
+When the [http.Client] returned a network timeout error.
+
+When the API returned an HTTP error, with the status code:
+  - [http.StatusBadGateway]
+  - [http.StatusGatewayTimeout]
+
+When the API returned an application error, with the code:
+  - [ErrorCodeConflict]
+  - [ErrorCodeRateLimitExceeded]
+
+Changes to the retry policy might occur between releases, and will not be considered
+breaking changes.
+*/
 package hcloud
 
 // Version is the library's version following Semantic Versioning.
-const Version = "2.8.0" // x-release-please-version
+const Version = "2.21.1" // x-releaser-pleaser-version
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go
index 1da240170ec4b2544133bab5cbe94a82f6c59857..16454da1cce7a7216e5db2c732d7feead03fc887 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go
@@ -1,14 +1,13 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
 	"fmt"
 	"net/url"
 	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -83,34 +82,29 @@ type ImageClient struct {
 
 // GetByID retrieves an image by its ID. If the image does not exist, nil is returned.
 func (c *ImageClient) GetByID(ctx context.Context, id int64) (*Image, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/images/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/images/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.ImageGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ImageGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return ImageFromSchema(body.Image), resp, nil
+
+	return ImageFromSchema(respBody.Image), resp, nil
 }
 
 // GetByName retrieves an image by its name. If the image does not exist, nil is returned.
 //
 // Deprecated: Use [ImageClient.GetByNameAndArchitecture] instead.
 func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	images, response, err := c.List(ctx, ImageListOpts{Name: name})
-	if len(images) == 0 {
-		return nil, response, err
-	}
-	return images[0], response, err
+	return firstByName(name, func() ([]*Image, *Response, error) {
+		return c.List(ctx, ImageListOpts{Name: name})
+	})
 }
 
 // GetByNameAndArchitecture retrieves an image by its name and architecture. If the image does not exist,
@@ -118,14 +112,9 @@ func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Resp
 // In contrast to [ImageClient.Get], this method also returns deprecated images. Depending on your needs you should
 // check for this in your calling method.
 func (c *ImageClient) GetByNameAndArchitecture(ctx context.Context, name string, architecture Architecture) (*Image, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	images, response, err := c.List(ctx, ImageListOpts{Name: name, Architecture: []Architecture{architecture}, IncludeDeprecated: true})
-	if len(images) == 0 {
-		return nil, response, err
-	}
-	return images[0], response, err
+	return firstByName(name, func() ([]*Image, *Response, error) {
+		return c.List(ctx, ImageListOpts{Name: name, Architecture: []Architecture{architecture}, IncludeDeprecated: true})
+	})
 }
 
 // Get retrieves an image by its ID if the input can be parsed as an integer, otherwise it
@@ -133,10 +122,7 @@ func (c *ImageClient) GetByNameAndArchitecture(ctx context.Context, name string,
 //
 // Deprecated: Use [ImageClient.GetForArchitecture] instead.
 func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // GetForArchitecture retrieves an image by its ID if the input can be parsed as an integer, otherwise it
@@ -145,10 +131,13 @@ func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Respon
 // In contrast to [ImageClient.Get], this method also returns deprecated images. Depending on your needs you should
 // check for this in your calling method.
 func (c *ImageClient) GetForArchitecture(ctx context.Context, idOrName string, architecture Architecture) (*Image, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByNameAndArchitecture(ctx, idOrName, architecture)
+	return getByIDOrName(ctx,
+		c.GetByID,
+		func(ctx context.Context, name string) (*Image, *Response, error) {
+			return c.GetByNameAndArchitecture(ctx, name, architecture)
+		},
+		idOrName,
+	)
 }
 
 // ImageListOpts specifies options for listing images.
@@ -194,22 +183,17 @@ func (l ImageListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *ImageClient) List(ctx context.Context, opts ImageListOpts) ([]*Image, *Response, error) {
-	path := "/images?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/images?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.ImageListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ImageListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	images := make([]*Image, 0, len(body.Images))
-	for _, i := range body.Images {
-		images = append(images, ImageFromSchema(i))
+		return nil, resp, err
 	}
-	return images, resp, nil
+
+	return allFromSchemaFunc(respBody.Images, ImageFromSchema), resp, nil
 }
 
 // All returns all images.
@@ -219,31 +203,20 @@ func (c *ImageClient) All(ctx context.Context) ([]*Image, error) {
 
 // AllWithOpts returns all images for the given options.
 func (c *ImageClient) AllWithOpts(ctx context.Context, opts ImageListOpts) ([]*Image, error) {
-	allImages := []*Image{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Image, *Response, error) {
 		opts.Page = page
-		images, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allImages = append(allImages, images...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allImages, nil
 }
 
 // Delete deletes an image.
 func (c *ImageClient) Delete(ctx context.Context, image *Image) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/images/%d", image.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/images/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, image.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // ImageUpdateOpts specifies options for updating an image.
@@ -255,6 +228,11 @@ type ImageUpdateOpts struct {
 
 // Update updates an image.
 func (c *ImageClient) Update(ctx context.Context, image *Image, opts ImageUpdateOpts) (*Image, *Response, error) {
+	const opPath = "/images/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, image.ID)
+
 	reqBody := schema.ImageUpdateRequest{
 		Description: opts.Description,
 	}
@@ -264,22 +242,12 @@ func (c *ImageClient) Update(ctx context.Context, image *Image, opts ImageUpdate
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/images/%d", image.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ImageUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.ImageUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ImageFromSchema(respBody.Image), resp, nil
 }
 
@@ -290,24 +258,19 @@ type ImageChangeProtectionOpts struct {
 
 // ChangeProtection changes the resource protection level of an image.
 func (c *ImageClient) ChangeProtection(ctx context.Context, image *Image, opts ImageChangeProtectionOpts) (*Action, *Response, error) {
+	const opPath = "/images/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, image.ID)
+
 	reqBody := schema.ImageActionChangeProtectionRequest{
 		Delete: opts.Delete,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/images/%d/actions/change_protection", image.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.ImageActionChangeProtectionResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ImageActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go
index e52bd606a13dd1c0f18adf5a0f575ccf26586033..da4c0af0f158a7995c580eeec45c9be4d1eb1a43 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go
@@ -1,6 +1,7 @@
 package instrumentation
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"regexp"
@@ -9,6 +10,8 @@ import (
 
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
+
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 )
 
 type Instrumenter struct {
@@ -22,7 +25,12 @@ func New(subsystemIdentifier string, instrumentationRegistry prometheus.Register
 }
 
 // InstrumentedRoundTripper returns an instrumented round tripper.
-func (i *Instrumenter) InstrumentedRoundTripper() http.RoundTripper {
+func (i *Instrumenter) InstrumentedRoundTripper(transport http.RoundTripper) http.RoundTripper {
+	// By default, http client would use DefaultTransport on nil, but we internally are relying on it being configured
+	if transport == nil {
+		transport = http.DefaultTransport
+	}
+
 	inFlightRequestsGauge := registerOrReuse(
 		i.instrumentationRegistry,
 		prometheus.NewGauge(prometheus.GaugeOpts{
@@ -57,7 +65,7 @@ func (i *Instrumenter) InstrumentedRoundTripper() http.RoundTripper {
 	return promhttp.InstrumentRoundTripperInFlight(inFlightRequestsGauge,
 		promhttp.InstrumentRoundTripperDuration(requestLatencyHistogram,
 			i.instrumentRoundTripperEndpoint(requestsPerEndpointCounter,
-				http.DefaultTransport,
+				transport,
 			),
 		),
 	)
@@ -73,8 +81,17 @@ func (i *Instrumenter) instrumentRoundTripperEndpoint(counter *prometheus.Counte
 	return func(r *http.Request) (*http.Response, error) {
 		resp, err := next.RoundTrip(r)
 		if err == nil {
-			statusCode := strconv.Itoa(resp.StatusCode)
-			counter.WithLabelValues(statusCode, strings.ToLower(resp.Request.Method), preparePathForLabel(resp.Request.URL.Path)).Inc()
+			apiEndpoint := ctxutil.OpPath(r.Context())
+			// If the request does not set the operation path, we must construct it. Happens e.g. for
+			// user crafted requests.
+			if apiEndpoint == "" {
+				apiEndpoint = preparePathForLabel(resp.Request.URL.Path)
+			}
+			counter.WithLabelValues(
+				strconv.Itoa(resp.StatusCode),
+				strings.ToLower(resp.Request.Method),
+				apiEndpoint,
+			).Inc()
 		}
 
 		return resp, err
@@ -87,9 +104,10 @@ func (i *Instrumenter) instrumentRoundTripperEndpoint(counter *prometheus.Counte
 func registerOrReuse[C prometheus.Collector](registry prometheus.Registerer, collector C) C {
 	err := registry.Register(collector)
 	if err != nil {
+		var arErr prometheus.AlreadyRegisteredError
 		// If we get a AlreadyRegisteredError we can return the existing collector
-		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
-			if existingCollector, ok := are.ExistingCollector.(C); ok {
+		if errors.As(err, &arErr) {
+			if existingCollector, ok := arErr.ExistingCollector.(C); ok {
 				collector = existingCollector
 			} else {
 				panic("received incompatible existing collector")
@@ -102,16 +120,16 @@ func registerOrReuse[C prometheus.Collector](registry prometheus.Registerer, col
 	return collector
 }
 
+var pathLabelRegexp = regexp.MustCompile("[^a-z/_]+")
+
 func preparePathForLabel(path string) string {
 	path = strings.ToLower(path)
 
-	// replace all numbers and chars that are not a-z, / or _
-	reg := regexp.MustCompile("[^a-z/_]+")
-	path = reg.ReplaceAllString(path, "")
+	// replace the /v1/ that indicated the API version
+	path, _ = strings.CutPrefix(path, "/v1")
 
-	// replace all artifacts of number replacement (//)
-	path = strings.ReplaceAll(path, "//", "/")
+	// replace all numbers and chars that are not a-z, / or _
+	path = pathLabelRegexp.ReplaceAllString(path, "-")
 
-	// replace the /v/ that indicated the API version
-	return strings.Replace(path, "/v/", "/", 1)
+	return path
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go
index 896a3ecdbb9137a902bc5e2de2b8c1bd7e6589d7..7aa8ab3a43b105a8d510913edae5a96503b7c33a 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go
@@ -4,9 +4,9 @@ import (
 	"context"
 	"fmt"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -40,40 +40,32 @@ type ISOClient struct {
 
 // GetByID retrieves an ISO by its ID.
 func (c *ISOClient) GetByID(ctx context.Context, id int64) (*ISO, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/isos/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/isos/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.ISOGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ISOGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
 		return nil, resp, err
 	}
-	return ISOFromSchema(body.ISO), resp, nil
+
+	return ISOFromSchema(respBody.ISO), resp, nil
 }
 
 // GetByName retrieves an ISO by its name.
 func (c *ISOClient) GetByName(ctx context.Context, name string) (*ISO, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	isos, response, err := c.List(ctx, ISOListOpts{Name: name})
-	if len(isos) == 0 {
-		return nil, response, err
-	}
-	return isos[0], response, err
+	return firstByName(name, func() ([]*ISO, *Response, error) {
+		return c.List(ctx, ISOListOpts{Name: name})
+	})
 }
 
 // Get retrieves an ISO by its ID if the input can be parsed as an integer, otherwise it retrieves an ISO by its name.
 func (c *ISOClient) Get(ctx context.Context, idOrName string) (*ISO, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // ISOListOpts specifies options for listing isos.
@@ -115,22 +107,17 @@ func (l ISOListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *ISOClient) List(ctx context.Context, opts ISOListOpts) ([]*ISO, *Response, error) {
-	path := "/isos?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/isos?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.ISOListResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.ISOListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	isos := make([]*ISO, 0, len(body.ISOs))
-	for _, i := range body.ISOs {
-		isos = append(isos, ISOFromSchema(i))
+		return nil, resp, err
 	}
-	return isos, resp, nil
+
+	return allFromSchemaFunc(respBody.ISOs, ISOFromSchema), resp, nil
 }
 
 // All returns all ISOs.
@@ -140,20 +127,8 @@ func (c *ISOClient) All(ctx context.Context) ([]*ISO, error) {
 
 // AllWithOpts returns all ISOs for the given options.
 func (c *ISOClient) AllWithOpts(ctx context.Context, opts ISOListOpts) ([]*ISO, error) {
-	allISOs := []*ISO{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*ISO, *Response, error) {
 		opts.Page = page
-		isos, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allISOs = append(allISOs, isos...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allISOs, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go
index 471eb947ad9c653e69b0836e7a4e5c6ef5a353fd..bcfbb230712aa75df51b43ff59e44409687205d7 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go
@@ -1,16 +1,14 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
 	"fmt"
 	"net"
-	"net/http"
 	"net/url"
 	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -200,26 +198,21 @@ type LoadBalancerProtection struct {
 // changeDNSPtr changes or resets the reverse DNS pointer for an IP address.
 // Pass a nil ptr to reset the reverse DNS pointer to its default value.
 func (lb *LoadBalancer) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/change_dns_ptr"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, lb.ID)
+
 	reqBody := schema.LoadBalancerActionChangeDNSPtrRequest{
 		IP:     ip.String(),
 		DNSPtr: ptr,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/load_balancers/%d/actions/change_dns_ptr", lb.ID)
-	req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.LoadBalancerActionChangeDNSPtrResponse{}
-	resp, err := client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -243,41 +236,33 @@ type LoadBalancerClient struct {
 
 // GetByID retrieves a Load Balancer by its ID. If the Load Balancer does not exist, nil is returned.
 func (c *LoadBalancerClient) GetByID(ctx context.Context, id int64) (*LoadBalancer, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/load_balancers/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancers/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.LoadBalancerGetResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, id)
+
+	respBody, resp, err := getRequest[schema.LoadBalancerGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return LoadBalancerFromSchema(body.LoadBalancer), resp, nil
+
+	return LoadBalancerFromSchema(respBody.LoadBalancer), resp, nil
 }
 
 // GetByName retrieves a Load Balancer by its name. If the Load Balancer does not exist, nil is returned.
 func (c *LoadBalancerClient) GetByName(ctx context.Context, name string) (*LoadBalancer, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	LoadBalancer, response, err := c.List(ctx, LoadBalancerListOpts{Name: name})
-	if len(LoadBalancer) == 0 {
-		return nil, response, err
-	}
-	return LoadBalancer[0], response, err
+	return firstByName(name, func() ([]*LoadBalancer, *Response, error) {
+		return c.List(ctx, LoadBalancerListOpts{Name: name})
+	})
 }
 
 // Get retrieves a Load Balancer by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a Load Balancer by its name. If the Load Balancer does not exist, nil is returned.
 func (c *LoadBalancerClient) Get(ctx context.Context, idOrName string) (*LoadBalancer, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // LoadBalancerListOpts specifies options for listing Load Balancers.
@@ -303,22 +288,17 @@ func (l LoadBalancerListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *LoadBalancerClient) List(ctx context.Context, opts LoadBalancerListOpts) ([]*LoadBalancer, *Response, error) {
-	path := "/load_balancers?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancers?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.LoadBalancerListResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.LoadBalancerListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	LoadBalancers := make([]*LoadBalancer, 0, len(body.LoadBalancers))
-	for _, s := range body.LoadBalancers {
-		LoadBalancers = append(LoadBalancers, LoadBalancerFromSchema(s))
+		return nil, resp, err
 	}
-	return LoadBalancers, resp, nil
+
+	return allFromSchemaFunc(respBody.LoadBalancers, LoadBalancerFromSchema), resp, nil
 }
 
 // All returns all Load Balancers.
@@ -328,22 +308,10 @@ func (c *LoadBalancerClient) All(ctx context.Context) ([]*LoadBalancer, error) {
 
 // AllWithOpts returns all Load Balancers for the given options.
 func (c *LoadBalancerClient) AllWithOpts(ctx context.Context, opts LoadBalancerListOpts) ([]*LoadBalancer, error) {
-	allLoadBalancers := []*LoadBalancer{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*LoadBalancer, *Response, error) {
 		opts.Page = page
-		LoadBalancers, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allLoadBalancers = append(allLoadBalancers, LoadBalancers...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allLoadBalancers, nil
 }
 
 // LoadBalancerUpdateOpts specifies options for updating a Load Balancer.
@@ -354,6 +322,11 @@ type LoadBalancerUpdateOpts struct {
 
 // Update updates a Load Balancer.
 func (c *LoadBalancerClient) Update(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerUpdateOpts) (*LoadBalancer, *Response, error) {
+	const opPath = "/load_balancers/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := schema.LoadBalancerUpdateRequest{}
 	if opts.Name != "" {
 		reqBody.Name = &opts.Name
@@ -361,22 +334,12 @@ func (c *LoadBalancerClient) Update(ctx context.Context, loadBalancer *LoadBalan
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/load_balancers/%d", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.LoadBalancerUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.LoadBalancerUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return LoadBalancerFromSchema(respBody.LoadBalancer), resp, nil
 }
 
@@ -472,73 +435,61 @@ type LoadBalancerCreateResult struct {
 
 // Create creates a new Load Balancer.
 func (c *LoadBalancerClient) Create(ctx context.Context, opts LoadBalancerCreateOpts) (LoadBalancerCreateResult, *Response, error) {
+	const opPath = "/load_balancers"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := LoadBalancerCreateResult{}
+
+	reqPath := opPath
+
 	reqBody := loadBalancerCreateOptsToSchema(opts)
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return LoadBalancerCreateResult{}, nil, err
-	}
-	req, err := c.client.NewRequest(ctx, "POST", "/load_balancers", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return LoadBalancerCreateResult{}, nil, err
-	}
 
-	respBody := schema.LoadBalancerCreateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return LoadBalancerCreateResult{}, resp, err
+		return result, resp, err
 	}
-	return LoadBalancerCreateResult{
-		LoadBalancer: LoadBalancerFromSchema(respBody.LoadBalancer),
-		Action:       ActionFromSchema(respBody.Action),
-	}, resp, nil
+
+	result.LoadBalancer = LoadBalancerFromSchema(respBody.LoadBalancer)
+	result.Action = ActionFromSchema(respBody.Action)
+
+	return result, resp, nil
 }
 
 // Delete deletes a Load Balancer.
 func (c *LoadBalancerClient) Delete(ctx context.Context, loadBalancer *LoadBalancer) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/load_balancers/%d", loadBalancer.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/load_balancers/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 func (c *LoadBalancerClient) addTarget(ctx context.Context, loadBalancer *LoadBalancer, reqBody schema.LoadBalancerActionAddTargetRequest) (*Action, *Response, error) {
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancers/%d/actions/add_target"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/load_balancers/%d/actions/add_target", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
 
-	var respBody schema.LoadBalancerActionAddTargetResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionAddTargetResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 func (c *LoadBalancerClient) removeTarget(ctx context.Context, loadBalancer *LoadBalancer, reqBody schema.LoadBalancerActionRemoveTargetRequest) (*Action, *Response, error) {
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancers/%d/actions/remove_target"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/load_balancers/%d/actions/remove_target", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
 
-	var respBody schema.LoadBalancerActionRemoveTargetResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionRemoveTargetResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -671,23 +622,18 @@ type LoadBalancerAddServiceOptsHealthCheckHTTP struct {
 
 // AddService adds a service to a Load Balancer.
 func (c *LoadBalancerClient) AddService(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerAddServiceOpts) (*Action, *Response, error) {
-	reqBody := loadBalancerAddServiceOptsToSchema(opts)
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancers/%d/actions/add_service"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/load_balancers/%d/actions/add_service", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
 
-	var respBody schema.LoadBalancerActionAddServiceResponse
-	resp, err := c.client.Do(req, &respBody)
+	reqBody := loadBalancerAddServiceOptsToSchema(opts)
+
+	respBody, resp, err := postRequest[schema.LoadBalancerActionAddServiceResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -732,48 +678,38 @@ type LoadBalancerUpdateServiceOptsHealthCheckHTTP struct {
 
 // UpdateService updates a Load Balancer service.
 func (c *LoadBalancerClient) UpdateService(ctx context.Context, loadBalancer *LoadBalancer, listenPort int, opts LoadBalancerUpdateServiceOpts) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/update_service"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := loadBalancerUpdateServiceOptsToSchema(opts)
 	reqBody.ListenPort = listenPort
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/load_balancers/%d/actions/update_service", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	var respBody schema.LoadBalancerActionUpdateServiceResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionUpdateServiceResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // DeleteService deletes a Load Balancer service.
 func (c *LoadBalancerClient) DeleteService(ctx context.Context, loadBalancer *LoadBalancer, listenPort int) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/delete_service"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := schema.LoadBalancerDeleteServiceRequest{
 		ListenPort: listenPort,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/load_balancers/%d/actions/delete_service", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody schema.LoadBalancerDeleteServiceResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerDeleteServiceResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -784,26 +720,21 @@ type LoadBalancerChangeProtectionOpts struct {
 
 // ChangeProtection changes the resource protection level of a Load Balancer.
 func (c *LoadBalancerClient) ChangeProtection(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeProtectionOpts) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := schema.LoadBalancerActionChangeProtectionRequest{
 		Delete: opts.Delete,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/load_balancers/%d/actions/change_protection", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.LoadBalancerActionChangeProtectionResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // LoadBalancerChangeAlgorithmOpts specifies options for changing the algorithm of a Load Balancer.
@@ -813,26 +744,21 @@ type LoadBalancerChangeAlgorithmOpts struct {
 
 // ChangeAlgorithm changes the algorithm of a Load Balancer.
 func (c *LoadBalancerClient) ChangeAlgorithm(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeAlgorithmOpts) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/change_algorithm"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := schema.LoadBalancerActionChangeAlgorithmRequest{
 		Type: string(opts.Type),
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/load_balancers/%d/actions/change_algorithm", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.LoadBalancerActionChangeAlgorithmResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionChangeAlgorithmResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // LoadBalancerAttachToNetworkOpts specifies options for attaching a Load Balancer to a network.
@@ -843,29 +769,24 @@ type LoadBalancerAttachToNetworkOpts struct {
 
 // AttachToNetwork attaches a Load Balancer to a network.
 func (c *LoadBalancerClient) AttachToNetwork(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerAttachToNetworkOpts) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/attach_to_network"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := schema.LoadBalancerActionAttachToNetworkRequest{
 		Network: opts.Network.ID,
 	}
 	if opts.IP != nil {
 		reqBody.IP = Ptr(opts.IP.String())
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/load_balancers/%d/actions/attach_to_network", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.LoadBalancerActionAttachToNetworkResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionAttachToNetworkResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // LoadBalancerDetachFromNetworkOpts specifies options for detaching a Load Balancer from a network.
@@ -875,56 +796,51 @@ type LoadBalancerDetachFromNetworkOpts struct {
 
 // DetachFromNetwork detaches a Load Balancer from a network.
 func (c *LoadBalancerClient) DetachFromNetwork(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerDetachFromNetworkOpts) (*Action, *Response, error) {
+	const opPath = "/load_balancers/%d/actions/detach_from_network"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
 	reqBody := schema.LoadBalancerActionDetachFromNetworkRequest{
 		Network: opts.Network.ID,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/load_balancers/%d/actions/detach_from_network", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.LoadBalancerActionDetachFromNetworkResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionDetachFromNetworkResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // EnablePublicInterface enables the Load Balancer's public network interface.
 func (c *LoadBalancerClient) EnablePublicInterface(ctx context.Context, loadBalancer *LoadBalancer) (*Action, *Response, error) {
-	path := fmt.Sprintf("/load_balancers/%d/actions/enable_public_interface", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
-	respBody := schema.LoadBalancerActionEnablePublicInterfaceResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	const opPath = "/load_balancers/%d/actions/enable_public_interface"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
+	respBody, resp, err := postRequest[schema.LoadBalancerActionEnablePublicInterfaceResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // DisablePublicInterface disables the Load Balancer's public network interface.
 func (c *LoadBalancerClient) DisablePublicInterface(ctx context.Context, loadBalancer *LoadBalancer) (*Action, *Response, error) {
-	path := fmt.Sprintf("/load_balancers/%d/actions/disable_public_interface", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
-	respBody := schema.LoadBalancerActionDisablePublicInterfaceResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	const opPath = "/load_balancers/%d/actions/disable_public_interface"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
+	respBody, resp, err := postRequest[schema.LoadBalancerActionDisablePublicInterfaceResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // LoadBalancerChangeTypeOpts specifies options for changing a Load Balancer's type.
@@ -934,28 +850,21 @@ type LoadBalancerChangeTypeOpts struct {
 
 // ChangeType changes a Load Balancer's type.
 func (c *LoadBalancerClient) ChangeType(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeTypeOpts) (*Action, *Response, error) {
-	reqBody := schema.LoadBalancerActionChangeTypeRequest{}
-	if opts.LoadBalancerType.ID != 0 {
-		reqBody.LoadBalancerType = opts.LoadBalancerType.ID
-	} else {
-		reqBody.LoadBalancerType = opts.LoadBalancerType.Name
-	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancers/%d/actions/change_type"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/load_balancers/%d/actions/change_type", loadBalancer.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID)
+
+	reqBody := schema.LoadBalancerActionChangeTypeRequest{}
+	if opts.LoadBalancerType.ID != 0 || opts.LoadBalancerType.Name != "" {
+		reqBody.LoadBalancerType = schema.IDOrName{ID: opts.LoadBalancerType.ID, Name: opts.LoadBalancerType.Name}
 	}
 
-	respBody := schema.LoadBalancerActionChangeTypeResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.LoadBalancerActionChangeTypeResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -980,32 +889,34 @@ type LoadBalancerGetMetricsOpts struct {
 	Step  int
 }
 
-func (o *LoadBalancerGetMetricsOpts) addQueryParams(req *http.Request) error {
-	query := req.URL.Query()
-
+func (o LoadBalancerGetMetricsOpts) Validate() error {
 	if len(o.Types) == 0 {
-		return fmt.Errorf("no metric types specified")
+		return missingField(o, "Types")
+	}
+	if o.Start.IsZero() {
+		return missingField(o, "Start")
+	}
+	if o.End.IsZero() {
+		return missingField(o, "End")
 	}
+	return nil
+}
+
+func (o LoadBalancerGetMetricsOpts) values() url.Values {
+	query := url.Values{}
+
 	for _, typ := range o.Types {
 		query.Add("type", string(typ))
 	}
 
-	if o.Start.IsZero() {
-		return fmt.Errorf("no start time specified")
-	}
 	query.Add("start", o.Start.Format(time.RFC3339))
-
-	if o.End.IsZero() {
-		return fmt.Errorf("no end time specified")
-	}
 	query.Add("end", o.End.Format(time.RFC3339))
 
 	if o.Step > 0 {
 		query.Add("step", strconv.Itoa(o.Step))
 	}
-	req.URL.RawQuery = query.Encode()
 
-	return nil
+	return query
 }
 
 // LoadBalancerMetrics contains the metrics requested for a Load Balancer.
@@ -1024,31 +935,32 @@ type LoadBalancerMetricsValue struct {
 
 // GetMetrics obtains metrics for a Load Balancer.
 func (c *LoadBalancerClient) GetMetrics(
-	ctx context.Context, lb *LoadBalancer, opts LoadBalancerGetMetricsOpts,
+	ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerGetMetricsOpts,
 ) (*LoadBalancerMetrics, *Response, error) {
-	var respBody schema.LoadBalancerGetMetricsResponse
+	const opPath = "/load_balancers/%d/metrics?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	if lb == nil {
-		return nil, nil, fmt.Errorf("illegal argument: load balancer is nil")
+	if loadBalancer == nil {
+		return nil, nil, missingArgument("loadBalancer", loadBalancer)
 	}
 
-	path := fmt.Sprintf("/load_balancers/%d/metrics", lb.ID)
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, fmt.Errorf("new request: %v", err)
-	}
-	if err := opts.addQueryParams(req); err != nil {
-		return nil, nil, fmt.Errorf("add query params: %v", err)
+	if err := opts.Validate(); err != nil {
+		return nil, nil, err
 	}
-	resp, err := c.client.Do(req, &respBody)
+
+	reqPath := fmt.Sprintf(opPath, loadBalancer.ID, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.LoadBalancerGetMetricsResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, fmt.Errorf("get metrics: %v", err)
+		return nil, resp, err
 	}
-	ms, err := loadBalancerMetricsFromSchema(&respBody)
+
+	metrics, err := loadBalancerMetricsFromSchema(&respBody)
 	if err != nil {
-		return nil, nil, fmt.Errorf("convert response body: %v", err)
+		return nil, nil, fmt.Errorf("convert response body: %w", err)
 	}
-	return ms, resp, nil
+
+	return metrics, resp, nil
 }
 
 // ChangeDNSPtr changes or resets the reverse DNS pointer for a Load Balancer.
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go
index 00c852b5ecc3e0b9ec3993feb9901401b3d5c9c4..58ddbfa8f7dee5e68785b30f9c5b208c9834cc78 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"strconv"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -29,32 +30,27 @@ type LoadBalancerTypeClient struct {
 
 // GetByID retrieves a Load Balancer type by its ID. If the Load Balancer type does not exist, nil is returned.
 func (c *LoadBalancerTypeClient) GetByID(ctx context.Context, id int64) (*LoadBalancerType, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/load_balancer_types/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancer_types/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.LoadBalancerTypeGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.LoadBalancerTypeGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return LoadBalancerTypeFromSchema(body.LoadBalancerType), resp, nil
+
+	return LoadBalancerTypeFromSchema(respBody.LoadBalancerType), resp, nil
 }
 
 // GetByName retrieves a Load Balancer type by its name. If the Load Balancer type does not exist, nil is returned.
 func (c *LoadBalancerTypeClient) GetByName(ctx context.Context, name string) (*LoadBalancerType, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	LoadBalancerTypes, response, err := c.List(ctx, LoadBalancerTypeListOpts{Name: name})
-	if len(LoadBalancerTypes) == 0 {
-		return nil, response, err
-	}
-	return LoadBalancerTypes[0], response, err
+	return firstByName(name, func() ([]*LoadBalancerType, *Response, error) {
+		return c.List(ctx, LoadBalancerTypeListOpts{Name: name})
+	})
 }
 
 // Get retrieves a Load Balancer type by its ID if the input can be parsed as an integer, otherwise it
@@ -89,22 +85,17 @@ func (l LoadBalancerTypeListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *LoadBalancerTypeClient) List(ctx context.Context, opts LoadBalancerTypeListOpts) ([]*LoadBalancerType, *Response, error) {
-	path := "/load_balancer_types?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/load_balancer_types?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.LoadBalancerTypeListResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.LoadBalancerTypeListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	LoadBalancerTypes := make([]*LoadBalancerType, 0, len(body.LoadBalancerTypes))
-	for _, s := range body.LoadBalancerTypes {
-		LoadBalancerTypes = append(LoadBalancerTypes, LoadBalancerTypeFromSchema(s))
+		return nil, resp, err
 	}
-	return LoadBalancerTypes, resp, nil
+
+	return allFromSchemaFunc(respBody.LoadBalancerTypes, LoadBalancerTypeFromSchema), resp, nil
 }
 
 // All returns all Load Balancer types.
@@ -114,20 +105,8 @@ func (c *LoadBalancerTypeClient) All(ctx context.Context) ([]*LoadBalancerType,
 
 // AllWithOpts returns all Load Balancer types for the given options.
 func (c *LoadBalancerTypeClient) AllWithOpts(ctx context.Context, opts LoadBalancerTypeListOpts) ([]*LoadBalancerType, error) {
-	allLoadBalancerTypes := []*LoadBalancerType{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*LoadBalancerType, *Response, error) {
 		opts.Page = page
-		LoadBalancerTypes, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allLoadBalancerTypes = append(allLoadBalancerTypes, LoadBalancerTypes...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allLoadBalancerTypes, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go
index c4d75177503483b8216655dec6472ab2957b3d98..66fd7fe0f4b5bf079ecbc729bab4442a2263f0a7 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"strconv"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -28,32 +29,27 @@ type LocationClient struct {
 
 // GetByID retrieves a location by its ID. If the location does not exist, nil is returned.
 func (c *LocationClient) GetByID(ctx context.Context, id int64) (*Location, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/locations/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/locations/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.LocationGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.LocationGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
 		return nil, resp, err
 	}
-	return LocationFromSchema(body.Location), resp, nil
+
+	return LocationFromSchema(respBody.Location), resp, nil
 }
 
 // GetByName retrieves an location by its name. If the location does not exist, nil is returned.
 func (c *LocationClient) GetByName(ctx context.Context, name string) (*Location, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	locations, response, err := c.List(ctx, LocationListOpts{Name: name})
-	if len(locations) == 0 {
-		return nil, response, err
-	}
-	return locations[0], response, err
+	return firstByName(name, func() ([]*Location, *Response, error) {
+		return c.List(ctx, LocationListOpts{Name: name})
+	})
 }
 
 // Get retrieves a location by its ID if the input can be parsed as an integer, otherwise it
@@ -88,22 +84,17 @@ func (l LocationListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *LocationClient) List(ctx context.Context, opts LocationListOpts) ([]*Location, *Response, error) {
-	path := "/locations?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/locations?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.LocationListResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.LocationListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	locations := make([]*Location, 0, len(body.Locations))
-	for _, i := range body.Locations {
-		locations = append(locations, LocationFromSchema(i))
+		return nil, resp, err
 	}
-	return locations, resp, nil
+
+	return allFromSchemaFunc(respBody.Locations, LocationFromSchema), resp, nil
 }
 
 // All returns all locations.
@@ -113,20 +104,8 @@ func (c *LocationClient) All(ctx context.Context) ([]*Location, error) {
 
 // AllWithOpts returns all locations for the given options.
 func (c *LocationClient) AllWithOpts(ctx context.Context, opts LocationListOpts) ([]*Location, error) {
-	allLocations := []*Location{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Location, *Response, error) {
 		opts.Page = page
-		locations, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allLocations = append(allLocations, locations...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allLocations, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go
index 8aeb772f8514a213432a4a921ede1d5ae45a24c3..837aef7ddd13bde684e2a27d2f9c0a471372b59e 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go
@@ -1,6 +1,8 @@
 package metadata
 
 import (
+	"bytes"
+	"context"
 	"fmt"
 	"io"
 	"net"
@@ -11,6 +13,7 @@ import (
 
 	"github.com/prometheus/client_golang/prometheus"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation"
 )
 
@@ -72,24 +75,33 @@ func NewClient(options ...ClientOption) *Client {
 
 	if client.instrumentationRegistry != nil {
 		i := instrumentation.New("metadata", client.instrumentationRegistry)
-		client.httpClient.Transport = i.InstrumentedRoundTripper()
+		client.httpClient.Transport = i.InstrumentedRoundTripper(client.httpClient.Transport)
 	}
 	return client
 }
 
 // get executes an HTTP request against the API.
 func (c *Client) get(path string) (string, error) {
-	url := c.endpoint + path
-	resp, err := c.httpClient.Get(url)
+	ctx := ctxutil.SetOpPath(context.Background(), path)
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.endpoint+path, http.NoBody)
+	if err != nil {
+		return "", err
+	}
+
+	resp, err := c.httpClient.Do(req)
 	if err != nil {
 		return "", err
 	}
+
 	defer resp.Body.Close()
 	bodyBytes, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return "", err
 	}
-	body := string(bodyBytes)
+
+	body := string(bytes.TrimSpace(bodyBytes))
+
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 		return body, fmt.Errorf("response status was %d", resp.StatusCode)
 	}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go
index d0452bc93ce447ee9ccc70c4dd1b91a23dd19fc9..8760f54780011f4c740fdd26ecc207bd07f4c2d4 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go
@@ -1,16 +1,13 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -19,9 +16,10 @@ type NetworkZone string
 
 // List of available Network Zones.
 const (
-	NetworkZoneEUCentral NetworkZone = "eu-central"
-	NetworkZoneUSEast    NetworkZone = "us-east"
-	NetworkZoneUSWest    NetworkZone = "us-west"
+	NetworkZoneEUCentral   NetworkZone = "eu-central"
+	NetworkZoneUSEast      NetworkZone = "us-east"
+	NetworkZoneUSWest      NetworkZone = "us-west"
+	NetworkZoneAPSouthEast NetworkZone = "ap-southeast"
 )
 
 // NetworkSubnetType specifies a type of a subnet.
@@ -29,22 +27,30 @@ type NetworkSubnetType string
 
 // List of available network subnet types.
 const (
-	NetworkSubnetTypeCloud   NetworkSubnetType = "cloud"
-	NetworkSubnetTypeServer  NetworkSubnetType = "server"
+	// Used to connect cloud servers and load balancers.
+	NetworkSubnetTypeCloud NetworkSubnetType = "cloud"
+	// Used to connect cloud servers and load balancers.
+	//
+	// Deprecated: Use [NetworkSubnetTypeCloud] instead.
+	NetworkSubnetTypeServer NetworkSubnetType = "server"
+	// Used to connect cloud servers and load balancers with dedicated servers.
+	//
+	// See https://docs.hetzner.com/cloud/networks/connect-dedi-vswitch/
 	NetworkSubnetTypeVSwitch NetworkSubnetType = "vswitch"
 )
 
 // Network represents a network in the Hetzner Cloud.
 type Network struct {
-	ID         int64
-	Name       string
-	Created    time.Time
-	IPRange    *net.IPNet
-	Subnets    []NetworkSubnet
-	Routes     []NetworkRoute
-	Servers    []*Server
-	Protection NetworkProtection
-	Labels     map[string]string
+	ID            int64
+	Name          string
+	Created       time.Time
+	IPRange       *net.IPNet
+	Subnets       []NetworkSubnet
+	Routes        []NetworkRoute
+	Servers       []*Server
+	LoadBalancers []*LoadBalancer
+	Protection    NetworkProtection
+	Labels        map[string]string
 
 	// ExposeRoutesToVSwitch indicates if the routes from this network should be exposed to the vSwitch connection.
 	ExposeRoutesToVSwitch bool
@@ -78,41 +84,33 @@ type NetworkClient struct {
 
 // GetByID retrieves a network by its ID. If the network does not exist, nil is returned.
 func (c *NetworkClient) GetByID(ctx context.Context, id int64) (*Network, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/networks/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/networks/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.NetworkGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.NetworkGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return NetworkFromSchema(body.Network), resp, nil
+
+	return NetworkFromSchema(respBody.Network), resp, nil
 }
 
 // GetByName retrieves a network by its name. If the network does not exist, nil is returned.
 func (c *NetworkClient) GetByName(ctx context.Context, name string) (*Network, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	Networks, response, err := c.List(ctx, NetworkListOpts{Name: name})
-	if len(Networks) == 0 {
-		return nil, response, err
-	}
-	return Networks[0], response, err
+	return firstByName(name, func() ([]*Network, *Response, error) {
+		return c.List(ctx, NetworkListOpts{Name: name})
+	})
 }
 
 // Get retrieves a network by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a network by its name. If the network does not exist, nil is returned.
 func (c *NetworkClient) Get(ctx context.Context, idOrName string) (*Network, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // NetworkListOpts specifies options for listing networks.
@@ -138,22 +136,17 @@ func (l NetworkListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *NetworkClient) List(ctx context.Context, opts NetworkListOpts) ([]*Network, *Response, error) {
-	path := "/networks?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/networks?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.NetworkListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.NetworkListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	Networks := make([]*Network, 0, len(body.Networks))
-	for _, s := range body.Networks {
-		Networks = append(Networks, NetworkFromSchema(s))
+		return nil, resp, err
 	}
-	return Networks, resp, nil
+
+	return allFromSchemaFunc(respBody.Networks, NetworkFromSchema), resp, nil
 }
 
 // All returns all networks.
@@ -163,31 +156,20 @@ func (c *NetworkClient) All(ctx context.Context) ([]*Network, error) {
 
 // AllWithOpts returns all networks for the given options.
 func (c *NetworkClient) AllWithOpts(ctx context.Context, opts NetworkListOpts) ([]*Network, error) {
-	allNetworks := []*Network{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Network, *Response, error) {
 		opts.Page = page
-		Networks, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allNetworks = append(allNetworks, Networks...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allNetworks, nil
 }
 
 // Delete deletes a network.
 func (c *NetworkClient) Delete(ctx context.Context, network *Network) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/networks/%d", network.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/networks/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // NetworkUpdateOpts specifies options for updating a network.
@@ -201,6 +183,11 @@ type NetworkUpdateOpts struct {
 
 // Update updates a network.
 func (c *NetworkClient) Update(ctx context.Context, network *Network, opts NetworkUpdateOpts) (*Network, *Response, error) {
+	const opPath = "/networks/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkUpdateRequest{
 		Name: opts.Name,
 	}
@@ -211,22 +198,11 @@ func (c *NetworkClient) Update(ctx context.Context, network *Network, opts Netwo
 		reqBody.ExposeRoutesToVSwitch = opts.ExposeRoutesToVSwitch
 	}
 
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/networks/%d", network.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.NetworkUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.NetworkUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return NetworkFromSchema(respBody.Network), resp, nil
 }
 
@@ -245,16 +221,21 @@ type NetworkCreateOpts struct {
 // Validate checks if options are valid.
 func (o NetworkCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	if o.IPRange == nil || o.IPRange.String() == "" {
-		return errors.New("missing IP range")
+		return missingField(o, "IPRange")
 	}
 	return nil
 }
 
 // Create creates a new network.
 func (c *NetworkClient) Create(ctx context.Context, opts NetworkCreateOpts) (*Network, *Response, error) {
+	const opPath = "/networks"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
 		return nil, nil, err
 	}
@@ -283,20 +264,12 @@ func (c *NetworkClient) Create(ctx context.Context, opts NetworkCreateOpts) (*Ne
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-	req, err := c.client.NewRequest(ctx, "POST", "/networks", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.NetworkCreateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return NetworkFromSchema(respBody.Network), resp, nil
 }
 
@@ -307,25 +280,20 @@ type NetworkChangeIPRangeOpts struct {
 
 // ChangeIPRange changes the IP range of a network.
 func (c *NetworkClient) ChangeIPRange(ctx context.Context, network *Network, opts NetworkChangeIPRangeOpts) (*Action, *Response, error) {
+	const opPath = "/networks/%d/actions/change_ip_range"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkActionChangeIPRangeRequest{
 		IPRange: opts.IPRange.String(),
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/networks/%d/actions/change_ip_range", network.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.NetworkActionChangeIPRangeResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkActionChangeIPRangeResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -336,6 +304,11 @@ type NetworkAddSubnetOpts struct {
 
 // AddSubnet adds a subnet to a network.
 func (c *NetworkClient) AddSubnet(ctx context.Context, network *Network, opts NetworkAddSubnetOpts) (*Action, *Response, error) {
+	const opPath = "/networks/%d/actions/add_subnet"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkActionAddSubnetRequest{
 		Type:        string(opts.Subnet.Type),
 		NetworkZone: string(opts.Subnet.NetworkZone),
@@ -346,22 +319,12 @@ func (c *NetworkClient) AddSubnet(ctx context.Context, network *Network, opts Ne
 	if opts.Subnet.VSwitchID != 0 {
 		reqBody.VSwitchID = opts.Subnet.VSwitchID
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/networks/%d/actions/add_subnet", network.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.NetworkActionAddSubnetResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkActionAddSubnetResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -372,25 +335,20 @@ type NetworkDeleteSubnetOpts struct {
 
 // DeleteSubnet deletes a subnet from a network.
 func (c *NetworkClient) DeleteSubnet(ctx context.Context, network *Network, opts NetworkDeleteSubnetOpts) (*Action, *Response, error) {
+	const opPath = "/networks/%d/actions/delete_subnet"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkActionDeleteSubnetRequest{
 		IPRange: opts.Subnet.IPRange.String(),
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/networks/%d/actions/delete_subnet", network.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.NetworkActionDeleteSubnetResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkActionDeleteSubnetResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -401,26 +359,21 @@ type NetworkAddRouteOpts struct {
 
 // AddRoute adds a route to a network.
 func (c *NetworkClient) AddRoute(ctx context.Context, network *Network, opts NetworkAddRouteOpts) (*Action, *Response, error) {
+	const opPath = "/networks/%d/actions/add_route"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkActionAddRouteRequest{
 		Destination: opts.Route.Destination.String(),
 		Gateway:     opts.Route.Gateway.String(),
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/networks/%d/actions/add_route", network.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.NetworkActionAddSubnetResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkActionAddRouteResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -431,26 +384,21 @@ type NetworkDeleteRouteOpts struct {
 
 // DeleteRoute deletes a route from a network.
 func (c *NetworkClient) DeleteRoute(ctx context.Context, network *Network, opts NetworkDeleteRouteOpts) (*Action, *Response, error) {
+	const opPath = "/networks/%d/actions/delete_route"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkActionDeleteRouteRequest{
 		Destination: opts.Route.Destination.String(),
 		Gateway:     opts.Route.Gateway.String(),
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/networks/%d/actions/delete_route", network.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.NetworkActionDeleteSubnetResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkActionDeleteRouteResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -461,24 +409,19 @@ type NetworkChangeProtectionOpts struct {
 
 // ChangeProtection changes the resource protection level of a network.
 func (c *NetworkClient) ChangeProtection(ctx context.Context, network *Network, opts NetworkChangeProtectionOpts) (*Action, *Response, error) {
+	const opPath = "/networks/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, network.ID)
+
 	reqBody := schema.NetworkActionChangeProtectionRequest{
 		Delete: opts.Delete,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/networks/%d/actions/change_protection", network.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.NetworkActionChangeProtectionResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.NetworkActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go
index 6907ad4495cbd638de6701bf96800ad7d269bcc4..41224344025e84fb31d6a1b9fca2e75382e7377f 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go
@@ -1,15 +1,12 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -38,41 +35,33 @@ type PlacementGroupClient struct {
 
 // GetByID retrieves a PlacementGroup by its ID. If the PlacementGroup does not exist, nil is returned.
 func (c *PlacementGroupClient) GetByID(ctx context.Context, id int64) (*PlacementGroup, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/placement_groups/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/placement_groups/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.PlacementGroupGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.PlacementGroupGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return PlacementGroupFromSchema(body.PlacementGroup), resp, nil
+
+	return PlacementGroupFromSchema(respBody.PlacementGroup), resp, nil
 }
 
 // GetByName retrieves a PlacementGroup by its name. If the PlacementGroup does not exist, nil is returned.
 func (c *PlacementGroupClient) GetByName(ctx context.Context, name string) (*PlacementGroup, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	placementGroups, response, err := c.List(ctx, PlacementGroupListOpts{Name: name})
-	if len(placementGroups) == 0 {
-		return nil, response, err
-	}
-	return placementGroups[0], response, err
+	return firstByName(name, func() ([]*PlacementGroup, *Response, error) {
+		return c.List(ctx, PlacementGroupListOpts{Name: name})
+	})
 }
 
 // Get retrieves a PlacementGroup by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a PlacementGroup by its name. If the PlacementGroup does not exist, nil is returned.
 func (c *PlacementGroupClient) Get(ctx context.Context, idOrName string) (*PlacementGroup, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // PlacementGroupListOpts specifies options for listing PlacementGroup.
@@ -102,22 +91,17 @@ func (l PlacementGroupListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *PlacementGroupClient) List(ctx context.Context, opts PlacementGroupListOpts) ([]*PlacementGroup, *Response, error) {
-	path := "/placement_groups?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/placement_groups?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.PlacementGroupListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.PlacementGroupListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	placementGroups := make([]*PlacementGroup, 0, len(body.PlacementGroups))
-	for _, g := range body.PlacementGroups {
-		placementGroups = append(placementGroups, PlacementGroupFromSchema(g))
+		return nil, resp, err
 	}
-	return placementGroups, resp, nil
+
+	return allFromSchemaFunc(respBody.PlacementGroups, PlacementGroupFromSchema), resp, nil
 }
 
 // All returns all PlacementGroups.
@@ -133,22 +117,10 @@ func (c *PlacementGroupClient) All(ctx context.Context) ([]*PlacementGroup, erro
 
 // AllWithOpts returns all PlacementGroups for the given options.
 func (c *PlacementGroupClient) AllWithOpts(ctx context.Context, opts PlacementGroupListOpts) ([]*PlacementGroup, error) {
-	allPlacementGroups := []*PlacementGroup{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*PlacementGroup, *Response, error) {
 		opts.Page = page
-		placementGroups, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allPlacementGroups = append(allPlacementGroups, placementGroups...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allPlacementGroups, nil
 }
 
 // PlacementGroupCreateOpts specifies options for creating a new PlacementGroup.
@@ -161,7 +133,7 @@ type PlacementGroupCreateOpts struct {
 // Validate checks if options are valid.
 func (o PlacementGroupCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	return nil
 }
@@ -174,27 +146,25 @@ type PlacementGroupCreateResult struct {
 
 // Create creates a new PlacementGroup.
 func (c *PlacementGroupClient) Create(ctx context.Context, opts PlacementGroupCreateOpts) (PlacementGroupCreateResult, *Response, error) {
+	const opPath = "/placement_groups"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := PlacementGroupCreateResult{}
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
-		return PlacementGroupCreateResult{}, nil, err
+		return result, nil, err
 	}
+
 	reqBody := placementGroupCreateOptsToSchema(opts)
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return PlacementGroupCreateResult{}, nil, err
-	}
-	req, err := c.client.NewRequest(ctx, "POST", "/placement_groups", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return PlacementGroupCreateResult{}, nil, err
-	}
 
-	respBody := schema.PlacementGroupCreateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.PlacementGroupCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return PlacementGroupCreateResult{}, nil, err
-	}
-	result := PlacementGroupCreateResult{
-		PlacementGroup: PlacementGroupFromSchema(respBody.PlacementGroup),
+		return result, resp, err
 	}
+
+	result.PlacementGroup = PlacementGroupFromSchema(respBody.PlacementGroup)
 	if respBody.Action != nil {
 		result.Action = ActionFromSchema(*respBody.Action)
 	}
@@ -210,6 +180,11 @@ type PlacementGroupUpdateOpts struct {
 
 // Update updates a PlacementGroup.
 func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *PlacementGroup, opts PlacementGroupUpdateOpts) (*PlacementGroup, *Response, error) {
+	const opPath = "/placement_groups/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, placementGroup.ID)
+
 	reqBody := schema.PlacementGroupUpdateRequest{}
 	if opts.Name != "" {
 		reqBody.Name = &opts.Name
@@ -217,19 +192,8 @@ func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *Place
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/placement_groups/%d", placementGroup.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.PlacementGroupUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.PlacementGroupUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -239,9 +203,10 @@ func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *Place
 
 // Delete deletes a PlacementGroup.
 func (c *PlacementGroupClient) Delete(ctx context.Context, placementGroup *PlacementGroup) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/placement_groups/%d", placementGroup.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/placement_groups/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, placementGroup.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go
index 9e9c4a464ed0ef971668ece3d02b77d73e108563..11ef71ae592abbd765435f9ebb483f4f01b20b92 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go
@@ -3,15 +3,19 @@ package hcloud
 import (
 	"context"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
 // Pricing specifies pricing information for various resources.
 type Pricing struct {
-	Image             ImagePricing
-	FloatingIP        FloatingIPPricing
-	FloatingIPs       []FloatingIPTypePricing
-	PrimaryIPs        []PrimaryIPPricing
+	Image ImagePricing
+	// Deprecated: [Pricing.FloatingIP] is deprecated, use [Pricing.FloatingIPs] instead.
+	FloatingIP  FloatingIPPricing
+	FloatingIPs []FloatingIPTypePricing
+	PrimaryIPs  []PrimaryIPPricing
+	// Deprecated: [Pricing.Traffic] is deprecated and will report 0 after 2024-08-05.
+	// Use traffic pricing from [Pricing.ServerTypes] or [Pricing.LoadBalancerTypes] instead.
 	Traffic           TrafficPricing
 	ServerBackup      ServerBackupPricing
 	ServerTypes       []ServerTypePricing
@@ -102,6 +106,10 @@ type ServerTypeLocationPricing struct {
 	Location *Location
 	Hourly   Price
 	Monthly  Price
+
+	// IncludedTraffic is the free traffic per month in bytes
+	IncludedTraffic uint64
+	PerTBTraffic    Price
 }
 
 // LoadBalancerTypePricing provides pricing information for a Load Balancer type.
@@ -116,6 +124,10 @@ type LoadBalancerTypeLocationPricing struct {
 	Location *Location
 	Hourly   Price
 	Monthly  Price
+
+	// IncludedTraffic is the free traffic per month in bytes
+	IncludedTraffic uint64
+	PerTBTraffic    Price
 }
 
 // PricingClient is a client for the pricing API.
@@ -125,15 +137,15 @@ type PricingClient struct {
 
 // Get retrieves pricing information.
 func (c *PricingClient) Get(ctx context.Context) (Pricing, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", "/pricing", nil)
-	if err != nil {
-		return Pricing{}, nil, err
-	}
+	const opPath = "/pricing"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := opPath
 
-	var body schema.PricingGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.PricingGetResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return Pricing{}, nil, err
+		return Pricing{}, resp, err
 	}
-	return PricingFromSchema(body.Pricing), resp, nil
+
+	return PricingFromSchema(respBody.Pricing), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go
index 48cc4edde194d97177dd598f00efcffb35b68d72..4ab686e0cdf3a26a961cd7b39a81398a3049c816 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go
@@ -1,15 +1,13 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
 	"fmt"
 	"net"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -46,26 +44,21 @@ type PrimaryIPDNSPTR struct {
 // changeDNSPtr changes or resets the reverse DNS pointer for a IP address.
 // Pass a nil ptr to reset the reverse DNS pointer to its default value.
 func (p *PrimaryIP) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) {
+	const opPath = "/primary_ips/%d/actions/change_dns_ptr"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, p.ID)
+
 	reqBody := schema.PrimaryIPActionChangeDNSPtrRequest{
 		IP:     ip.String(),
 		DNSPtr: ptr,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/primary_ips/%d/actions/change_dns_ptr", p.ID)
-	req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody PrimaryIPChangeDNSPtrResult
-	resp, err := client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -92,13 +85,13 @@ const (
 // PrimaryIPCreateOpts defines the request to
 // create a Primary IP.
 type PrimaryIPCreateOpts struct {
-	AssigneeID   *int64            `json:"assignee_id,omitempty"`
-	AssigneeType string            `json:"assignee_type"`
-	AutoDelete   *bool             `json:"auto_delete,omitempty"`
-	Datacenter   string            `json:"datacenter,omitempty"`
-	Labels       map[string]string `json:"labels,omitempty"`
-	Name         string            `json:"name"`
-	Type         PrimaryIPType     `json:"type"`
+	AssigneeID   *int64
+	AssigneeType string
+	AutoDelete   *bool
+	Datacenter   string
+	Labels       map[string]string
+	Name         string
+	Type         PrimaryIPType
 }
 
 // PrimaryIPCreateResult defines the response
@@ -111,51 +104,42 @@ type PrimaryIPCreateResult struct {
 // PrimaryIPUpdateOpts defines the request to
 // update a Primary IP.
 type PrimaryIPUpdateOpts struct {
-	AutoDelete *bool              `json:"auto_delete,omitempty"`
-	Labels     *map[string]string `json:"labels,omitempty"`
-	Name       string             `json:"name,omitempty"`
+	AutoDelete *bool
+	Labels     *map[string]string
+	Name       string
 }
 
 // PrimaryIPAssignOpts defines the request to
 // assign a Primary IP to an assignee (usually a server).
 type PrimaryIPAssignOpts struct {
 	ID           int64
-	AssigneeID   int64  `json:"assignee_id"`
-	AssigneeType string `json:"assignee_type"`
+	AssigneeID   int64
+	AssigneeType string
 }
 
-// PrimaryIPAssignResult defines the response
-// when assigning a Primary IP to a assignee.
-type PrimaryIPAssignResult struct {
-	Action schema.Action `json:"action"`
-}
+// Deprecated: Please use [schema.PrimaryIPActionAssignResponse] instead.
+type PrimaryIPAssignResult = schema.PrimaryIPActionAssignResponse
 
 // PrimaryIPChangeDNSPtrOpts defines the request to
 // change a DNS PTR entry from a Primary IP.
 type PrimaryIPChangeDNSPtrOpts struct {
 	ID     int64
-	DNSPtr string `json:"dns_ptr"`
-	IP     string `json:"ip"`
+	DNSPtr string
+	IP     string
 }
 
-// PrimaryIPChangeDNSPtrResult defines the response
-// when assigning a Primary IP to a assignee.
-type PrimaryIPChangeDNSPtrResult struct {
-	Action schema.Action `json:"action"`
-}
+// Deprecated: Please use [schema.PrimaryIPChangeDNSPtrResponse] instead.
+type PrimaryIPChangeDNSPtrResult = schema.PrimaryIPActionChangeDNSPtrResponse
 
 // PrimaryIPChangeProtectionOpts defines the request to
 // change protection configuration of a Primary IP.
 type PrimaryIPChangeProtectionOpts struct {
 	ID     int64
-	Delete bool `json:"delete"`
+	Delete bool
 }
 
-// PrimaryIPChangeProtectionResult defines the response
-// when changing a protection of a PrimaryIP.
-type PrimaryIPChangeProtectionResult struct {
-	Action schema.Action `json:"action"`
-}
+// Deprecated: Please use [schema.PrimaryIPActionChangeProtectionResponse] instead.
+type PrimaryIPChangeProtectionResult = schema.PrimaryIPActionChangeProtectionResponse
 
 // PrimaryIPClient is a client for the Primary IP API.
 type PrimaryIPClient struct {
@@ -165,20 +149,20 @@ type PrimaryIPClient struct {
 
 // GetByID retrieves a Primary IP by its ID. If the Primary IP does not exist, nil is returned.
 func (c *PrimaryIPClient) GetByID(ctx context.Context, id int64) (*PrimaryIP, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/primary_ips/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/primary_ips/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.PrimaryIPGetResult
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.PrimaryIPGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return PrimaryIPFromSchema(body.PrimaryIP), resp, nil
+
+	return PrimaryIPFromSchema(respBody.PrimaryIP), resp, nil
 }
 
 // GetByIP retrieves a Primary IP by its IP Address. If the Primary IP does not exist, nil is returned.
@@ -186,32 +170,22 @@ func (c *PrimaryIPClient) GetByIP(ctx context.Context, ip string) (*PrimaryIP, *
 	if ip == "" {
 		return nil, nil, nil
 	}
-	primaryIPs, response, err := c.List(ctx, PrimaryIPListOpts{IP: ip})
-	if len(primaryIPs) == 0 {
-		return nil, response, err
-	}
-	return primaryIPs[0], response, err
+	return firstBy(func() ([]*PrimaryIP, *Response, error) {
+		return c.List(ctx, PrimaryIPListOpts{IP: ip})
+	})
 }
 
 // GetByName retrieves a Primary IP by its name. If the Primary IP does not exist, nil is returned.
 func (c *PrimaryIPClient) GetByName(ctx context.Context, name string) (*PrimaryIP, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	primaryIPs, response, err := c.List(ctx, PrimaryIPListOpts{Name: name})
-	if len(primaryIPs) == 0 {
-		return nil, response, err
-	}
-	return primaryIPs[0], response, err
+	return firstByName(name, func() ([]*PrimaryIP, *Response, error) {
+		return c.List(ctx, PrimaryIPListOpts{Name: name})
+	})
 }
 
 // Get retrieves a Primary IP by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a Primary IP by its name. If the Primary IP does not exist, nil is returned.
 func (c *PrimaryIPClient) Get(ctx context.Context, idOrName string) (*PrimaryIP, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // PrimaryIPListOpts specifies options for listing Primary IPs.
@@ -241,22 +215,17 @@ func (l PrimaryIPListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *PrimaryIPClient) List(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, *Response, error) {
-	path := "/primary_ips?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/primary_ips?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.PrimaryIPListResult
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.PrimaryIPListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	primaryIPs := make([]*PrimaryIP, 0, len(body.PrimaryIPs))
-	for _, s := range body.PrimaryIPs {
-		primaryIPs = append(primaryIPs, PrimaryIPFromSchema(s))
+		return nil, resp, err
 	}
-	return primaryIPs, resp, nil
+
+	return allFromSchemaFunc(respBody.PrimaryIPs, PrimaryIPFromSchema), resp, nil
 }
 
 // All returns all Primary IPs.
@@ -266,157 +235,125 @@ func (c *PrimaryIPClient) All(ctx context.Context) ([]*PrimaryIP, error) {
 
 // AllWithOpts returns all Primary IPs for the given options.
 func (c *PrimaryIPClient) AllWithOpts(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, error) {
-	allPrimaryIPs := []*PrimaryIP{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*PrimaryIP, *Response, error) {
 		opts.Page = page
-		primaryIPs, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allPrimaryIPs = append(allPrimaryIPs, primaryIPs...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allPrimaryIPs, nil
 }
 
 // Create creates a Primary IP.
-func (c *PrimaryIPClient) Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) {
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return &PrimaryIPCreateResult{}, nil, err
-	}
+func (c *PrimaryIPClient) Create(ctx context.Context, opts PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) {
+	const opPath = "/primary_ips"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	req, err := c.client.NewRequest(ctx, "POST", "/primary_ips", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return &PrimaryIPCreateResult{}, nil, err
-	}
+	result := &PrimaryIPCreateResult{}
 
-	var respBody schema.PrimaryIPCreateResponse
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := opPath
+
+	reqBody := SchemaFromPrimaryIPCreateOpts(opts)
+
+	respBody, resp, err := postRequest[schema.PrimaryIPCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return &PrimaryIPCreateResult{}, resp, err
+		return result, resp, err
 	}
-	var action *Action
+
+	result.PrimaryIP = PrimaryIPFromSchema(respBody.PrimaryIP)
 	if respBody.Action != nil {
-		action = ActionFromSchema(*respBody.Action)
+		result.Action = ActionFromSchema(*respBody.Action)
 	}
-	primaryIP := PrimaryIPFromSchema(respBody.PrimaryIP)
-	return &PrimaryIPCreateResult{
-		PrimaryIP: primaryIP,
-		Action:    action,
-	}, resp, nil
+
+	return result, resp, nil
 }
 
 // Delete deletes a Primary IP.
 func (c *PrimaryIPClient) Delete(ctx context.Context, primaryIP *PrimaryIP) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/primary_ips/%d", primaryIP.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/primary_ips/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, primaryIP.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // Update updates a Primary IP.
-func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) {
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, opts PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) {
+	const opPath = "/primary_ips/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/primary_ips/%d", primaryIP.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, primaryIP.ID)
+
+	reqBody := SchemaFromPrimaryIPUpdateOpts(opts)
 
-	var respBody schema.PrimaryIPUpdateResult
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.PrimaryIPUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return PrimaryIPFromSchema(respBody.PrimaryIP), resp, nil
 }
 
 // Assign a Primary IP to a resource.
 func (c *PrimaryIPClient) Assign(ctx context.Context, opts PrimaryIPAssignOpts) (*Action, *Response, error) {
-	reqBodyData, err := json.Marshal(opts)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/primary_ips/%d/actions/assign"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/primary_ips/%d/actions/assign", opts.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, opts.ID)
 
-	var respBody PrimaryIPAssignResult
-	resp, err := c.client.Do(req, &respBody)
+	reqBody := SchemaFromPrimaryIPAssignOpts(opts)
+
+	respBody, resp, err := postRequest[schema.PrimaryIPActionAssignResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Unassign a Primary IP from a resource.
 func (c *PrimaryIPClient) Unassign(ctx context.Context, id int64) (*Action, *Response, error) {
-	path := fmt.Sprintf("/primary_ips/%d/actions/unassign", id)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader([]byte{}))
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/primary_ips/%d/actions/unassign"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var respBody PrimaryIPAssignResult
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionUnassignResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // ChangeDNSPtr Change the reverse DNS from a Primary IP.
 func (c *PrimaryIPClient) ChangeDNSPtr(ctx context.Context, opts PrimaryIPChangeDNSPtrOpts) (*Action, *Response, error) {
-	reqBodyData, err := json.Marshal(opts)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/primary_ips/%d/actions/change_dns_ptr"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/primary_ips/%d/actions/change_dns_ptr", opts.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, opts.ID)
+
+	reqBody := SchemaFromPrimaryIPChangeDNSPtrOpts(opts)
 
-	var respBody PrimaryIPChangeDNSPtrResult
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionChangeDNSPtrResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // ChangeProtection Changes the protection configuration of a Primary IP.
 func (c *PrimaryIPClient) ChangeProtection(ctx context.Context, opts PrimaryIPChangeProtectionOpts) (*Action, *Response, error) {
-	reqBodyData, err := json.Marshal(opts)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/primary_ips/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/primary_ips/%d/actions/change_protection", opts.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, opts.ID)
 
-	var respBody PrimaryIPChangeProtectionResult
-	resp, err := c.client.Do(req, &respBody)
+	reqBody := SchemaFromPrimaryIPChangeProtectionOpts(opts)
+
+	respBody, resp, err := postRequest[schema.PrimaryIPActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go
index 1f5ae42211d721f4d70a098dcb510d14b116f837..fd9d08ee2914d71f7a2384ade792d7c763cfed58 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go
@@ -49,6 +49,26 @@ func SchemaFromPrimaryIP(p *PrimaryIP) schema.PrimaryIP {
 	return c.SchemaFromPrimaryIP(p)
 }
 
+func SchemaFromPrimaryIPCreateOpts(o PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest {
+	return c.SchemaFromPrimaryIPCreateOpts(o)
+}
+
+func SchemaFromPrimaryIPUpdateOpts(o PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest {
+	return c.SchemaFromPrimaryIPUpdateOpts(o)
+}
+
+func SchemaFromPrimaryIPChangeDNSPtrOpts(o PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest {
+	return c.SchemaFromPrimaryIPChangeDNSPtrOpts(o)
+}
+
+func SchemaFromPrimaryIPChangeProtectionOpts(o PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest {
+	return c.SchemaFromPrimaryIPChangeProtectionOpts(o)
+}
+
+func SchemaFromPrimaryIPAssignOpts(o PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest {
+	return c.SchemaFromPrimaryIPAssignOpts(o)
+}
+
 // ISOFromSchema converts a schema.ISO to an ISO.
 func ISOFromSchema(s schema.ISO) *ISO {
 	return c.ISOFromSchema(s)
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/doc.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..057aef057f05049622debaddff5192d570d4cb36
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/doc.go
@@ -0,0 +1,4 @@
+// The schema package holds API schemas for the `hcloud-go` library.
+
+// Breaking changes may occur without notice. Do not use in production!
+package schema
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go
index 2d5cf5ddd8336090c2c4ae752c2d98ca471b8207..f1066bdee67e3f3920606a8d1f42ab1cda0d14b5 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go
@@ -7,7 +7,7 @@ type Error struct {
 	Code       string          `json:"code"`
 	Message    string          `json:"message"`
 	DetailsRaw json.RawMessage `json:"details"`
-	Details    interface{}
+	Details    any             `json:"-"`
 }
 
 // UnmarshalJSON overrides default json unmarshalling.
@@ -17,13 +17,20 @@ func (e *Error) UnmarshalJSON(data []byte) (err error) {
 	if err = json.Unmarshal(data, alias); err != nil {
 		return
 	}
-	if e.Code == "invalid_input" {
+	if e.Code == "invalid_input" && len(e.DetailsRaw) > 0 {
 		details := ErrorDetailsInvalidInput{}
 		if err = json.Unmarshal(e.DetailsRaw, &details); err != nil {
 			return
 		}
 		alias.Details = details
 	}
+	if e.Code == "deprecated_api_endpoint" && len(e.DetailsRaw) > 0 {
+		details := ErrorDetailsDeprecatedAPIEndpoint{}
+		if err = json.Unmarshal(e.DetailsRaw, &details); err != nil {
+			return
+		}
+		alias.Details = details
+	}
 	return
 }
 
@@ -40,3 +47,9 @@ type ErrorDetailsInvalidInput struct {
 		Messages []string `json:"messages"`
 	} `json:"fields"`
 }
+
+// ErrorDetailsDeprecatedAPIEndpoint defines the schema of the Details field
+// of an error with code 'deprecated_api_endpoint'.
+type ErrorDetailsDeprecatedAPIEndpoint struct {
+	Announcement string `json:"announcement"`
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/id_or_name.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/id_or_name.go
new file mode 100644
index 0000000000000000000000000000000000000000..75e1169bd47ac28eecd317786e0fb6a976c2dfcc
--- /dev/null
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/id_or_name.go
@@ -0,0 +1,68 @@
+package schema
+
+import (
+	"bytes"
+	"encoding/json"
+	"reflect"
+	"strconv"
+)
+
+// IDOrName can be used in API requests where either a resource id or name can be
+// specified.
+type IDOrName struct {
+	ID   int64
+	Name string
+}
+
+var _ json.Unmarshaler = (*IDOrName)(nil)
+var _ json.Marshaler = (*IDOrName)(nil)
+
+func (o IDOrName) MarshalJSON() ([]byte, error) {
+	if o.ID != 0 {
+		return json.Marshal(o.ID)
+	}
+	if o.Name != "" {
+		return json.Marshal(o.Name)
+	}
+
+	// We want to preserve the behavior of an empty interface{} to prevent breaking
+	// changes (marshaled to null when empty).
+	return json.Marshal(nil)
+}
+
+func (o *IDOrName) UnmarshalJSON(data []byte) error {
+	d := json.NewDecoder(bytes.NewBuffer(data))
+	// This ensures we won't lose precision on large IDs, see json.Number below
+	d.UseNumber()
+
+	var v any
+	if err := d.Decode(&v); err != nil {
+		return err
+	}
+
+	switch typed := v.(type) {
+	case string:
+		id, err := strconv.ParseInt(typed, 10, 64)
+		if err == nil {
+			o.ID = id
+		} else if typed != "" {
+			o.Name = typed
+		}
+	case json.Number:
+		id, err := typed.Int64()
+		if err != nil {
+			return &json.UnmarshalTypeError{
+				Value: string(data),
+				Type:  reflect.TypeOf(*o),
+			}
+		}
+		o.ID = id
+	default:
+		return &json.UnmarshalTypeError{
+			Value: string(data),
+			Type:  reflect.TypeOf(*o),
+		}
+	}
+
+	return nil
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go
index 7e1c4f5da64b572f88f525db4e28c188b4f64391..d8760ad2633ad03d218c0c843bdb89e3193f1b25 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go
@@ -251,7 +251,7 @@ type LoadBalancerDeleteServiceResponse struct {
 
 type LoadBalancerCreateRequest struct {
 	Name             string                              `json:"name"`
-	LoadBalancerType interface{}                         `json:"load_balancer_type"` // int or string
+	LoadBalancerType IDOrName                            `json:"load_balancer_type"`
 	Algorithm        *LoadBalancerCreateRequestAlgorithm `json:"algorithm,omitempty"`
 	Location         *string                             `json:"location,omitempty"`
 	NetworkZone      *string                             `json:"network_zone,omitempty"`
@@ -380,7 +380,7 @@ type LoadBalancerActionDisablePublicInterfaceResponse struct {
 }
 
 type LoadBalancerActionChangeTypeRequest struct {
-	LoadBalancerType interface{} `json:"load_balancer_type"` // int or string
+	LoadBalancerType IDOrName `json:"load_balancer_type"`
 }
 
 type LoadBalancerActionChangeTypeResponse struct {
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go
index 2344aea450a6b5c37650a24f4938cb1ec4fd9330..0e89c70eda8a47f8594d51ac8d63ecbebb4026bc 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go
@@ -11,6 +11,7 @@ type Network struct {
 	Subnets               []NetworkSubnet   `json:"subnets"`
 	Routes                []NetworkRoute    `json:"routes"`
 	Servers               []int64           `json:"servers"`
+	LoadBalancers         []int64           `json:"load_balancers"`
 	Protection            NetworkProtection `json:"protection"`
 	Labels                map[string]string `json:"labels"`
 	ExposeRoutesToVSwitch bool              `json:"expose_routes_to_vswitch"`
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go
index 192352f5d79058920bb5128aa4e499823b0d99bb..c1adbb1bd762edcd2b2cb92226b3128aeb1ad901 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go
@@ -2,12 +2,15 @@ package schema
 
 // Pricing defines the schema for pricing information.
 type Pricing struct {
-	Currency          string                    `json:"currency"`
-	VATRate           string                    `json:"vat_rate"`
-	Image             PricingImage              `json:"image"`
-	FloatingIP        PricingFloatingIP         `json:"floating_ip"`
-	FloatingIPs       []PricingFloatingIPType   `json:"floating_ips"`
-	PrimaryIPs        []PricingPrimaryIP        `json:"primary_ips"`
+	Currency string       `json:"currency"`
+	VATRate  string       `json:"vat_rate"`
+	Image    PricingImage `json:"image"`
+	// Deprecated: [Pricing.FloatingIP] is deprecated, use [Pricing.FloatingIPs] instead.
+	FloatingIP  PricingFloatingIP       `json:"floating_ip"`
+	FloatingIPs []PricingFloatingIPType `json:"floating_ips"`
+	PrimaryIPs  []PricingPrimaryIP      `json:"primary_ips"`
+	// Deprecated: [Pricing.Traffic] is deprecated and will report 0 after 2024-08-05.
+	// Use traffic pricing from [Pricing.ServerTypes] or [Pricing.LoadBalancerTypes] instead.
 	Traffic           PricingTraffic            `json:"traffic"`
 	ServerBackup      PricingServerBackup       `json:"server_backup"`
 	ServerTypes       []PricingServerType       `json:"server_types"`
@@ -72,6 +75,9 @@ type PricingServerTypePrice struct {
 	Location     string `json:"location"`
 	PriceHourly  Price  `json:"price_hourly"`
 	PriceMonthly Price  `json:"price_monthly"`
+
+	IncludedTraffic   uint64 `json:"included_traffic"`
+	PricePerTBTraffic Price  `json:"price_per_tb_traffic"`
 }
 
 // PricingLoadBalancerType defines the schema of pricing information for a Load Balancer type.
@@ -87,6 +93,9 @@ type PricingLoadBalancerTypePrice struct {
 	Location     string `json:"location"`
 	PriceHourly  Price  `json:"price_hourly"`
 	PriceMonthly Price  `json:"price_monthly"`
+
+	IncludedTraffic   uint64 `json:"included_traffic"`
+	PricePerTBTraffic Price  `json:"price_per_tb_traffic"`
 }
 
 // PricingGetResponse defines the schema of the response when retrieving pricing information.
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go
index 1901ce80017024b95bc468c7b4ed1d71fcabfec8..300711fc254e87a5416a2df0497429334e58c73c 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go
@@ -31,6 +31,18 @@ type PrimaryIPDNSPTR struct {
 	IP     string `json:"ip"`
 }
 
+// PrimaryIPCreateOpts defines the request to
+// create a Primary IP.
+type PrimaryIPCreateRequest struct {
+	Name         string            `json:"name"`
+	Type         string            `json:"type"`
+	AssigneeType string            `json:"assignee_type"`
+	AssigneeID   *int64            `json:"assignee_id,omitempty"`
+	Labels       map[string]string `json:"labels,omitempty"`
+	AutoDelete   *bool             `json:"auto_delete,omitempty"`
+	Datacenter   string            `json:"datacenter,omitempty"`
+}
+
 // PrimaryIPCreateResponse defines the schema of the response
 // when creating a Primary IP.
 type PrimaryIPCreateResponse struct {
@@ -38,19 +50,27 @@ type PrimaryIPCreateResponse struct {
 	Action    *Action   `json:"action"`
 }
 
-// PrimaryIPGetResult defines the response when retrieving a single Primary IP.
-type PrimaryIPGetResult struct {
+// PrimaryIPGetResponse defines the response when retrieving a single Primary IP.
+type PrimaryIPGetResponse struct {
 	PrimaryIP PrimaryIP `json:"primary_ip"`
 }
 
-// PrimaryIPListResult defines the response when listing Primary IPs.
-type PrimaryIPListResult struct {
+// PrimaryIPListResponse defines the response when listing Primary IPs.
+type PrimaryIPListResponse struct {
 	PrimaryIPs []PrimaryIP `json:"primary_ips"`
 }
 
-// PrimaryIPUpdateResult defines the response
+// PrimaryIPUpdateOpts defines the request to
+// update a Primary IP.
+type PrimaryIPUpdateRequest struct {
+	Name       string            `json:"name,omitempty"`
+	Labels     map[string]string `json:"labels,omitempty"`
+	AutoDelete *bool             `json:"auto_delete,omitempty"`
+}
+
+// PrimaryIPUpdateResponse defines the response
 // when updating a Primary IP.
-type PrimaryIPUpdateResult struct {
+type PrimaryIPUpdateResponse struct {
 	PrimaryIP PrimaryIP `json:"primary_ip"`
 }
 
@@ -60,3 +80,39 @@ type PrimaryIPActionChangeDNSPtrRequest struct {
 	IP     string  `json:"ip"`
 	DNSPtr *string `json:"dns_ptr"`
 }
+
+// PrimaryIPActionChangeDNSPtrResponse defines the response when setting a reverse DNS
+// pointer for a IP address.
+type PrimaryIPActionChangeDNSPtrResponse struct {
+	Action Action `json:"action"`
+}
+
+// PrimaryIPActionAssignRequest defines the request to
+// assign a Primary IP to an assignee (usually a server).
+type PrimaryIPActionAssignRequest struct {
+	AssigneeID   int64  `json:"assignee_id"`
+	AssigneeType string `json:"assignee_type"`
+}
+
+// PrimaryIPActionAssignResponse defines the response when assigning a Primary IP to a
+// assignee.
+type PrimaryIPActionAssignResponse struct {
+	Action Action `json:"action"`
+}
+
+// PrimaryIPActionUnassignResponse defines the response to unassign a Primary IP.
+type PrimaryIPActionUnassignResponse struct {
+	Action Action `json:"action"`
+}
+
+// PrimaryIPActionChangeProtectionRequest defines the request to
+// change protection configuration of a Primary IP.
+type PrimaryIPActionChangeProtectionRequest struct {
+	Delete bool `json:"delete"`
+}
+
+// PrimaryIPActionChangeProtectionResponse defines the response when changing the
+// protection of a Primary IP.
+type PrimaryIPActionChangeProtectionResponse struct {
+	Action Action `json:"action"`
+}
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go
index 068b3e81f9454d59c213b9397be0333b1110add2..72aaf269de7bd8e6ee0fbb0e899c558b9d0cc477 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go
@@ -99,8 +99,8 @@ type ServerListResponse struct {
 // create a server.
 type ServerCreateRequest struct {
 	Name             string                  `json:"name"`
-	ServerType       interface{}             `json:"server_type"` // int or string
-	Image            interface{}             `json:"image"`       // int or string
+	ServerType       IDOrName                `json:"server_type"`
+	Image            IDOrName                `json:"image"`
 	SSHKeys          []int64                 `json:"ssh_keys,omitempty"`
 	Location         string                  `json:"location,omitempty"`
 	Datacenter       string                  `json:"datacenter,omitempty"`
@@ -257,7 +257,7 @@ type ServerActionDisableRescueResponse struct {
 // ServerActionRebuildRequest defines the schema for the request to
 // rebuild a server.
 type ServerActionRebuildRequest struct {
-	Image interface{} `json:"image"` // int or string
+	Image IDOrName `json:"image"`
 }
 
 // ServerActionRebuildResponse defines the schema of the response when
@@ -270,7 +270,7 @@ type ServerActionRebuildResponse struct {
 // ServerActionAttachISORequest defines the schema for the request to
 // attach an ISO to a server.
 type ServerActionAttachISORequest struct {
-	ISO interface{} `json:"iso"` // int or string
+	ISO IDOrName `json:"iso"`
 }
 
 // ServerActionAttachISOResponse defines the schema of the response when
@@ -289,12 +289,6 @@ type ServerActionDetachISOResponse struct {
 	Action Action `json:"action"`
 }
 
-// ServerActionEnableBackupRequest defines the schema for the request to
-// enable backup for a server.
-type ServerActionEnableBackupRequest struct {
-	BackupWindow *string `json:"backup_window,omitempty"`
-}
-
 // ServerActionEnableBackupResponse defines the schema of the response when
 // creating a enable_backup server action.
 type ServerActionEnableBackupResponse struct {
@@ -314,8 +308,8 @@ type ServerActionDisableBackupResponse struct {
 // ServerActionChangeTypeRequest defines the schema for the request to
 // change a server's type.
 type ServerActionChangeTypeRequest struct {
-	ServerType  interface{} `json:"server_type"` // int or string
-	UpgradeDisk bool        `json:"upgrade_disk"`
+	ServerType  IDOrName `json:"server_type"`
+	UpgradeDisk bool     `json:"upgrade_disk"`
 }
 
 // ServerActionChangeTypeResponse defines the schema of the response when
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go
index 21a87da9a9b7cc3cdd9cb314de98e519eeff1d22..60f43a1bf860f40e3962d57351d9b09335c3ca05 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go
@@ -2,15 +2,18 @@ package schema
 
 // ServerType defines the schema of a server type.
 type ServerType struct {
-	ID              int64                    `json:"id"`
-	Name            string                   `json:"name"`
-	Description     string                   `json:"description"`
-	Cores           int                      `json:"cores"`
-	Memory          float32                  `json:"memory"`
-	Disk            int                      `json:"disk"`
-	StorageType     string                   `json:"storage_type"`
-	CPUType         string                   `json:"cpu_type"`
-	Architecture    string                   `json:"architecture"`
+	ID           int64   `json:"id"`
+	Name         string  `json:"name"`
+	Description  string  `json:"description"`
+	Cores        int     `json:"cores"`
+	Memory       float32 `json:"memory"`
+	Disk         int     `json:"disk"`
+	StorageType  string  `json:"storage_type"`
+	CPUType      string  `json:"cpu_type"`
+	Architecture string  `json:"architecture"`
+
+	// Deprecated: [ServerType.IncludedTraffic] is deprecated and will always report 0 after 2024-08-05.
+	// Use [ServerType.Prices] instead to get the included traffic for each location.
 	IncludedTraffic int64                    `json:"included_traffic"`
 	Prices          []PricingServerTypePrice `json:"prices"`
 	Deprecated      bool                     `json:"deprecated"`
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go
index 1de6459550c210531d74b0ba5e455016e165f5b1..89223ba43afe74e873e4e57931b082e94dfd5305 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go
@@ -23,7 +23,7 @@ type VolumeCreateRequest struct {
 	Name      string             `json:"name"`
 	Size      int                `json:"size"`
 	Server    *int64             `json:"server,omitempty"`
-	Location  interface{}        `json:"location,omitempty"` // int, string, or nil
+	Location  *IDOrName          `json:"location,omitempty"`
 	Labels    *map[string]string `json:"labels,omitempty"`
 	Automount *bool              `json:"automount,omitempty"`
 	Format    *string            `json:"format,omitempty"`
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go
index 7cc9df8d052ea26c4414bd1c42b2deb3b2352a47..b50df5ef277f41c2b39393af1141fadb1f3bebb9 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go
@@ -69,7 +69,6 @@ You can find a documentation of goverter here: https://goverter.jmattheis.de/
 // goverter:extend durationFromIntSeconds
 // goverter:extend intSecondsFromDuration
 // goverter:extend serverFromImageCreatedFromSchema
-// goverter:extend anyFromLoadBalancerType
 // goverter:extend serverMetricsTimeSeriesFromSchema
 // goverter:extend loadBalancerMetricsTimeSeriesFromSchema
 // goverter:extend stringPtrFromLoadBalancerServiceProtocol
@@ -108,6 +107,12 @@ type converter interface {
 	// goverter:map AssigneeID | mapZeroInt64ToNil
 	SchemaFromPrimaryIP(*PrimaryIP) schema.PrimaryIP
 
+	SchemaFromPrimaryIPCreateOpts(PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest
+	SchemaFromPrimaryIPUpdateOpts(PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest
+	SchemaFromPrimaryIPChangeDNSPtrOpts(PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest
+	SchemaFromPrimaryIPChangeProtectionOpts(PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest
+	SchemaFromPrimaryIPAssignOpts(PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest
+
 	ISOFromSchema(schema.ISO) *ISO
 
 	// We cannot use goverter settings when mapping a struct to a struct pointer
@@ -207,10 +212,12 @@ type converter interface {
 
 	// goverter:map PriceHourly Hourly
 	// goverter:map PriceMonthly Monthly
+	// goverter:map PricePerTBTraffic PerTBTraffic
 	LoadBalancerTypeLocationPricingFromSchema(schema.PricingLoadBalancerTypePrice) LoadBalancerTypeLocationPricing
 
 	// goverter:map Hourly PriceHourly
 	// goverter:map Monthly PriceMonthly
+	// goverter:map PerTBTraffic PricePerTBTraffic
 	SchemaFromLoadBalancerTypeLocationPricing(LoadBalancerTypeLocationPricing) schema.PricingLoadBalancerTypePrice
 
 	LoadBalancerServiceFromSchema(schema.LoadBalancerService) LoadBalancerService
@@ -263,6 +270,7 @@ type converter interface {
 
 	// goverter:map PriceHourly Hourly
 	// goverter:map PriceMonthly Monthly
+	// goverter:map PricePerTBTraffic PerTBTraffic
 	serverTypePricingFromSchema(schema.PricingServerTypePrice) ServerTypeLocationPricing
 
 	// goverter:map Image.PerGBMonth.Currency Currency
@@ -306,6 +314,7 @@ type converter interface {
 
 	// goverter:map Monthly PriceMonthly
 	// goverter:map Hourly PriceHourly
+	// goverter:map PerTBTraffic PricePerTBTraffic
 	schemaFromServerTypeLocationPricing(ServerTypeLocationPricing) schema.PricingServerTypePrice
 
 	FirewallFromSchema(schema.Firewall) *Firewall
@@ -606,37 +615,48 @@ func intSecondsFromDuration(d time.Duration) int {
 }
 
 func errorDetailsFromSchema(d interface{}) interface{} {
-	if d, ok := d.(schema.ErrorDetailsInvalidInput); ok {
+	switch typed := d.(type) {
+	case schema.ErrorDetailsInvalidInput:
 		details := ErrorDetailsInvalidInput{
-			Fields: make([]ErrorDetailsInvalidInputField, len(d.Fields)),
+			Fields: make([]ErrorDetailsInvalidInputField, len(typed.Fields)),
 		}
-		for i, field := range d.Fields {
+		for i, field := range typed.Fields {
 			details.Fields[i] = ErrorDetailsInvalidInputField{
 				Name:     field.Name,
 				Messages: field.Messages,
 			}
 		}
 		return details
+
+	case schema.ErrorDetailsDeprecatedAPIEndpoint:
+		return ErrorDetailsDeprecatedAPIEndpoint{
+			Announcement: typed.Announcement,
+		}
 	}
 	return nil
 }
 
 func schemaFromErrorDetails(d interface{}) interface{} {
-	if d, ok := d.(ErrorDetailsInvalidInput); ok {
+	switch typed := d.(type) {
+	case ErrorDetailsInvalidInput:
 		details := schema.ErrorDetailsInvalidInput{
 			Fields: make([]struct {
 				Name     string   `json:"name"`
 				Messages []string `json:"messages"`
-			}, len(d.Fields)),
+			}, len(typed.Fields)),
 		}
-		for i, field := range d.Fields {
+		for i, field := range typed.Fields {
 			details.Fields[i] = struct {
 				Name     string   `json:"name"`
 				Messages []string `json:"messages"`
 			}{Name: field.Name, Messages: field.Messages}
 		}
 		return details
+
+	case ErrorDetailsDeprecatedAPIEndpoint:
+		return schema.ErrorDetailsDeprecatedAPIEndpoint{Announcement: typed.Announcement}
 	}
+
 	return nil
 }
 
@@ -654,8 +674,8 @@ func imagePricingFromSchema(s schema.Pricing) ImagePricing {
 func floatingIPPricingFromSchema(s schema.Pricing) FloatingIPPricing {
 	return FloatingIPPricing{
 		Monthly: Price{
-			Net:      s.FloatingIP.PriceMonthly.Net,
-			Gross:    s.FloatingIP.PriceMonthly.Gross,
+			Net:      s.FloatingIP.PriceMonthly.Net,   // nolint:staticcheck // Field is deprecated, but removal is not planned
+			Gross:    s.FloatingIP.PriceMonthly.Gross, // nolint:staticcheck // Field is deprecated, but removal is not planned
 			Currency: s.Currency,
 			VATRate:  s.VATRate,
 		},
@@ -707,8 +727,8 @@ func primaryIPPricingFromSchema(s schema.Pricing) []PrimaryIPPricing {
 func trafficPricingFromSchema(s schema.Pricing) TrafficPricing {
 	return TrafficPricing{
 		PerTB: Price{
-			Net:      s.Traffic.PricePerTB.Net,
-			Gross:    s.Traffic.PricePerTB.Gross,
+			Net:      s.Traffic.PricePerTB.Net,   // nolint:staticcheck // Field is deprecated, but we still need to map it as long as it is available
+			Gross:    s.Traffic.PricePerTB.Gross, // nolint:staticcheck // Field is deprecated, but we still need to map it as long as it is available
 			Currency: s.Currency,
 			VATRate:  s.VATRate,
 		},
@@ -734,6 +754,13 @@ func serverTypePricingFromSchema(s schema.Pricing) []ServerTypePricing {
 					Net:      price.PriceMonthly.Net,
 					Gross:    price.PriceMonthly.Gross,
 				},
+				IncludedTraffic: price.IncludedTraffic,
+				PerTBTraffic: Price{
+					Currency: s.Currency,
+					VATRate:  s.VATRate,
+					Net:      price.PricePerTBTraffic.Net,
+					Gross:    price.PricePerTBTraffic.Gross,
+				},
 			}
 		}
 		p[i] = ServerTypePricing{
@@ -766,6 +793,13 @@ func loadBalancerTypePricingFromSchema(s schema.Pricing) []LoadBalancerTypePrici
 					Net:      price.PriceMonthly.Net,
 					Gross:    price.PriceMonthly.Gross,
 				},
+				IncludedTraffic: price.IncludedTraffic,
+				PerTBTraffic: Price{
+					Currency: s.Currency,
+					VATRate:  s.VATRate,
+					Net:      price.PricePerTBTraffic.Net,
+					Gross:    price.PricePerTBTraffic.Gross,
+				},
 			}
 		}
 		p[i] = LoadBalancerTypePricing{
@@ -790,16 +824,6 @@ func volumePricingFromSchema(s schema.Pricing) VolumePricing {
 	}
 }
 
-func anyFromLoadBalancerType(t *LoadBalancerType) interface{} {
-	if t == nil {
-		return nil
-	}
-	if t.ID != 0 {
-		return t.ID
-	}
-	return t.Name
-}
-
 func serverMetricsTimeSeriesFromSchema(s schema.ServerTimeSeriesVals) ([]ServerMetricsValue, error) {
 	vals := make([]ServerMetricsValue, len(s.Values))
 
@@ -922,7 +946,10 @@ func rawSchemaFromErrorDetails(v interface{}) json.RawMessage {
 	if v == nil {
 		return nil
 	}
-	msg, _ := json.Marshal(d)
+	msg, err := json.Marshal(d)
+	if err != nil {
+		return nil
+	}
 	return msg
 }
 
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go
index 8898456281ec947469086bd488a65d1467c1b328..78b822a40745969aafaa9193eb8aa11572ce8bdf 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go
@@ -1,17 +1,14 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net"
-	"net/http"
 	"net/url"
 	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -155,26 +152,21 @@ const (
 // changeDNSPtr changes or resets the reverse DNS pointer for a IP address.
 // Pass a nil ptr to reset the reverse DNS pointer to its default value.
 func (s *Server) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/change_dns_ptr"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, s.ID)
+
 	reqBody := schema.ServerActionChangeDNSPtrRequest{
 		IP:     ip.String(),
 		DNSPtr: ptr,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/servers/%d/actions/change_dns_ptr", s.ID)
-	req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ServerActionChangeDNSPtrResponse{}
-	resp, err := client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -198,41 +190,33 @@ type ServerClient struct {
 
 // GetByID retrieves a server by its ID. If the server does not exist, nil is returned.
 func (c *ServerClient) GetByID(ctx context.Context, id int64) (*Server, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/servers/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.ServerGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ServerGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return ServerFromSchema(body.Server), resp, nil
+
+	return ServerFromSchema(respBody.Server), resp, nil
 }
 
 // GetByName retrieves a server by its name. If the server does not exist, nil is returned.
 func (c *ServerClient) GetByName(ctx context.Context, name string) (*Server, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	servers, response, err := c.List(ctx, ServerListOpts{Name: name})
-	if len(servers) == 0 {
-		return nil, response, err
-	}
-	return servers[0], response, err
+	return firstByName(name, func() ([]*Server, *Response, error) {
+		return c.List(ctx, ServerListOpts{Name: name})
+	})
 }
 
 // Get retrieves a server by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a server by its name. If the server does not exist, nil is returned.
 func (c *ServerClient) Get(ctx context.Context, idOrName string) (*Server, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // ServerListOpts specifies options for listing servers.
@@ -262,22 +246,17 @@ func (l ServerListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *ServerClient) List(ctx context.Context, opts ServerListOpts) ([]*Server, *Response, error) {
-	path := "/servers?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.ServerListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ServerListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	servers := make([]*Server, 0, len(body.Servers))
-	for _, s := range body.Servers {
-		servers = append(servers, ServerFromSchema(s))
+		return nil, resp, err
 	}
-	return servers, resp, nil
+
+	return allFromSchemaFunc(respBody.Servers, ServerFromSchema), resp, nil
 }
 
 // All returns all servers.
@@ -287,22 +266,10 @@ func (c *ServerClient) All(ctx context.Context) ([]*Server, error) {
 
 // AllWithOpts returns all servers for the given options.
 func (c *ServerClient) AllWithOpts(ctx context.Context, opts ServerListOpts) ([]*Server, error) {
-	allServers := []*Server{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Server, *Response, error) {
 		opts.Page = page
-		servers, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allServers = append(allServers, servers...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allServers, nil
 }
 
 // ServerCreateOpts specifies options for creating a new server.
@@ -339,22 +306,16 @@ type ServerCreateFirewall struct {
 // Validate checks if options are valid.
 func (o ServerCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	if o.ServerType == nil || (o.ServerType.ID == 0 && o.ServerType.Name == "") {
-		return errors.New("missing server type")
+		return missingField(o, "ServerType")
 	}
 	if o.Image == nil || (o.Image.ID == 0 && o.Image.Name == "") {
-		return errors.New("missing image")
+		return missingField(o, "Image")
 	}
 	if o.Location != nil && o.Datacenter != nil {
-		return errors.New("location and datacenter are mutually exclusive")
-	}
-	if o.PublicNet != nil {
-		if !o.PublicNet.EnableIPv4 && !o.PublicNet.EnableIPv6 &&
-			len(o.Networks) == 0 && (o.StartAfterCreate == nil || *o.StartAfterCreate) {
-			return errors.New("missing networks or StartAfterCreate == false when EnableIPv4 and EnableIPv6 is false")
-		}
+		return mutuallyExclusiveFields(o, "Location", "Datacenter")
 	}
 	return nil
 }
@@ -369,8 +330,15 @@ type ServerCreateResult struct {
 
 // Create creates a new server.
 func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (ServerCreateResult, *Response, error) {
+	const opPath = "/servers"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := ServerCreateResult{}
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
-		return ServerCreateResult{}, nil, err
+		return result, nil, err
 	}
 
 	var reqBody schema.ServerCreateRequest
@@ -378,15 +346,11 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve
 	reqBody.Name = opts.Name
 	reqBody.Automount = opts.Automount
 	reqBody.StartAfterCreate = opts.StartAfterCreate
-	if opts.ServerType.ID != 0 {
-		reqBody.ServerType = opts.ServerType.ID
-	} else if opts.ServerType.Name != "" {
-		reqBody.ServerType = opts.ServerType.Name
+	if opts.ServerType.ID != 0 || opts.ServerType.Name != "" {
+		reqBody.ServerType = schema.IDOrName{ID: opts.ServerType.ID, Name: opts.ServerType.Name}
 	}
-	if opts.Image.ID != 0 {
-		reqBody.Image = opts.Image.ID
-	} else if opts.Image.Name != "" {
-		reqBody.Image = opts.Image.Name
+	if opts.Image.ID != 0 || opts.Image.Name != "" {
+		reqBody.Image = schema.IDOrName{ID: opts.Image.ID, Name: opts.Image.Name}
 	}
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
@@ -435,29 +399,19 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve
 	if opts.PlacementGroup != nil {
 		reqBody.PlacementGroup = opts.PlacementGroup.ID
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return ServerCreateResult{}, nil, err
-	}
 
-	req, err := c.client.NewRequest(ctx, "POST", "/servers", bytes.NewReader(reqBodyData))
+	respBody, resp, err := postRequest[schema.ServerCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return ServerCreateResult{}, nil, err
+		return result, resp, err
 	}
 
-	var respBody schema.ServerCreateResponse
-	resp, err := c.client.Do(req, &respBody)
-	if err != nil {
-		return ServerCreateResult{}, resp, err
-	}
-	result := ServerCreateResult{
-		Server:      ServerFromSchema(respBody.Server),
-		Action:      ActionFromSchema(respBody.Action),
-		NextActions: ActionsFromSchema(respBody.NextActions),
-	}
+	result.Server = ServerFromSchema(respBody.Server)
+	result.Action = ActionFromSchema(respBody.Action)
+	result.NextActions = ActionsFromSchema(respBody.NextActions)
 	if respBody.RootPassword != nil {
 		result.RootPassword = *respBody.RootPassword
 	}
+
 	return result, resp, nil
 }
 
@@ -476,20 +430,21 @@ func (c *ServerClient) Delete(ctx context.Context, server *Server) (*Response, e
 
 // DeleteWithResult deletes a server and returns the parsed response containing the action.
 func (c *ServerClient) DeleteWithResult(ctx context.Context, server *Server) (*ServerDeleteResult, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/servers/%d", server.ID), nil)
-	if err != nil {
-		return &ServerDeleteResult{}, nil, err
-	}
+	const opPath = "/servers/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var respBody schema.ServerDeleteResponse
-	resp, err := c.client.Do(req, &respBody)
+	result := &ServerDeleteResult{}
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	respBody, resp, err := deleteRequest[schema.ServerDeleteResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return &ServerDeleteResult{}, resp, err
+		return result, resp, err
 	}
 
-	return &ServerDeleteResult{
-		Action: ActionFromSchema(respBody.Action),
-	}, resp, nil
+	result.Action = ActionFromSchema(respBody.Action)
+
+	return result, resp, nil
 }
 
 // ServerUpdateOpts specifies options for updating a server.
@@ -500,108 +455,98 @@ type ServerUpdateOpts struct {
 
 // Update updates a server.
 func (c *ServerClient) Update(ctx context.Context, server *Server, opts ServerUpdateOpts) (*Server, *Response, error) {
+	const opPath = "/servers/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerUpdateRequest{
 		Name: opts.Name,
 	}
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/servers/%d", server.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ServerUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.ServerUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ServerFromSchema(respBody.Server), resp, nil
 }
 
 // Poweron starts a server.
 func (c *ServerClient) Poweron(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/poweron", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/poweron"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionPoweronResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionPoweronResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Reboot reboots a server.
 func (c *ServerClient) Reboot(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/reboot", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/reboot"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	respBody := schema.ServerActionRebootResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	respBody, resp, err := postRequest[schema.ServerActionRebootResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Reset resets a server.
 func (c *ServerClient) Reset(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/reset", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/reset"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionResetResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionResetResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Shutdown shuts down a server.
 func (c *ServerClient) Shutdown(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/shutdown", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/shutdown"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	respBody := schema.ServerActionShutdownResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	respBody, resp, err := postRequest[schema.ServerActionShutdownResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Poweroff stops a server.
 func (c *ServerClient) Poweroff(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/poweroff", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/poweroff"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionPoweroffResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionPoweroffResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -613,21 +558,22 @@ type ServerResetPasswordResult struct {
 
 // ResetPassword resets a server's password.
 func (c *ServerClient) ResetPassword(ctx context.Context, server *Server) (ServerResetPasswordResult, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/reset_password", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return ServerResetPasswordResult{}, nil, err
-	}
+	const opPath = "/servers/%d/actions/reset_password"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := ServerResetPasswordResult{}
 
-	respBody := schema.ServerActionResetPasswordResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	respBody, resp, err := postRequest[schema.ServerActionResetPasswordResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
-		return ServerResetPasswordResult{}, resp, err
+		return result, resp, err
 	}
-	return ServerResetPasswordResult{
-		Action:       ActionFromSchema(respBody.Action),
-		RootPassword: respBody.RootPassword,
-	}, resp, nil
+
+	result.Action = ActionFromSchema(respBody.Action)
+	result.RootPassword = respBody.RootPassword
+
+	return result, resp, nil
 }
 
 // ServerCreateImageOpts specifies options for creating an image from a server.
@@ -645,7 +591,7 @@ func (o ServerCreateImageOpts) Validate() error {
 	case "":
 		break
 	default:
-		return errors.New("invalid type")
+		return invalidFieldValue(o, "Type", o.Type)
 	}
 
 	return nil
@@ -659,10 +605,17 @@ type ServerCreateImageResult struct {
 
 // CreateImage creates an image from a server.
 func (c *ServerClient) CreateImage(ctx context.Context, server *Server, opts *ServerCreateImageOpts) (ServerCreateImageResult, *Response, error) {
-	var reqBody schema.ServerActionCreateImageRequest
+	const opPath = "/servers/%d/actions/create_image"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := ServerCreateImageResult{}
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	reqBody := schema.ServerActionCreateImageRequest{}
 	if opts != nil {
 		if err := opts.Validate(); err != nil {
-			return ServerCreateImageResult{}, nil, fmt.Errorf("invalid options: %s", err)
+			return result, nil, err
 		}
 		if opts.Description != nil {
 			reqBody.Description = opts.Description
@@ -674,26 +627,16 @@ func (c *ServerClient) CreateImage(ctx context.Context, server *Server, opts *Se
 			reqBody.Labels = &opts.Labels
 		}
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return ServerCreateImageResult{}, nil, err
-	}
 
-	path := fmt.Sprintf("/servers/%d/actions/create_image", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
+	respBody, resp, err := postRequest[schema.ServerActionCreateImageResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return ServerCreateImageResult{}, nil, err
+		return result, resp, err
 	}
 
-	respBody := schema.ServerActionCreateImageResponse{}
-	resp, err := c.client.Do(req, &respBody)
-	if err != nil {
-		return ServerCreateImageResult{}, resp, err
-	}
-	return ServerCreateImageResult{
-		Action: ActionFromSchema(respBody.Action),
-		Image:  ImageFromSchema(respBody.Image),
-	}, resp, nil
+	result.Image = ImageFromSchema(respBody.Image)
+	result.Action = ActionFromSchema(respBody.Action)
+
+	return result, resp, nil
 }
 
 // ServerEnableRescueOpts specifies options for enabling rescue mode for a server.
@@ -710,48 +653,43 @@ type ServerEnableRescueResult struct {
 
 // EnableRescue enables rescue mode for a server.
 func (c *ServerClient) EnableRescue(ctx context.Context, server *Server, opts ServerEnableRescueOpts) (ServerEnableRescueResult, *Response, error) {
+	const opPath = "/servers/%d/actions/enable_rescue"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := ServerEnableRescueResult{}
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionEnableRescueRequest{
 		Type: Ptr(string(opts.Type)),
 	}
 	for _, sshKey := range opts.SSHKeys {
 		reqBody.SSHKeys = append(reqBody.SSHKeys, sshKey.ID)
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return ServerEnableRescueResult{}, nil, err
-	}
 
-	path := fmt.Sprintf("/servers/%d/actions/enable_rescue", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
+	respBody, resp, err := postRequest[schema.ServerActionEnableRescueResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return ServerEnableRescueResult{}, nil, err
+		return result, resp, err
 	}
 
-	respBody := schema.ServerActionEnableRescueResponse{}
-	resp, err := c.client.Do(req, &respBody)
-	if err != nil {
-		return ServerEnableRescueResult{}, resp, err
-	}
-	result := ServerEnableRescueResult{
-		Action:       ActionFromSchema(respBody.Action),
-		RootPassword: respBody.RootPassword,
-	}
+	result.Action = ActionFromSchema(respBody.Action)
+	result.RootPassword = respBody.RootPassword
+
 	return result, resp, nil
 }
 
 // DisableRescue disables rescue mode for a server.
 func (c *ServerClient) DisableRescue(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/disable_rescue", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/disable_rescue"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionDisableRescueResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionDisableRescueResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -777,32 +715,24 @@ func (c *ServerClient) Rebuild(ctx context.Context, server *Server, opts ServerR
 
 // RebuildWithResult rebuilds a server.
 func (c *ServerClient) RebuildWithResult(ctx context.Context, server *Server, opts ServerRebuildOpts) (ServerRebuildResult, *Response, error) {
-	reqBody := schema.ServerActionRebuildRequest{}
-	if opts.Image.ID != 0 {
-		reqBody.Image = opts.Image.ID
-	} else {
-		reqBody.Image = opts.Image.Name
-	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return ServerRebuildResult{}, nil, err
-	}
+	const opPath = "/servers/%d/actions/rebuild"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/servers/%d/actions/rebuild", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return ServerRebuildResult{}, nil, err
+	result := ServerRebuildResult{}
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	reqBody := schema.ServerActionRebuildRequest{}
+	if opts.Image.ID != 0 || opts.Image.Name != "" {
+		reqBody.Image = schema.IDOrName{ID: opts.Image.ID, Name: opts.Image.Name}
 	}
 
-	respBody := schema.ServerActionRebuildResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionRebuildResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return ServerRebuildResult{}, resp, err
+		return result, resp, err
 	}
 
-	result := ServerRebuildResult{
-		Action: ActionFromSchema(respBody.Action),
-	}
+	result.Action = ActionFromSchema(respBody.Action)
 	if respBody.RootPassword != nil {
 		result.RootPassword = *respBody.RootPassword
 	}
@@ -812,87 +742,69 @@ func (c *ServerClient) RebuildWithResult(ctx context.Context, server *Server, op
 
 // AttachISO attaches an ISO to a server.
 func (c *ServerClient) AttachISO(ctx context.Context, server *Server, iso *ISO) (*Action, *Response, error) {
-	reqBody := schema.ServerActionAttachISORequest{}
-	if iso.ID != 0 {
-		reqBody.ISO = iso.ID
-	} else {
-		reqBody.ISO = iso.Name
-	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/attach_iso"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/servers/%d/actions/attach_iso", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	reqBody := schema.ServerActionAttachISORequest{}
+	if iso.ID != 0 || iso.Name != "" {
+		reqBody.ISO = schema.IDOrName{ID: iso.ID, Name: iso.Name}
 	}
 
-	respBody := schema.ServerActionAttachISOResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionAttachISOResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // DetachISO detaches the currently attached ISO from a server.
 func (c *ServerClient) DetachISO(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/detach_iso", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/detach_iso"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionDetachISOResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionDetachISOResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
-// EnableBackup enables backup for a server. Pass in an empty backup window to let the
-// API pick a window for you. See the API documentation at docs.hetzner.cloud for a list
-// of valid backup windows.
+// EnableBackup enables backup for a server.
+// The window parameter is deprecated and will be ignored.
 func (c *ServerClient) EnableBackup(ctx context.Context, server *Server, window string) (*Action, *Response, error) {
-	reqBody := schema.ServerActionEnableBackupRequest{}
-	if window != "" {
-		reqBody.BackupWindow = Ptr(window)
-	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	_ = window
 
-	path := fmt.Sprintf("/servers/%d/actions/enable_backup", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/enable_backup"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	respBody := schema.ServerActionEnableBackupResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	respBody, resp, err := postRequest[schema.ServerActionEnableBackupResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // DisableBackup disables backup for a server.
 func (c *ServerClient) DisableBackup(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/disable_backup", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/disable_backup"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionDisableBackupResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionDisableBackupResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -904,30 +816,23 @@ type ServerChangeTypeOpts struct {
 
 // ChangeType changes a server's type.
 func (c *ServerClient) ChangeType(ctx context.Context, server *Server, opts ServerChangeTypeOpts) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/change_type"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionChangeTypeRequest{
 		UpgradeDisk: opts.UpgradeDisk,
 	}
-	if opts.ServerType.ID != 0 {
-		reqBody.ServerType = opts.ServerType.ID
-	} else {
-		reqBody.ServerType = opts.ServerType.Name
-	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/servers/%d/actions/change_type", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
+	if opts.ServerType.ID != 0 || opts.ServerType.Name != "" {
+		reqBody.ServerType = schema.IDOrName{ID: opts.ServerType.ID, Name: opts.ServerType.Name}
 	}
 
-	respBody := schema.ServerActionChangeTypeResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionChangeTypeResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -949,27 +854,22 @@ type ServerChangeProtectionOpts struct {
 
 // ChangeProtection changes the resource protection level of a server.
 func (c *ServerClient) ChangeProtection(ctx context.Context, server *Server, opts ServerChangeProtectionOpts) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionChangeProtectionRequest{
 		Rebuild: opts.Rebuild,
 		Delete:  opts.Delete,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/servers/%d/actions/change_protection", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ServerActionChangeProtectionResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // ServerRequestConsoleResult is the result of requesting a WebSocket VNC console.
@@ -981,22 +881,23 @@ type ServerRequestConsoleResult struct {
 
 // RequestConsole requests a WebSocket VNC console.
 func (c *ServerClient) RequestConsole(ctx context.Context, server *Server) (ServerRequestConsoleResult, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/request_console", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return ServerRequestConsoleResult{}, nil, err
-	}
+	const opPath = "/servers/%d/actions/request_console"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := ServerRequestConsoleResult{}
 
-	respBody := schema.ServerActionRequestConsoleResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
+	respBody, resp, err := postRequest[schema.ServerActionRequestConsoleResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
-		return ServerRequestConsoleResult{}, resp, err
+		return result, resp, err
 	}
-	return ServerRequestConsoleResult{
-		Action:   ActionFromSchema(respBody.Action),
-		WSSURL:   respBody.WSSURL,
-		Password: respBody.Password,
-	}, resp, nil
+
+	result.Action = ActionFromSchema(respBody.Action)
+	result.WSSURL = respBody.WSSURL
+	result.Password = respBody.Password
+
+	return result, resp, nil
 }
 
 // ServerAttachToNetworkOpts specifies options for attaching a server to a network.
@@ -1008,6 +909,11 @@ type ServerAttachToNetworkOpts struct {
 
 // AttachToNetwork attaches a server to a network.
 func (c *ServerClient) AttachToNetwork(ctx context.Context, server *Server, opts ServerAttachToNetworkOpts) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/attach_to_network"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionAttachToNetworkRequest{
 		Network: opts.Network.ID,
 	}
@@ -1017,22 +923,12 @@ func (c *ServerClient) AttachToNetwork(ctx context.Context, server *Server, opts
 	for _, aliasIP := range opts.AliasIPs {
 		reqBody.AliasIPs = append(reqBody.AliasIPs, Ptr(aliasIP.String()))
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/servers/%d/actions/attach_to_network", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.ServerActionAttachToNetworkResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionAttachToNetworkResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, err
 }
 
@@ -1043,25 +939,20 @@ type ServerDetachFromNetworkOpts struct {
 
 // DetachFromNetwork detaches a server from a network.
 func (c *ServerClient) DetachFromNetwork(ctx context.Context, server *Server, opts ServerDetachFromNetworkOpts) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/detach_from_network"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionDetachFromNetworkRequest{
 		Network: opts.Network.ID,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/servers/%d/actions/detach_from_network", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ServerActionDetachFromNetworkResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionDetachFromNetworkResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, err
 }
 
@@ -1073,6 +964,11 @@ type ServerChangeAliasIPsOpts struct {
 
 // ChangeAliasIPs changes a server's alias IPs in a network.
 func (c *ServerClient) ChangeAliasIPs(ctx context.Context, server *Server, opts ServerChangeAliasIPsOpts) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/change_alias_ips"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionChangeAliasIPsRequest{
 		Network:  opts.Network.ID,
 		AliasIPs: []string{},
@@ -1080,21 +976,12 @@ func (c *ServerClient) ChangeAliasIPs(ctx context.Context, server *Server, opts
 	for _, aliasIP := range opts.AliasIPs {
 		reqBody.AliasIPs = append(reqBody.AliasIPs, aliasIP.String())
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-	path := fmt.Sprintf("/servers/%d/actions/change_alias_ips", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ServerActionDetachFromNetworkResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionChangeAliasIPsResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, err
 }
 
@@ -1117,32 +1004,34 @@ type ServerGetMetricsOpts struct {
 	Step  int
 }
 
-func (o *ServerGetMetricsOpts) addQueryParams(req *http.Request) error {
-	query := req.URL.Query()
-
+func (o ServerGetMetricsOpts) Validate() error {
 	if len(o.Types) == 0 {
-		return fmt.Errorf("no metric types specified")
+		return missingField(o, "Types")
+	}
+	if o.Start.IsZero() {
+		return missingField(o, "Start")
 	}
+	if o.End.IsZero() {
+		return missingField(o, "End")
+	}
+	return nil
+}
+
+func (o ServerGetMetricsOpts) values() url.Values {
+	query := url.Values{}
+
 	for _, typ := range o.Types {
 		query.Add("type", string(typ))
 	}
 
-	if o.Start.IsZero() {
-		return fmt.Errorf("no start time specified")
-	}
 	query.Add("start", o.Start.Format(time.RFC3339))
-
-	if o.End.IsZero() {
-		return fmt.Errorf("no end time specified")
-	}
 	query.Add("end", o.End.Format(time.RFC3339))
 
 	if o.Step > 0 {
 		query.Add("step", strconv.Itoa(o.Step))
 	}
-	req.URL.RawQuery = query.Encode()
 
-	return nil
+	return query
 }
 
 // ServerMetrics contains the metrics requested for a Server.
@@ -1161,64 +1050,60 @@ type ServerMetricsValue struct {
 
 // GetMetrics obtains metrics for Server.
 func (c *ServerClient) GetMetrics(ctx context.Context, server *Server, opts ServerGetMetricsOpts) (*ServerMetrics, *Response, error) {
-	var respBody schema.ServerGetMetricsResponse
+	const opPath = "/servers/%d/metrics?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
 	if server == nil {
-		return nil, nil, fmt.Errorf("illegal argument: server is nil")
+		return nil, nil, missingArgument("server", server)
 	}
 
-	path := fmt.Sprintf("/servers/%d/metrics", server.ID)
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, fmt.Errorf("new request: %v", err)
-	}
-	if err := opts.addQueryParams(req); err != nil {
-		return nil, nil, fmt.Errorf("add query params: %v", err)
+	if err := opts.Validate(); err != nil {
+		return nil, nil, err
 	}
-	resp, err := c.client.Do(req, &respBody)
+
+	reqPath := fmt.Sprintf(opPath, server.ID, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.ServerGetMetricsResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, fmt.Errorf("get metrics: %v", err)
+		return nil, resp, err
 	}
-	ms, err := serverMetricsFromSchema(&respBody)
+
+	metrics, err := serverMetricsFromSchema(&respBody)
 	if err != nil {
-		return nil, nil, fmt.Errorf("convert response body: %v", err)
+		return nil, nil, fmt.Errorf("convert response body: %w", err)
 	}
-	return ms, resp, nil
+
+	return metrics, resp, nil
 }
 
 func (c *ServerClient) AddToPlacementGroup(ctx context.Context, server *Server, placementGroup *PlacementGroup) (*Action, *Response, error) {
+	const opPath = "/servers/%d/actions/add_to_placement_group"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
+
 	reqBody := schema.ServerActionAddToPlacementGroupRequest{
 		PlacementGroup: placementGroup.ID,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-	path := fmt.Sprintf("/servers/%d/actions/add_to_placement_group", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.ServerActionAddToPlacementGroupResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionAddToPlacementGroupResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 func (c *ServerClient) RemoveFromPlacementGroup(ctx context.Context, server *Server) (*Action, *Response, error) {
-	path := fmt.Sprintf("/servers/%d/actions/remove_from_placement_group", server.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/servers/%d/actions/remove_from_placement_group"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, server.ID)
 
-	respBody := schema.ServerActionRemoveFromPlacementGroupResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.ServerActionRemoveFromPlacementGroupResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go
index 9640a2ac5482a8e931e4d56e51044d72d18d5d47..261f0c417f0df1b801c70cb102972d114745cf09 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"strconv"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -20,7 +21,9 @@ type ServerType struct {
 	StorageType  StorageType
 	CPUType      CPUType
 	Architecture Architecture
-	// IncludedTraffic is the free traffic per month in bytes
+
+	// Deprecated: [ServerType.IncludedTraffic] is deprecated and will always report 0 after 2024-08-05.
+	// Use [ServerType.Pricings] instead to get the included traffic for each location.
 	IncludedTraffic int64
 	Pricings        []ServerTypeLocationPricing
 	DeprecatableResource
@@ -55,32 +58,27 @@ type ServerTypeClient struct {
 
 // GetByID retrieves a server type by its ID. If the server type does not exist, nil is returned.
 func (c *ServerTypeClient) GetByID(ctx context.Context, id int64) (*ServerType, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/server_types/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/server_types/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.ServerTypeGetResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, id)
+
+	respBody, resp, err := getRequest[schema.ServerTypeGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return ServerTypeFromSchema(body.ServerType), resp, nil
+
+	return ServerTypeFromSchema(respBody.ServerType), resp, nil
 }
 
 // GetByName retrieves a server type by its name. If the server type does not exist, nil is returned.
 func (c *ServerTypeClient) GetByName(ctx context.Context, name string) (*ServerType, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	serverTypes, response, err := c.List(ctx, ServerTypeListOpts{Name: name})
-	if len(serverTypes) == 0 {
-		return nil, response, err
-	}
-	return serverTypes[0], response, err
+	return firstByName(name, func() ([]*ServerType, *Response, error) {
+		return c.List(ctx, ServerTypeListOpts{Name: name})
+	})
 }
 
 // Get retrieves a server type by its ID if the input can be parsed as an integer, otherwise it
@@ -115,22 +113,17 @@ func (l ServerTypeListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *ServerTypeClient) List(ctx context.Context, opts ServerTypeListOpts) ([]*ServerType, *Response, error) {
-	path := "/server_types?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/server_types?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.ServerTypeListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.ServerTypeListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	serverTypes := make([]*ServerType, 0, len(body.ServerTypes))
-	for _, s := range body.ServerTypes {
-		serverTypes = append(serverTypes, ServerTypeFromSchema(s))
+		return nil, resp, err
 	}
-	return serverTypes, resp, nil
+
+	return allFromSchemaFunc(respBody.ServerTypes, ServerTypeFromSchema), resp, nil
 }
 
 // All returns all server types.
@@ -140,20 +133,8 @@ func (c *ServerTypeClient) All(ctx context.Context) ([]*ServerType, error) {
 
 // AllWithOpts returns all server types for the given options.
 func (c *ServerTypeClient) AllWithOpts(ctx context.Context, opts ServerTypeListOpts) ([]*ServerType, error) {
-	allServerTypes := []*ServerType{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*ServerType, *Response, error) {
 		opts.Page = page
-		serverTypes, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allServerTypes = append(allServerTypes, serverTypes...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allServerTypes, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go
index 40537606c63354bdb4953eeacd873a40c865028e..ba6e5e65938187efb8d37fc68f7aac1eb2de1b61 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go
@@ -1,15 +1,12 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -30,50 +27,40 @@ type SSHKeyClient struct {
 
 // GetByID retrieves a SSH key by its ID. If the SSH key does not exist, nil is returned.
 func (c *SSHKeyClient) GetByID(ctx context.Context, id int64) (*SSHKey, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/ssh_keys/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/ssh_keys/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.SSHKeyGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.SSHKeyGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return SSHKeyFromSchema(body.SSHKey), resp, nil
+
+	return SSHKeyFromSchema(respBody.SSHKey), resp, nil
 }
 
 // GetByName retrieves a SSH key by its name. If the SSH key does not exist, nil is returned.
 func (c *SSHKeyClient) GetByName(ctx context.Context, name string) (*SSHKey, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	sshKeys, response, err := c.List(ctx, SSHKeyListOpts{Name: name})
-	if len(sshKeys) == 0 {
-		return nil, response, err
-	}
-	return sshKeys[0], response, err
+	return firstByName(name, func() ([]*SSHKey, *Response, error) {
+		return c.List(ctx, SSHKeyListOpts{Name: name})
+	})
 }
 
 // GetByFingerprint retreives a SSH key by its fingerprint. If the SSH key does not exist, nil is returned.
 func (c *SSHKeyClient) GetByFingerprint(ctx context.Context, fingerprint string) (*SSHKey, *Response, error) {
-	sshKeys, response, err := c.List(ctx, SSHKeyListOpts{Fingerprint: fingerprint})
-	if len(sshKeys) == 0 {
-		return nil, response, err
-	}
-	return sshKeys[0], response, err
+	return firstBy(func() ([]*SSHKey, *Response, error) {
+		return c.List(ctx, SSHKeyListOpts{Fingerprint: fingerprint})
+	})
 }
 
 // Get retrieves a SSH key by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a SSH key by its name. If the SSH key does not exist, nil is returned.
 func (c *SSHKeyClient) Get(ctx context.Context, idOrName string) (*SSHKey, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // SSHKeyListOpts specifies options for listing SSH keys.
@@ -103,22 +90,17 @@ func (l SSHKeyListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *SSHKeyClient) List(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, *Response, error) {
-	path := "/ssh_keys?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/ssh_keys?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	var body schema.SSHKeyListResponse
-	resp, err := c.client.Do(req, &body)
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
+
+	respBody, resp, err := getRequest[schema.SSHKeyListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	sshKeys := make([]*SSHKey, 0, len(body.SSHKeys))
-	for _, s := range body.SSHKeys {
-		sshKeys = append(sshKeys, SSHKeyFromSchema(s))
+		return nil, resp, err
 	}
-	return sshKeys, resp, nil
+
+	return allFromSchemaFunc(respBody.SSHKeys, SSHKeyFromSchema), resp, nil
 }
 
 // All returns all SSH keys.
@@ -128,22 +110,10 @@ func (c *SSHKeyClient) All(ctx context.Context) ([]*SSHKey, error) {
 
 // AllWithOpts returns all SSH keys with the given options.
 func (c *SSHKeyClient) AllWithOpts(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, error) {
-	allSSHKeys := []*SSHKey{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*SSHKey, *Response, error) {
 		opts.Page = page
-		sshKeys, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allSSHKeys = append(allSSHKeys, sshKeys...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allSSHKeys, nil
 }
 
 // SSHKeyCreateOpts specifies parameters for creating a SSH key.
@@ -156,16 +126,21 @@ type SSHKeyCreateOpts struct {
 // Validate checks if options are valid.
 func (o SSHKeyCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	if o.PublicKey == "" {
-		return errors.New("missing public key")
+		return missingField(o, "PublicKey")
 	}
 	return nil
 }
 
 // Create creates a new SSH key with the given options.
 func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHKey, *Response, error) {
+	const opPath = "/ssh_keys"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
 		return nil, nil, err
 	}
@@ -176,31 +151,23 @@ func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHK
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	req, err := c.client.NewRequest(ctx, "POST", "/ssh_keys", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody schema.SSHKeyCreateResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.SSHKeyCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return SSHKeyFromSchema(respBody.SSHKey), resp, nil
 }
 
 // Delete deletes a SSH key.
 func (c *SSHKeyClient) Delete(ctx context.Context, sshKey *SSHKey) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/ssh_keys/%d", sshKey.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/ssh_keys/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, sshKey.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // SSHKeyUpdateOpts specifies options for updating a SSH key.
@@ -211,27 +178,22 @@ type SSHKeyUpdateOpts struct {
 
 // Update updates a SSH key.
 func (c *SSHKeyClient) Update(ctx context.Context, sshKey *SSHKey, opts SSHKeyUpdateOpts) (*SSHKey, *Response, error) {
+	const opPath = "/ssh_keys/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, sshKey.ID)
+
 	reqBody := schema.SSHKeyUpdateRequest{
 		Name: opts.Name,
 	}
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/ssh_keys/%d", sshKey.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
 
-	respBody := schema.SSHKeyUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.SSHKeyUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return SSHKeyFromSchema(respBody.SSHKey), resp, nil
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go
index 225a23a52033b5bc134cdddc68a4e489a464b198..78685341cf4b902ca11c2dd8da779a63af435c42 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go
@@ -1,15 +1,12 @@
 package hcloud
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net/url"
-	"strconv"
 	"time"
 
+	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil"
 	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
 )
 
@@ -57,41 +54,33 @@ const (
 
 // GetByID retrieves a volume by its ID. If the volume does not exist, nil is returned.
 func (c *VolumeClient) GetByID(ctx context.Context, id int64) (*Volume, *Response, error) {
-	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/volumes/%d", id), nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/volumes/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, id)
 
-	var body schema.VolumeGetResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.VolumeGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
 		}
-		return nil, nil, err
+		return nil, resp, err
 	}
-	return VolumeFromSchema(body.Volume), resp, nil
+
+	return VolumeFromSchema(respBody.Volume), resp, nil
 }
 
 // GetByName retrieves a volume by its name. If the volume does not exist, nil is returned.
 func (c *VolumeClient) GetByName(ctx context.Context, name string) (*Volume, *Response, error) {
-	if name == "" {
-		return nil, nil, nil
-	}
-	volumes, response, err := c.List(ctx, VolumeListOpts{Name: name})
-	if len(volumes) == 0 {
-		return nil, response, err
-	}
-	return volumes[0], response, err
+	return firstByName(name, func() ([]*Volume, *Response, error) {
+		return c.List(ctx, VolumeListOpts{Name: name})
+	})
 }
 
 // Get retrieves a volume by its ID if the input can be parsed as an integer, otherwise it
 // retrieves a volume by its name. If the volume does not exist, nil is returned.
 func (c *VolumeClient) Get(ctx context.Context, idOrName string) (*Volume, *Response, error) {
-	if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
-		return c.GetByID(ctx, id)
-	}
-	return c.GetByName(ctx, idOrName)
+	return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName)
 }
 
 // VolumeListOpts specifies options for listing volumes.
@@ -121,22 +110,17 @@ func (l VolumeListOpts) values() url.Values {
 // Please note that filters specified in opts are not taken into account
 // when their value corresponds to their zero value or when they are empty.
 func (c *VolumeClient) List(ctx context.Context, opts VolumeListOpts) ([]*Volume, *Response, error) {
-	path := "/volumes?" + opts.values().Encode()
-	req, err := c.client.NewRequest(ctx, "GET", path, nil)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/volumes?%s"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	var body schema.VolumeListResponse
-	resp, err := c.client.Do(req, &body)
+	respBody, resp, err := getRequest[schema.VolumeListResponse](ctx, c.client, reqPath)
 	if err != nil {
-		return nil, nil, err
-	}
-	volumes := make([]*Volume, 0, len(body.Volumes))
-	for _, s := range body.Volumes {
-		volumes = append(volumes, VolumeFromSchema(s))
+		return nil, resp, err
 	}
-	return volumes, resp, nil
+
+	return allFromSchemaFunc(respBody.Volumes, VolumeFromSchema), resp, nil
 }
 
 // All returns all volumes.
@@ -146,22 +130,10 @@ func (c *VolumeClient) All(ctx context.Context) ([]*Volume, error) {
 
 // AllWithOpts returns all volumes with the given options.
 func (c *VolumeClient) AllWithOpts(ctx context.Context, opts VolumeListOpts) ([]*Volume, error) {
-	allVolumes := []*Volume{}
-
-	err := c.client.all(func(page int) (*Response, error) {
+	return iterPages(func(page int) ([]*Volume, *Response, error) {
 		opts.Page = page
-		volumes, resp, err := c.List(ctx, opts)
-		if err != nil {
-			return resp, err
-		}
-		allVolumes = append(allVolumes, volumes...)
-		return resp, nil
+		return c.List(ctx, opts)
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return allVolumes, nil
 }
 
 // VolumeCreateOpts specifies parameters for creating a volume.
@@ -178,19 +150,19 @@ type VolumeCreateOpts struct {
 // Validate checks if options are valid.
 func (o VolumeCreateOpts) Validate() error {
 	if o.Name == "" {
-		return errors.New("missing name")
+		return missingField(o, "Name")
 	}
 	if o.Size <= 0 {
-		return errors.New("size must be greater than 0")
+		return invalidFieldValue(o, "Size", o.Size)
 	}
 	if o.Server == nil && o.Location == nil {
-		return errors.New("one of server or location must be provided")
+		return missingOneOfFields(o, "Server", "Location")
 	}
 	if o.Server != nil && o.Location != nil {
-		return errors.New("only one of server or location must be provided")
+		return mutuallyExclusiveFields(o, "Server", "Location")
 	}
 	if o.Server == nil && (o.Automount != nil && *o.Automount) {
-		return errors.New("server must be provided when automount is true")
+		return missingRequiredTogetherFields(o, "Automount", "Server")
 	}
 	return nil
 }
@@ -204,8 +176,15 @@ type VolumeCreateResult struct {
 
 // Create creates a new volume with the given options.
 func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (VolumeCreateResult, *Response, error) {
+	const opPath = "/volumes"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	result := VolumeCreateResult{}
+
+	reqPath := opPath
+
 	if err := opts.Validate(); err != nil {
-		return VolumeCreateResult{}, nil, err
+		return result, nil, err
 	}
 	reqBody := schema.VolumeCreateRequest{
 		Name:      opts.Name,
@@ -220,48 +199,33 @@ func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (Volum
 		reqBody.Server = Ptr(opts.Server.ID)
 	}
 	if opts.Location != nil {
-		if opts.Location.ID != 0 {
-			reqBody.Location = opts.Location.ID
-		} else {
-			reqBody.Location = opts.Location.Name
+		if opts.Location.ID != 0 || opts.Location.Name != "" {
+			reqBody.Location = &schema.IDOrName{ID: opts.Location.ID, Name: opts.Location.Name}
 		}
 	}
 
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return VolumeCreateResult{}, nil, err
-	}
-
-	req, err := c.client.NewRequest(ctx, "POST", "/volumes", bytes.NewReader(reqBodyData))
-	if err != nil {
-		return VolumeCreateResult{}, nil, err
-	}
-
-	var respBody schema.VolumeCreateResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.VolumeCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
-		return VolumeCreateResult{}, resp, err
+		return result, resp, err
 	}
 
-	var action *Action
+	result.Volume = VolumeFromSchema(respBody.Volume)
 	if respBody.Action != nil {
-		action = ActionFromSchema(*respBody.Action)
+		result.Action = ActionFromSchema(*respBody.Action)
 	}
+	result.NextActions = ActionsFromSchema(respBody.NextActions)
 
-	return VolumeCreateResult{
-		Volume:      VolumeFromSchema(respBody.Volume),
-		Action:      action,
-		NextActions: ActionsFromSchema(respBody.NextActions),
-	}, resp, nil
+	return result, resp, nil
 }
 
 // Delete deletes a volume.
 func (c *VolumeClient) Delete(ctx context.Context, volume *Volume) (*Response, error) {
-	req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/volumes/%d", volume.ID), nil)
-	if err != nil {
-		return nil, err
-	}
-	return c.client.Do(req, nil)
+	const opPath = "/volumes/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, volume.ID)
+
+	return deleteRequestNoResult(ctx, c.client, reqPath)
 }
 
 // VolumeUpdateOpts specifies options for updating a volume.
@@ -272,28 +236,23 @@ type VolumeUpdateOpts struct {
 
 // Update updates a volume.
 func (c *VolumeClient) Update(ctx context.Context, volume *Volume, opts VolumeUpdateOpts) (*Volume, *Response, error) {
+	const opPath = "/volumes/%d"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, volume.ID)
+
 	reqBody := schema.VolumeUpdateRequest{
 		Name: opts.Name,
 	}
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/volumes/%d", volume.ID)
-	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.VolumeUpdateResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := putRequest[schema.VolumeUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return VolumeFromSchema(respBody.Volume), resp, nil
 }
 
@@ -305,27 +264,21 @@ type VolumeAttachOpts struct {
 
 // AttachWithOpts attaches a volume to a server.
 func (c *VolumeClient) AttachWithOpts(ctx context.Context, volume *Volume, opts VolumeAttachOpts) (*Action, *Response, error) {
+	const opPath = "/volumes/%d/actions/attach"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, volume.ID)
+
 	reqBody := schema.VolumeActionAttachVolumeRequest{
 		Server:    opts.Server.ID,
 		Automount: opts.Automount,
 	}
 
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	path := fmt.Sprintf("/volumes/%d/actions/attach", volume.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var respBody schema.VolumeActionAttachVolumeResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.VolumeActionAttachVolumeResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -336,23 +289,18 @@ func (c *VolumeClient) Attach(ctx context.Context, volume *Volume, server *Serve
 
 // Detach detaches a volume from a server.
 func (c *VolumeClient) Detach(ctx context.Context, volume *Volume) (*Action, *Response, error) {
-	var reqBody schema.VolumeActionDetachVolumeRequest
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
+	const opPath = "/volumes/%d/actions/detach"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
 
-	path := fmt.Sprintf("/volumes/%d/actions/detach", volume.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
+	reqPath := fmt.Sprintf(opPath, volume.ID)
+
+	var reqBody schema.VolumeActionDetachVolumeRequest
 
-	var respBody schema.VolumeActionDetachVolumeResponse
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.VolumeActionDetachVolumeResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
@@ -363,48 +311,38 @@ type VolumeChangeProtectionOpts struct {
 
 // ChangeProtection changes the resource protection level of a volume.
 func (c *VolumeClient) ChangeProtection(ctx context.Context, volume *Volume, opts VolumeChangeProtectionOpts) (*Action, *Response, error) {
+	const opPath = "/volumes/%d/actions/change_protection"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, volume.ID)
+
 	reqBody := schema.VolumeActionChangeProtectionRequest{
 		Delete: opts.Delete,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/volumes/%d/actions/change_protection", volume.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.VolumeActionChangeProtectionResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.VolumeActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return ActionFromSchema(respBody.Action), resp, err
+
+	return ActionFromSchema(respBody.Action), resp, nil
 }
 
 // Resize changes the size of a volume.
 func (c *VolumeClient) Resize(ctx context.Context, volume *Volume, size int) (*Action, *Response, error) {
+	const opPath = "/volumes/%d/actions/resize"
+	ctx = ctxutil.SetOpPath(ctx, opPath)
+
+	reqPath := fmt.Sprintf(opPath, volume.ID)
+
 	reqBody := schema.VolumeActionResizeVolumeRequest{
 		Size: size,
 	}
-	reqBodyData, err := json.Marshal(reqBody)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	path := fmt.Sprintf("/volumes/%d/actions/resize", volume.ID)
-	req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
-	if err != nil {
-		return nil, nil, err
-	}
-
-	respBody := schema.VolumeActionResizeVolumeResponse{}
-	resp, err := c.client.Do(req, &respBody)
+	respBody, resp, err := postRequest[schema.VolumeActionResizeVolumeResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return ActionFromSchema(respBody.Action), resp, err
 }
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go
index 13f8665dfd22e9f11ab7a6313d5e6d9c4a4744a2..ec4d3bfb097d2614a4239ea077760277e1fe59aa 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go
@@ -16,8 +16,12 @@ type IActionClient interface {
 	// when their value corresponds to their zero value or when they are empty.
 	List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error)
 	// All returns all actions.
+	//
+	// Deprecated: It is required to pass in a list of IDs since 30 January 2025. Please use [ActionClient.AllWithOpts] instead.
 	All(ctx context.Context) ([]*Action, error)
 	// AllWithOpts returns all actions for the given options.
+	//
+	// It is required to set [ActionListOpts.ID]. Any other fields set in the opts are ignored.
 	AllWithOpts(ctx context.Context, opts ActionListOpts) ([]*Action, error)
 	// WatchOverallProgress watches several actions' progress until they complete
 	// with success or error. This watching happens in a goroutine and updates are
@@ -35,7 +39,7 @@ type IActionClient interface {
 	// timeout, use the [context.Context]. Once the method has stopped watching,
 	// both returned channels are closed.
 	//
-	// WatchOverallProgress uses the [WithPollBackoffFunc] of the [Client] to wait
+	// WatchOverallProgress uses the [WithPollOpts] of the [Client] to wait
 	// until sending the next request.
 	//
 	// Deprecated: WatchOverallProgress is deprecated, use [WaitForFunc] instead.
@@ -56,19 +60,19 @@ type IActionClient interface {
 	// timeout, use the [context.Context]. Once the method has stopped watching,
 	// both returned channels are closed.
 	//
-	// WatchProgress uses the [WithPollBackoffFunc] of the [Client] to wait until
+	// WatchProgress uses the [WithPollOpts] of the [Client] to wait until
 	// sending the next request.
 	//
 	// Deprecated: WatchProgress is deprecated, use [WaitForFunc] instead.
 	WatchProgress(ctx context.Context, action *Action) (<-chan int, <-chan error)
 	// WaitForFunc waits until all actions are completed by polling the API at the interval
-	// defined by [WithPollBackoffFunc]. An action is considered as complete when its status is
+	// defined by [WithPollOpts]. An action is considered as complete when its status is
 	// either [ActionStatusSuccess] or [ActionStatusError].
 	//
 	// The handleUpdate callback is called every time an action is updated.
 	WaitForFunc(ctx context.Context, handleUpdate func(update *Action) error, actions ...*Action) error
 	// WaitFor waits until all actions succeed by polling the API at the interval defined by
-	// [WithPollBackoffFunc]. An action is considered as succeeded when its status is either
+	// [WithPollOpts]. An action is considered as succeeded when its status is either
 	// [ActionStatusSuccess].
 	//
 	// If a single action fails, the function will stop waiting and the error set in the
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go
index 2665f22b02d04a14f8e10ad2cd5bbdc62c68c2a7..16a635954a3cdd436ea8e83e5ae3279189cf877f 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go
@@ -64,7 +64,7 @@ type ILoadBalancerClient interface {
 	// ChangeType changes a Load Balancer's type.
 	ChangeType(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeTypeOpts) (*Action, *Response, error)
 	// GetMetrics obtains metrics for a Load Balancer.
-	GetMetrics(ctx context.Context, lb *LoadBalancer, opts LoadBalancerGetMetricsOpts) (*LoadBalancerMetrics, *Response, error)
+	GetMetrics(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerGetMetricsOpts) (*LoadBalancerMetrics, *Response, error)
 	// ChangeDNSPtr changes or resets the reverse DNS pointer for a Load Balancer.
 	// Pass a nil ptr to reset the reverse DNS pointer to its default value.
 	ChangeDNSPtr(ctx context.Context, lb *LoadBalancer, ip string, ptr *string) (*Action, *Response, error)
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go
index 6b8e617e233b0a59b7882f7e459f918fd7df8334..94390d475a2beaa4272d29b8b3db6cac8a4918bd 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go
@@ -27,11 +27,11 @@ type IPrimaryIPClient interface {
 	// AllWithOpts returns all Primary IPs for the given options.
 	AllWithOpts(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, error)
 	// Create creates a Primary IP.
-	Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error)
+	Create(ctx context.Context, opts PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error)
 	// Delete deletes a Primary IP.
 	Delete(ctx context.Context, primaryIP *PrimaryIP) (*Response, error)
 	// Update updates a Primary IP.
-	Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error)
+	Update(ctx context.Context, primaryIP *PrimaryIP, opts PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error)
 	// Assign a Primary IP to a resource.
 	Assign(ctx context.Context, opts PrimaryIPAssignOpts) (*Action, *Response, error)
 	// Unassign a Primary IP from a resource.
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go
old mode 100755
new mode 100644
index 66fe08d3a8e87361a98a564388f4cf0f13c78b5b..e9d63b8411943021cd0508b576a9fce6a246810c
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go
@@ -23,28 +23,22 @@ func (c *converterImpl) ActionFromSchema(source schema.Action) *Action {
 	if source.Error != nil {
 		pString = &source.Error.Code
 	}
-	var xstring string
 	if pString != nil {
-		xstring = *pString
+		hcloudAction.ErrorCode = *pString
 	}
-	hcloudAction.ErrorCode = xstring
 	var pString2 *string
 	if source.Error != nil {
 		pString2 = &source.Error.Message
 	}
-	var xstring2 string
 	if pString2 != nil {
-		xstring2 = *pString2
+		hcloudAction.ErrorMessage = *pString2
 	}
-	hcloudAction.ErrorMessage = xstring2
-	var pHcloudActionResourceList []*ActionResource
 	if source.Resources != nil {
-		pHcloudActionResourceList = make([]*ActionResource, len(source.Resources))
+		hcloudAction.Resources = make([]*ActionResource, len(source.Resources))
 		for i := 0; i < len(source.Resources); i++ {
-			pHcloudActionResourceList[i] = c.schemaActionResourceReferenceToPHcloudActionResource(source.Resources[i])
+			hcloudAction.Resources[i] = c.schemaActionResourceReferenceToPHcloudActionResource(source.Resources[i])
 		}
 	}
-	hcloudAction.Resources = pHcloudActionResourceList
 	return &hcloudAction
 }
 func (c *converterImpl) ActionsFromSchema(source []schema.Action) []*Action {
@@ -70,14 +64,12 @@ func (c *converterImpl) CertificateFromSchema(source schema.Certificate) *Certif
 	hcloudCertificate.DomainNames = source.DomainNames
 	hcloudCertificate.Fingerprint = source.Fingerprint
 	hcloudCertificate.Status = c.pSchemaCertificateStatusRefToPHcloudCertificateStatus(source.Status)
-	var hcloudCertificateUsedByRefList []CertificateUsedByRef
 	if source.UsedBy != nil {
-		hcloudCertificateUsedByRefList = make([]CertificateUsedByRef, len(source.UsedBy))
+		hcloudCertificate.UsedBy = make([]CertificateUsedByRef, len(source.UsedBy))
 		for i := 0; i < len(source.UsedBy); i++ {
-			hcloudCertificateUsedByRefList[i] = c.schemaCertificateUsedByRefToHcloudCertificateUsedByRef(source.UsedBy[i])
+			hcloudCertificate.UsedBy[i] = c.schemaCertificateUsedByRefToHcloudCertificateUsedByRef(source.UsedBy[i])
 		}
 	}
-	hcloudCertificate.UsedBy = hcloudCertificateUsedByRefList
 	return &hcloudCertificate
 }
 func (c *converterImpl) DatacenterFromSchema(source schema.Datacenter) *Datacenter {
@@ -112,42 +104,34 @@ func (c *converterImpl) FirewallFromSchema(source schema.Firewall) *Firewall {
 	hcloudFirewall.Name = source.Name
 	hcloudFirewall.Labels = source.Labels
 	hcloudFirewall.Created = c.timeTimeToTimeTime(source.Created)
-	var hcloudFirewallRuleList []FirewallRule
 	if source.Rules != nil {
-		hcloudFirewallRuleList = make([]FirewallRule, len(source.Rules))
+		hcloudFirewall.Rules = make([]FirewallRule, len(source.Rules))
 		for i := 0; i < len(source.Rules); i++ {
-			hcloudFirewallRuleList[i] = c.schemaFirewallRuleToHcloudFirewallRule(source.Rules[i])
+			hcloudFirewall.Rules[i] = c.schemaFirewallRuleToHcloudFirewallRule(source.Rules[i])
 		}
 	}
-	hcloudFirewall.Rules = hcloudFirewallRuleList
-	var hcloudFirewallResourceList []FirewallResource
 	if source.AppliedTo != nil {
-		hcloudFirewallResourceList = make([]FirewallResource, len(source.AppliedTo))
+		hcloudFirewall.AppliedTo = make([]FirewallResource, len(source.AppliedTo))
 		for j := 0; j < len(source.AppliedTo); j++ {
-			hcloudFirewallResourceList[j] = c.schemaFirewallResourceToHcloudFirewallResource(source.AppliedTo[j])
+			hcloudFirewall.AppliedTo[j] = c.schemaFirewallResourceToHcloudFirewallResource(source.AppliedTo[j])
 		}
 	}
-	hcloudFirewall.AppliedTo = hcloudFirewallResourceList
 	return &hcloudFirewall
 }
 func (c *converterImpl) FloatingIPFromSchema(source schema.FloatingIP) *FloatingIP {
 	var hcloudFloatingIP FloatingIP
 	hcloudFloatingIP.ID = source.ID
-	var xstring string
 	if source.Description != nil {
-		xstring = *source.Description
+		hcloudFloatingIP.Description = *source.Description
 	}
-	hcloudFloatingIP.Description = xstring
 	hcloudFloatingIP.Created = c.timeTimeToTimeTime(source.Created)
 	hcloudFloatingIP.IP = ipFromFloatingIPSchema(source)
 	hcloudFloatingIP.Network = networkFromFloatingIPSchema(source)
 	hcloudFloatingIP.Type = FloatingIPType(source.Type)
-	var pHcloudServer *Server
 	if source.Server != nil {
 		hcloudServer := serverFromInt64(*source.Server)
-		pHcloudServer = &hcloudServer
+		hcloudFloatingIP.Server = &hcloudServer
 	}
-	hcloudFloatingIP.Server = pHcloudServer
 	hcloudFloatingIP.DNSPtr = mapFromFloatingIPDNSPtrSchema(source.DNSPtr)
 	hcloudFloatingIP.HomeLocation = c.LocationFromSchema(source.HomeLocation)
 	hcloudFloatingIP.Blocked = source.Blocked
@@ -163,35 +147,27 @@ func (c *converterImpl) ISOFromSchema(source schema.ISO) *ISO {
 func (c *converterImpl) ImageFromSchema(source schema.Image) *Image {
 	var hcloudImage Image
 	hcloudImage.ID = source.ID
-	var xstring string
 	if source.Name != nil {
-		xstring = *source.Name
+		hcloudImage.Name = *source.Name
 	}
-	hcloudImage.Name = xstring
 	hcloudImage.Type = ImageType(source.Type)
 	hcloudImage.Status = ImageStatus(source.Status)
 	hcloudImage.Description = source.Description
-	var xfloat32 float32
 	if source.ImageSize != nil {
-		xfloat32 = *source.ImageSize
+		hcloudImage.ImageSize = *source.ImageSize
 	}
-	hcloudImage.ImageSize = xfloat32
 	hcloudImage.DiskSize = source.DiskSize
 	hcloudImage.Created = c.pTimeTimeToTimeTime(source.Created)
 	hcloudImage.CreatedFrom = c.pSchemaImageCreatedFromToPHcloudServer(source.CreatedFrom)
-	var pHcloudServer *Server
 	if source.BoundTo != nil {
 		hcloudServer := serverFromInt64(*source.BoundTo)
-		pHcloudServer = &hcloudServer
+		hcloudImage.BoundTo = &hcloudServer
 	}
-	hcloudImage.BoundTo = pHcloudServer
 	hcloudImage.RapidDeploy = source.RapidDeploy
 	hcloudImage.OSFlavor = source.OSFlavor
-	var xstring2 string
 	if source.OSVersion != nil {
-		xstring2 = *source.OSVersion
+		hcloudImage.OSVersion = *source.OSVersion
 	}
-	hcloudImage.OSVersion = xstring2
 	hcloudImage.Architecture = Architecture(source.Architecture)
 	hcloudImage.Protection = c.schemaImageProtectionToHcloudImageProtection(source.Protection)
 	hcloudImage.Deprecated = c.pTimeTimeToTimeTime(source.Deprecated)
@@ -204,47 +180,37 @@ func (c *converterImpl) LoadBalancerFromSchema(source schema.LoadBalancer) *Load
 	hcloudLoadBalancer.ID = source.ID
 	hcloudLoadBalancer.Name = source.Name
 	hcloudLoadBalancer.PublicNet = c.schemaLoadBalancerPublicNetToHcloudLoadBalancerPublicNet(source.PublicNet)
-	var hcloudLoadBalancerPrivateNetList []LoadBalancerPrivateNet
 	if source.PrivateNet != nil {
-		hcloudLoadBalancerPrivateNetList = make([]LoadBalancerPrivateNet, len(source.PrivateNet))
+		hcloudLoadBalancer.PrivateNet = make([]LoadBalancerPrivateNet, len(source.PrivateNet))
 		for i := 0; i < len(source.PrivateNet); i++ {
-			hcloudLoadBalancerPrivateNetList[i] = c.schemaLoadBalancerPrivateNetToHcloudLoadBalancerPrivateNet(source.PrivateNet[i])
+			hcloudLoadBalancer.PrivateNet[i] = c.schemaLoadBalancerPrivateNetToHcloudLoadBalancerPrivateNet(source.PrivateNet[i])
 		}
 	}
-	hcloudLoadBalancer.PrivateNet = hcloudLoadBalancerPrivateNetList
 	hcloudLoadBalancer.Location = c.LocationFromSchema(source.Location)
 	hcloudLoadBalancer.LoadBalancerType = c.LoadBalancerTypeFromSchema(source.LoadBalancerType)
 	hcloudLoadBalancer.Algorithm = c.schemaLoadBalancerAlgorithmToHcloudLoadBalancerAlgorithm(source.Algorithm)
-	var hcloudLoadBalancerServiceList []LoadBalancerService
 	if source.Services != nil {
-		hcloudLoadBalancerServiceList = make([]LoadBalancerService, len(source.Services))
+		hcloudLoadBalancer.Services = make([]LoadBalancerService, len(source.Services))
 		for j := 0; j < len(source.Services); j++ {
-			hcloudLoadBalancerServiceList[j] = c.LoadBalancerServiceFromSchema(source.Services[j])
+			hcloudLoadBalancer.Services[j] = c.LoadBalancerServiceFromSchema(source.Services[j])
 		}
 	}
-	hcloudLoadBalancer.Services = hcloudLoadBalancerServiceList
-	var hcloudLoadBalancerTargetList []LoadBalancerTarget
 	if source.Targets != nil {
-		hcloudLoadBalancerTargetList = make([]LoadBalancerTarget, len(source.Targets))
+		hcloudLoadBalancer.Targets = make([]LoadBalancerTarget, len(source.Targets))
 		for k := 0; k < len(source.Targets); k++ {
-			hcloudLoadBalancerTargetList[k] = c.LoadBalancerTargetFromSchema(source.Targets[k])
+			hcloudLoadBalancer.Targets[k] = c.LoadBalancerTargetFromSchema(source.Targets[k])
 		}
 	}
-	hcloudLoadBalancer.Targets = hcloudLoadBalancerTargetList
 	hcloudLoadBalancer.Protection = c.schemaLoadBalancerProtectionToHcloudLoadBalancerProtection(source.Protection)
 	hcloudLoadBalancer.Labels = source.Labels
 	hcloudLoadBalancer.Created = c.timeTimeToTimeTime(source.Created)
 	hcloudLoadBalancer.IncludedTraffic = source.IncludedTraffic
-	var xuint64 uint64
 	if source.OutgoingTraffic != nil {
-		xuint64 = *source.OutgoingTraffic
+		hcloudLoadBalancer.OutgoingTraffic = *source.OutgoingTraffic
 	}
-	hcloudLoadBalancer.OutgoingTraffic = xuint64
-	var xuint642 uint64
 	if source.IngoingTraffic != nil {
-		xuint642 = *source.IngoingTraffic
+		hcloudLoadBalancer.IngoingTraffic = *source.IngoingTraffic
 	}
-	hcloudLoadBalancer.IngoingTraffic = xuint642
 	return &hcloudLoadBalancer
 }
 func (c *converterImpl) LoadBalancerMetricsFromSchema(source *schema.LoadBalancerGetMetricsResponse) (*LoadBalancerMetrics, error) {
@@ -254,18 +220,16 @@ func (c *converterImpl) LoadBalancerMetricsFromSchema(source *schema.LoadBalance
 		hcloudLoadBalancerMetrics.Start = c.timeTimeToTimeTime((*source).Metrics.Start)
 		hcloudLoadBalancerMetrics.End = c.timeTimeToTimeTime((*source).Metrics.End)
 		hcloudLoadBalancerMetrics.Step = (*source).Metrics.Step
-		var mapStringHcloudLoadBalancerMetricsValueList map[string][]LoadBalancerMetricsValue
 		if (*source).Metrics.TimeSeries != nil {
-			mapStringHcloudLoadBalancerMetricsValueList = make(map[string][]LoadBalancerMetricsValue, len((*source).Metrics.TimeSeries))
+			hcloudLoadBalancerMetrics.TimeSeries = make(map[string][]LoadBalancerMetricsValue, len((*source).Metrics.TimeSeries))
 			for key, value := range (*source).Metrics.TimeSeries {
 				hcloudLoadBalancerMetricsValueList, err := loadBalancerMetricsTimeSeriesFromSchema(value)
 				if err != nil {
 					return nil, err
 				}
-				mapStringHcloudLoadBalancerMetricsValueList[key] = hcloudLoadBalancerMetricsValueList
+				hcloudLoadBalancerMetrics.TimeSeries[key] = hcloudLoadBalancerMetricsValueList
 			}
 		}
-		hcloudLoadBalancerMetrics.TimeSeries = mapStringHcloudLoadBalancerMetricsValueList
 		pHcloudLoadBalancerMetrics = &hcloudLoadBalancerMetrics
 	}
 	return pHcloudLoadBalancerMetrics, nil
@@ -300,22 +264,18 @@ func (c *converterImpl) LoadBalancerTargetFromSchema(source schema.LoadBalancerT
 	hcloudLoadBalancerTarget.Server = c.pSchemaLoadBalancerTargetServerToPHcloudLoadBalancerTargetServer(source.Server)
 	hcloudLoadBalancerTarget.LabelSelector = c.pSchemaLoadBalancerTargetLabelSelectorToPHcloudLoadBalancerTargetLabelSelector(source.LabelSelector)
 	hcloudLoadBalancerTarget.IP = c.pSchemaLoadBalancerTargetIPToPHcloudLoadBalancerTargetIP(source.IP)
-	var hcloudLoadBalancerTargetHealthStatusList []LoadBalancerTargetHealthStatus
 	if source.HealthStatus != nil {
-		hcloudLoadBalancerTargetHealthStatusList = make([]LoadBalancerTargetHealthStatus, len(source.HealthStatus))
+		hcloudLoadBalancerTarget.HealthStatus = make([]LoadBalancerTargetHealthStatus, len(source.HealthStatus))
 		for i := 0; i < len(source.HealthStatus); i++ {
-			hcloudLoadBalancerTargetHealthStatusList[i] = c.LoadBalancerTargetHealthStatusFromSchema(source.HealthStatus[i])
+			hcloudLoadBalancerTarget.HealthStatus[i] = c.LoadBalancerTargetHealthStatusFromSchema(source.HealthStatus[i])
 		}
 	}
-	hcloudLoadBalancerTarget.HealthStatus = hcloudLoadBalancerTargetHealthStatusList
-	var hcloudLoadBalancerTargetList []LoadBalancerTarget
 	if source.Targets != nil {
-		hcloudLoadBalancerTargetList = make([]LoadBalancerTarget, len(source.Targets))
+		hcloudLoadBalancerTarget.Targets = make([]LoadBalancerTarget, len(source.Targets))
 		for j := 0; j < len(source.Targets); j++ {
-			hcloudLoadBalancerTargetList[j] = c.LoadBalancerTargetFromSchema(source.Targets[j])
+			hcloudLoadBalancerTarget.Targets[j] = c.LoadBalancerTargetFromSchema(source.Targets[j])
 		}
 	}
-	hcloudLoadBalancerTarget.Targets = hcloudLoadBalancerTargetList
 	hcloudLoadBalancerTarget.UsePrivateIP = source.UsePrivateIP
 	return hcloudLoadBalancerTarget
 }
@@ -340,14 +300,12 @@ func (c *converterImpl) LoadBalancerTypeFromSchema(source schema.LoadBalancerTyp
 	hcloudLoadBalancerType.MaxServices = source.MaxServices
 	hcloudLoadBalancerType.MaxTargets = source.MaxTargets
 	hcloudLoadBalancerType.MaxAssignedCertificates = source.MaxAssignedCertificates
-	var hcloudLoadBalancerTypeLocationPricingList []LoadBalancerTypeLocationPricing
 	if source.Prices != nil {
-		hcloudLoadBalancerTypeLocationPricingList = make([]LoadBalancerTypeLocationPricing, len(source.Prices))
+		hcloudLoadBalancerType.Pricings = make([]LoadBalancerTypeLocationPricing, len(source.Prices))
 		for i := 0; i < len(source.Prices); i++ {
-			hcloudLoadBalancerTypeLocationPricingList[i] = c.LoadBalancerTypeLocationPricingFromSchema(source.Prices[i])
+			hcloudLoadBalancerType.Pricings[i] = c.LoadBalancerTypeLocationPricingFromSchema(source.Prices[i])
 		}
 	}
-	hcloudLoadBalancerType.Pricings = hcloudLoadBalancerTypeLocationPricingList
 	hcloudLoadBalancerType.Deprecated = source.Deprecated
 	return &hcloudLoadBalancerType
 }
@@ -357,6 +315,8 @@ func (c *converterImpl) LoadBalancerTypeLocationPricingFromSchema(source schema.
 	hcloudLoadBalancerTypeLocationPricing.Location = &hcloudLocation
 	hcloudLoadBalancerTypeLocationPricing.Hourly = c.PriceFromSchema(source.PriceHourly)
 	hcloudLoadBalancerTypeLocationPricing.Monthly = c.PriceFromSchema(source.PriceMonthly)
+	hcloudLoadBalancerTypeLocationPricing.IncludedTraffic = source.IncludedTraffic
+	hcloudLoadBalancerTypeLocationPricing.PerTBTraffic = c.PriceFromSchema(source.PricePerTBTraffic)
 	return hcloudLoadBalancerTypeLocationPricing
 }
 func (c *converterImpl) LocationFromSchema(source schema.Location) *Location {
@@ -378,31 +338,32 @@ func (c *converterImpl) NetworkFromSchema(source schema.Network) *Network {
 	hcloudNetwork.Created = c.timeTimeToTimeTime(source.Created)
 	netIPNet := ipNetFromString(source.IPRange)
 	hcloudNetwork.IPRange = &netIPNet
-	var hcloudNetworkSubnetList []NetworkSubnet
 	if source.Subnets != nil {
-		hcloudNetworkSubnetList = make([]NetworkSubnet, len(source.Subnets))
+		hcloudNetwork.Subnets = make([]NetworkSubnet, len(source.Subnets))
 		for i := 0; i < len(source.Subnets); i++ {
-			hcloudNetworkSubnetList[i] = c.NetworkSubnetFromSchema(source.Subnets[i])
+			hcloudNetwork.Subnets[i] = c.NetworkSubnetFromSchema(source.Subnets[i])
 		}
 	}
-	hcloudNetwork.Subnets = hcloudNetworkSubnetList
-	var hcloudNetworkRouteList []NetworkRoute
 	if source.Routes != nil {
-		hcloudNetworkRouteList = make([]NetworkRoute, len(source.Routes))
+		hcloudNetwork.Routes = make([]NetworkRoute, len(source.Routes))
 		for j := 0; j < len(source.Routes); j++ {
-			hcloudNetworkRouteList[j] = c.NetworkRouteFromSchema(source.Routes[j])
+			hcloudNetwork.Routes[j] = c.NetworkRouteFromSchema(source.Routes[j])
 		}
 	}
-	hcloudNetwork.Routes = hcloudNetworkRouteList
-	var pHcloudServerList []*Server
 	if source.Servers != nil {
-		pHcloudServerList = make([]*Server, len(source.Servers))
+		hcloudNetwork.Servers = make([]*Server, len(source.Servers))
 		for k := 0; k < len(source.Servers); k++ {
 			hcloudServer := serverFromInt64(source.Servers[k])
-			pHcloudServerList[k] = &hcloudServer
+			hcloudNetwork.Servers[k] = &hcloudServer
+		}
+	}
+	if source.LoadBalancers != nil {
+		hcloudNetwork.LoadBalancers = make([]*LoadBalancer, len(source.LoadBalancers))
+		for l := 0; l < len(source.LoadBalancers); l++ {
+			hcloudLoadBalancer := loadBalancerFromInt64(source.LoadBalancers[l])
+			hcloudNetwork.LoadBalancers[l] = &hcloudLoadBalancer
 		}
 	}
-	hcloudNetwork.Servers = pHcloudServerList
 	hcloudNetwork.Protection = c.schemaNetworkProtectionToHcloudNetworkProtection(source.Protection)
 	hcloudNetwork.Labels = source.Labels
 	hcloudNetwork.ExposeRoutesToVSwitch = source.ExposeRoutesToVSwitch
@@ -474,11 +435,9 @@ func (c *converterImpl) PrimaryIPFromSchema(source schema.PrimaryIP) *PrimaryIP
 	hcloudPrimaryIP.Type = PrimaryIPType(source.Type)
 	hcloudPrimaryIP.Protection = c.schemaPrimaryIPProtectionToHcloudPrimaryIPProtection(source.Protection)
 	hcloudPrimaryIP.DNSPtr = mapFromPrimaryIPDNSPtrSchema(source.DNSPtr)
-	var xint64 int64
 	if source.AssigneeID != nil {
-		xint64 = *source.AssigneeID
+		hcloudPrimaryIP.AssigneeID = *source.AssigneeID
 	}
-	hcloudPrimaryIP.AssigneeID = xint64
 	hcloudPrimaryIP.AssigneeType = source.AssigneeType
 	hcloudPrimaryIP.AutoDelete = source.AutoDelete
 	hcloudPrimaryIP.Blocked = source.Blocked
@@ -507,14 +466,12 @@ func (c *converterImpl) SchemaFromAction(source *Action) schema.Action {
 		schemaAction2.Started = c.timeTimeToTimeTime((*source).Started)
 		schemaAction2.Finished = timeToTimePtr((*source).Finished)
 		schemaAction2.Error = schemaActionErrorFromAction((*source))
-		var schemaActionResourceReferenceList []schema.ActionResourceReference
 		if (*source).Resources != nil {
-			schemaActionResourceReferenceList = make([]schema.ActionResourceReference, len((*source).Resources))
+			schemaAction2.Resources = make([]schema.ActionResourceReference, len((*source).Resources))
 			for i := 0; i < len((*source).Resources); i++ {
-				schemaActionResourceReferenceList[i] = c.pHcloudActionResourceToSchemaActionResourceReference((*source).Resources[i])
+				schemaAction2.Resources[i] = c.pHcloudActionResourceToSchemaActionResourceReference((*source).Resources[i])
 			}
 		}
-		schemaAction2.Resources = schemaActionResourceReferenceList
 		schemaAction = schemaAction2
 	}
 	return schemaAction
@@ -544,14 +501,12 @@ func (c *converterImpl) SchemaFromCertificate(source *Certificate) schema.Certif
 		schemaCertificate2.DomainNames = (*source).DomainNames
 		schemaCertificate2.Fingerprint = (*source).Fingerprint
 		schemaCertificate2.Status = c.pHcloudCertificateStatusToPSchemaCertificateStatusRef((*source).Status)
-		var schemaCertificateUsedByRefList []schema.CertificateUsedByRef
 		if (*source).UsedBy != nil {
-			schemaCertificateUsedByRefList = make([]schema.CertificateUsedByRef, len((*source).UsedBy))
+			schemaCertificate2.UsedBy = make([]schema.CertificateUsedByRef, len((*source).UsedBy))
 			for i := 0; i < len((*source).UsedBy); i++ {
-				schemaCertificateUsedByRefList[i] = c.hcloudCertificateUsedByRefToSchemaCertificateUsedByRef((*source).UsedBy[i])
+				schemaCertificate2.UsedBy[i] = c.hcloudCertificateUsedByRefToSchemaCertificateUsedByRef((*source).UsedBy[i])
 			}
 		}
-		schemaCertificate2.UsedBy = schemaCertificateUsedByRefList
 		schemaCertificate = schemaCertificate2
 	}
 	return schemaCertificate
@@ -595,22 +550,18 @@ func (c *converterImpl) SchemaFromFirewall(source *Firewall) schema.Firewall {
 		schemaFirewall2.Name = (*source).Name
 		schemaFirewall2.Labels = (*source).Labels
 		schemaFirewall2.Created = c.timeTimeToTimeTime((*source).Created)
-		var schemaFirewallRuleList []schema.FirewallRule
 		if (*source).Rules != nil {
-			schemaFirewallRuleList = make([]schema.FirewallRule, len((*source).Rules))
+			schemaFirewall2.Rules = make([]schema.FirewallRule, len((*source).Rules))
 			for i := 0; i < len((*source).Rules); i++ {
-				schemaFirewallRuleList[i] = c.hcloudFirewallRuleToSchemaFirewallRule((*source).Rules[i])
+				schemaFirewall2.Rules[i] = c.hcloudFirewallRuleToSchemaFirewallRule((*source).Rules[i])
 			}
 		}
-		schemaFirewall2.Rules = schemaFirewallRuleList
-		var schemaFirewallResourceList []schema.FirewallResource
 		if (*source).AppliedTo != nil {
-			schemaFirewallResourceList = make([]schema.FirewallResource, len((*source).AppliedTo))
+			schemaFirewall2.AppliedTo = make([]schema.FirewallResource, len((*source).AppliedTo))
 			for j := 0; j < len((*source).AppliedTo); j++ {
-				schemaFirewallResourceList[j] = c.SchemaFromFirewallResource((*source).AppliedTo[j])
+				schemaFirewall2.AppliedTo[j] = c.SchemaFromFirewallResource((*source).AppliedTo[j])
 			}
 		}
-		schemaFirewall2.AppliedTo = schemaFirewallResourceList
 		schemaFirewall = schemaFirewall2
 	}
 	return schemaFirewall
@@ -619,22 +570,18 @@ func (c *converterImpl) SchemaFromFirewallCreateOpts(source FirewallCreateOpts)
 	var schemaFirewallCreateRequest schema.FirewallCreateRequest
 	schemaFirewallCreateRequest.Name = source.Name
 	schemaFirewallCreateRequest.Labels = stringMapToStringMapPtr(source.Labels)
-	var schemaFirewallRuleRequestList []schema.FirewallRuleRequest
 	if source.Rules != nil {
-		schemaFirewallRuleRequestList = make([]schema.FirewallRuleRequest, len(source.Rules))
+		schemaFirewallCreateRequest.Rules = make([]schema.FirewallRuleRequest, len(source.Rules))
 		for i := 0; i < len(source.Rules); i++ {
-			schemaFirewallRuleRequestList[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i])
+			schemaFirewallCreateRequest.Rules[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i])
 		}
 	}
-	schemaFirewallCreateRequest.Rules = schemaFirewallRuleRequestList
-	var schemaFirewallResourceList []schema.FirewallResource
 	if source.ApplyTo != nil {
-		schemaFirewallResourceList = make([]schema.FirewallResource, len(source.ApplyTo))
+		schemaFirewallCreateRequest.ApplyTo = make([]schema.FirewallResource, len(source.ApplyTo))
 		for j := 0; j < len(source.ApplyTo); j++ {
-			schemaFirewallResourceList[j] = c.SchemaFromFirewallResource(source.ApplyTo[j])
+			schemaFirewallCreateRequest.ApplyTo[j] = c.SchemaFromFirewallResource(source.ApplyTo[j])
 		}
 	}
-	schemaFirewallCreateRequest.ApplyTo = schemaFirewallResourceList
 	return schemaFirewallCreateRequest
 }
 func (c *converterImpl) SchemaFromFirewallResource(source FirewallResource) schema.FirewallResource {
@@ -646,14 +593,12 @@ func (c *converterImpl) SchemaFromFirewallResource(source FirewallResource) sche
 }
 func (c *converterImpl) SchemaFromFirewallSetRulesOpts(source FirewallSetRulesOpts) schema.FirewallActionSetRulesRequest {
 	var schemaFirewallActionSetRulesRequest schema.FirewallActionSetRulesRequest
-	var schemaFirewallRuleRequestList []schema.FirewallRuleRequest
 	if source.Rules != nil {
-		schemaFirewallRuleRequestList = make([]schema.FirewallRuleRequest, len(source.Rules))
+		schemaFirewallActionSetRulesRequest.Rules = make([]schema.FirewallRuleRequest, len(source.Rules))
 		for i := 0; i < len(source.Rules); i++ {
-			schemaFirewallRuleRequestList[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i])
+			schemaFirewallActionSetRulesRequest.Rules[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i])
 		}
 	}
-	schemaFirewallActionSetRulesRequest.Rules = schemaFirewallRuleRequestList
 	return schemaFirewallActionSetRulesRequest
 }
 func (c *converterImpl) SchemaFromFloatingIP(source *FloatingIP) schema.FloatingIP {
@@ -685,12 +630,10 @@ func (c *converterImpl) SchemaFromISO(source *ISO) schema.ISO {
 		schemaISO2.Name = (*source).Name
 		schemaISO2.Description = (*source).Description
 		schemaISO2.Type = string((*source).Type)
-		var pString *string
 		if (*source).Architecture != nil {
 			xstring := string(*(*source).Architecture)
-			pString = &xstring
+			schemaISO2.Architecture = &xstring
 		}
-		schemaISO2.Architecture = pString
 		schemaISO2.DeprecatableResource = c.hcloudDeprecatableResourceToSchemaDeprecatableResource((*source).DeprecatableResource)
 		schemaISO = schemaISO2
 	}
@@ -710,35 +653,29 @@ func (c *converterImpl) SchemaFromLoadBalancer(source *LoadBalancer) schema.Load
 		schemaLoadBalancer2.ID = (*source).ID
 		schemaLoadBalancer2.Name = (*source).Name
 		schemaLoadBalancer2.PublicNet = c.hcloudLoadBalancerPublicNetToSchemaLoadBalancerPublicNet((*source).PublicNet)
-		var schemaLoadBalancerPrivateNetList []schema.LoadBalancerPrivateNet
 		if (*source).PrivateNet != nil {
-			schemaLoadBalancerPrivateNetList = make([]schema.LoadBalancerPrivateNet, len((*source).PrivateNet))
+			schemaLoadBalancer2.PrivateNet = make([]schema.LoadBalancerPrivateNet, len((*source).PrivateNet))
 			for i := 0; i < len((*source).PrivateNet); i++ {
-				schemaLoadBalancerPrivateNetList[i] = c.hcloudLoadBalancerPrivateNetToSchemaLoadBalancerPrivateNet((*source).PrivateNet[i])
+				schemaLoadBalancer2.PrivateNet[i] = c.hcloudLoadBalancerPrivateNetToSchemaLoadBalancerPrivateNet((*source).PrivateNet[i])
 			}
 		}
-		schemaLoadBalancer2.PrivateNet = schemaLoadBalancerPrivateNetList
 		schemaLoadBalancer2.Location = c.SchemaFromLocation((*source).Location)
 		schemaLoadBalancer2.LoadBalancerType = c.SchemaFromLoadBalancerType((*source).LoadBalancerType)
 		schemaLoadBalancer2.Protection = c.hcloudLoadBalancerProtectionToSchemaLoadBalancerProtection((*source).Protection)
 		schemaLoadBalancer2.Labels = (*source).Labels
 		schemaLoadBalancer2.Created = c.timeTimeToTimeTime((*source).Created)
-		var schemaLoadBalancerServiceList []schema.LoadBalancerService
 		if (*source).Services != nil {
-			schemaLoadBalancerServiceList = make([]schema.LoadBalancerService, len((*source).Services))
+			schemaLoadBalancer2.Services = make([]schema.LoadBalancerService, len((*source).Services))
 			for j := 0; j < len((*source).Services); j++ {
-				schemaLoadBalancerServiceList[j] = c.SchemaFromLoadBalancerService((*source).Services[j])
+				schemaLoadBalancer2.Services[j] = c.SchemaFromLoadBalancerService((*source).Services[j])
 			}
 		}
-		schemaLoadBalancer2.Services = schemaLoadBalancerServiceList
-		var schemaLoadBalancerTargetList []schema.LoadBalancerTarget
 		if (*source).Targets != nil {
-			schemaLoadBalancerTargetList = make([]schema.LoadBalancerTarget, len((*source).Targets))
+			schemaLoadBalancer2.Targets = make([]schema.LoadBalancerTarget, len((*source).Targets))
 			for k := 0; k < len((*source).Targets); k++ {
-				schemaLoadBalancerTargetList[k] = c.SchemaFromLoadBalancerTarget((*source).Targets[k])
+				schemaLoadBalancer2.Targets[k] = c.SchemaFromLoadBalancerTarget((*source).Targets[k])
 			}
 		}
-		schemaLoadBalancer2.Targets = schemaLoadBalancerTargetList
 		schemaLoadBalancer2.Algorithm = c.hcloudLoadBalancerAlgorithmToSchemaLoadBalancerAlgorithm((*source).Algorithm)
 		schemaLoadBalancer2.IncludedTraffic = (*source).IncludedTraffic
 		schemaLoadBalancer2.OutgoingTraffic = mapZeroUint64ToNil((*source).OutgoingTraffic)
@@ -760,27 +697,23 @@ func (c *converterImpl) SchemaFromLoadBalancerAddServiceOpts(source LoadBalancer
 func (c *converterImpl) SchemaFromLoadBalancerCreateOpts(source LoadBalancerCreateOpts) schema.LoadBalancerCreateRequest {
 	var schemaLoadBalancerCreateRequest schema.LoadBalancerCreateRequest
 	schemaLoadBalancerCreateRequest.Name = source.Name
-	schemaLoadBalancerCreateRequest.LoadBalancerType = anyFromLoadBalancerType(source.LoadBalancerType)
+	schemaLoadBalancerCreateRequest.LoadBalancerType = c.pHcloudLoadBalancerTypeToSchemaIDOrName(source.LoadBalancerType)
 	schemaLoadBalancerCreateRequest.Algorithm = c.pHcloudLoadBalancerAlgorithmToPSchemaLoadBalancerCreateRequestAlgorithm(source.Algorithm)
 	schemaLoadBalancerCreateRequest.Location = c.pHcloudLocationToPString(source.Location)
 	schemaLoadBalancerCreateRequest.NetworkZone = stringPtrFromNetworkZone(source.NetworkZone)
 	schemaLoadBalancerCreateRequest.Labels = stringMapToStringMapPtr(source.Labels)
-	var schemaLoadBalancerCreateRequestTargetList []schema.LoadBalancerCreateRequestTarget
 	if source.Targets != nil {
-		schemaLoadBalancerCreateRequestTargetList = make([]schema.LoadBalancerCreateRequestTarget, len(source.Targets))
+		schemaLoadBalancerCreateRequest.Targets = make([]schema.LoadBalancerCreateRequestTarget, len(source.Targets))
 		for i := 0; i < len(source.Targets); i++ {
-			schemaLoadBalancerCreateRequestTargetList[i] = c.hcloudLoadBalancerCreateOptsTargetToSchemaLoadBalancerCreateRequestTarget(source.Targets[i])
+			schemaLoadBalancerCreateRequest.Targets[i] = c.hcloudLoadBalancerCreateOptsTargetToSchemaLoadBalancerCreateRequestTarget(source.Targets[i])
 		}
 	}
-	schemaLoadBalancerCreateRequest.Targets = schemaLoadBalancerCreateRequestTargetList
-	var schemaLoadBalancerCreateRequestServiceList []schema.LoadBalancerCreateRequestService
 	if source.Services != nil {
-		schemaLoadBalancerCreateRequestServiceList = make([]schema.LoadBalancerCreateRequestService, len(source.Services))
+		schemaLoadBalancerCreateRequest.Services = make([]schema.LoadBalancerCreateRequestService, len(source.Services))
 		for j := 0; j < len(source.Services); j++ {
-			schemaLoadBalancerCreateRequestServiceList[j] = c.hcloudLoadBalancerCreateOptsServiceToSchemaLoadBalancerCreateRequestService(source.Services[j])
+			schemaLoadBalancerCreateRequest.Services[j] = c.hcloudLoadBalancerCreateOptsServiceToSchemaLoadBalancerCreateRequestService(source.Services[j])
 		}
 	}
-	schemaLoadBalancerCreateRequest.Services = schemaLoadBalancerCreateRequestServiceList
 	schemaLoadBalancerCreateRequest.PublicInterface = source.PublicInterface
 	schemaLoadBalancerCreateRequest.Network = c.pHcloudNetworkToPInt64(source.Network)
 	return schemaLoadBalancerCreateRequest
@@ -791,11 +724,9 @@ func (c *converterImpl) SchemaFromLoadBalancerCreateOptsTargetServer(source Load
 	if source.Server != nil {
 		pInt64 = &source.Server.ID
 	}
-	var xint64 int64
 	if pInt64 != nil {
-		xint64 = *pInt64
+		schemaLoadBalancerCreateRequestTargetServer.ID = *pInt64
 	}
-	schemaLoadBalancerCreateRequestTargetServer.ID = xint64
 	return schemaLoadBalancerCreateRequestTargetServer
 }
 func (c *converterImpl) SchemaFromLoadBalancerServerTarget(source LoadBalancerTargetServer) schema.LoadBalancerTargetServer {
@@ -829,23 +760,19 @@ func (c *converterImpl) SchemaFromLoadBalancerTarget(source LoadBalancerTarget)
 	schemaLoadBalancerTarget.Server = c.pHcloudLoadBalancerTargetServerToPSchemaLoadBalancerTargetServer(source.Server)
 	schemaLoadBalancerTarget.LabelSelector = c.pHcloudLoadBalancerTargetLabelSelectorToPSchemaLoadBalancerTargetLabelSelector(source.LabelSelector)
 	schemaLoadBalancerTarget.IP = c.pHcloudLoadBalancerTargetIPToPSchemaLoadBalancerTargetIP(source.IP)
-	var schemaLoadBalancerTargetHealthStatusList []schema.LoadBalancerTargetHealthStatus
 	if source.HealthStatus != nil {
-		schemaLoadBalancerTargetHealthStatusList = make([]schema.LoadBalancerTargetHealthStatus, len(source.HealthStatus))
+		schemaLoadBalancerTarget.HealthStatus = make([]schema.LoadBalancerTargetHealthStatus, len(source.HealthStatus))
 		for i := 0; i < len(source.HealthStatus); i++ {
-			schemaLoadBalancerTargetHealthStatusList[i] = c.SchemaFromLoadBalancerTargetHealthStatus(source.HealthStatus[i])
+			schemaLoadBalancerTarget.HealthStatus[i] = c.SchemaFromLoadBalancerTargetHealthStatus(source.HealthStatus[i])
 		}
 	}
-	schemaLoadBalancerTarget.HealthStatus = schemaLoadBalancerTargetHealthStatusList
 	schemaLoadBalancerTarget.UsePrivateIP = source.UsePrivateIP
-	var schemaLoadBalancerTargetList []schema.LoadBalancerTarget
 	if source.Targets != nil {
-		schemaLoadBalancerTargetList = make([]schema.LoadBalancerTarget, len(source.Targets))
+		schemaLoadBalancerTarget.Targets = make([]schema.LoadBalancerTarget, len(source.Targets))
 		for j := 0; j < len(source.Targets); j++ {
-			schemaLoadBalancerTargetList[j] = c.SchemaFromLoadBalancerTarget(source.Targets[j])
+			schemaLoadBalancerTarget.Targets[j] = c.SchemaFromLoadBalancerTarget(source.Targets[j])
 		}
 	}
-	schemaLoadBalancerTarget.Targets = schemaLoadBalancerTargetList
 	return schemaLoadBalancerTarget
 }
 func (c *converterImpl) SchemaFromLoadBalancerTargetHealthStatus(source LoadBalancerTargetHealthStatus) schema.LoadBalancerTargetHealthStatus {
@@ -865,14 +792,12 @@ func (c *converterImpl) SchemaFromLoadBalancerType(source *LoadBalancerType) sch
 		schemaLoadBalancerType2.MaxServices = (*source).MaxServices
 		schemaLoadBalancerType2.MaxTargets = (*source).MaxTargets
 		schemaLoadBalancerType2.MaxAssignedCertificates = (*source).MaxAssignedCertificates
-		var schemaPricingLoadBalancerTypePriceList []schema.PricingLoadBalancerTypePrice
 		if (*source).Pricings != nil {
-			schemaPricingLoadBalancerTypePriceList = make([]schema.PricingLoadBalancerTypePrice, len((*source).Pricings))
+			schemaLoadBalancerType2.Prices = make([]schema.PricingLoadBalancerTypePrice, len((*source).Pricings))
 			for i := 0; i < len((*source).Pricings); i++ {
-				schemaPricingLoadBalancerTypePriceList[i] = c.SchemaFromLoadBalancerTypeLocationPricing((*source).Pricings[i])
+				schemaLoadBalancerType2.Prices[i] = c.SchemaFromLoadBalancerTypeLocationPricing((*source).Pricings[i])
 			}
 		}
-		schemaLoadBalancerType2.Prices = schemaPricingLoadBalancerTypePriceList
 		schemaLoadBalancerType2.Deprecated = (*source).Deprecated
 		schemaLoadBalancerType = schemaLoadBalancerType2
 	}
@@ -883,6 +808,8 @@ func (c *converterImpl) SchemaFromLoadBalancerTypeLocationPricing(source LoadBal
 	schemaPricingLoadBalancerTypePrice.Location = c.pHcloudLocationToString(source.Location)
 	schemaPricingLoadBalancerTypePrice.PriceHourly = c.hcloudPriceToSchemaPrice(source.Hourly)
 	schemaPricingLoadBalancerTypePrice.PriceMonthly = c.hcloudPriceToSchemaPrice(source.Monthly)
+	schemaPricingLoadBalancerTypePrice.IncludedTraffic = source.IncludedTraffic
+	schemaPricingLoadBalancerTypePrice.PricePerTBTraffic = c.hcloudPriceToSchemaPrice(source.PerTBTraffic)
 	return schemaPricingLoadBalancerTypePrice
 }
 func (c *converterImpl) SchemaFromLoadBalancerUpdateServiceOpts(source LoadBalancerUpdateServiceOpts) schema.LoadBalancerActionUpdateServiceRequest {
@@ -918,30 +845,30 @@ func (c *converterImpl) SchemaFromNetwork(source *Network) schema.Network {
 		schemaNetwork2.Name = (*source).Name
 		schemaNetwork2.Created = c.timeTimeToTimeTime((*source).Created)
 		schemaNetwork2.IPRange = c.pNetIPNetToString((*source).IPRange)
-		var schemaNetworkSubnetList []schema.NetworkSubnet
 		if (*source).Subnets != nil {
-			schemaNetworkSubnetList = make([]schema.NetworkSubnet, len((*source).Subnets))
+			schemaNetwork2.Subnets = make([]schema.NetworkSubnet, len((*source).Subnets))
 			for i := 0; i < len((*source).Subnets); i++ {
-				schemaNetworkSubnetList[i] = c.SchemaFromNetworkSubnet((*source).Subnets[i])
+				schemaNetwork2.Subnets[i] = c.SchemaFromNetworkSubnet((*source).Subnets[i])
 			}
 		}
-		schemaNetwork2.Subnets = schemaNetworkSubnetList
-		var schemaNetworkRouteList []schema.NetworkRoute
 		if (*source).Routes != nil {
-			schemaNetworkRouteList = make([]schema.NetworkRoute, len((*source).Routes))
+			schemaNetwork2.Routes = make([]schema.NetworkRoute, len((*source).Routes))
 			for j := 0; j < len((*source).Routes); j++ {
-				schemaNetworkRouteList[j] = c.SchemaFromNetworkRoute((*source).Routes[j])
+				schemaNetwork2.Routes[j] = c.SchemaFromNetworkRoute((*source).Routes[j])
 			}
 		}
-		schemaNetwork2.Routes = schemaNetworkRouteList
-		var int64List []int64
 		if (*source).Servers != nil {
-			int64List = make([]int64, len((*source).Servers))
+			schemaNetwork2.Servers = make([]int64, len((*source).Servers))
 			for k := 0; k < len((*source).Servers); k++ {
-				int64List[k] = c.pHcloudServerToInt64((*source).Servers[k])
+				schemaNetwork2.Servers[k] = c.pHcloudServerToInt64((*source).Servers[k])
+			}
+		}
+		if (*source).LoadBalancers != nil {
+			schemaNetwork2.LoadBalancers = make([]int64, len((*source).LoadBalancers))
+			for l := 0; l < len((*source).LoadBalancers); l++ {
+				schemaNetwork2.LoadBalancers[l] = c.pHcloudLoadBalancerToInt64((*source).LoadBalancers[l])
 			}
 		}
-		schemaNetwork2.Servers = int64List
 		schemaNetwork2.Protection = c.hcloudNetworkProtectionToSchemaNetworkProtection((*source).Protection)
 		schemaNetwork2.Labels = (*source).Labels
 		schemaNetwork2.ExposeRoutesToVSwitch = (*source).ExposeRoutesToVSwitch
@@ -1001,40 +928,32 @@ func (c *converterImpl) SchemaFromPricing(source Pricing) schema.Pricing {
 	schemaPricing.VATRate = source.Image.PerGBMonth.VATRate
 	schemaPricing.Image = c.schemaFromImagePricing(source.Image)
 	schemaPricing.FloatingIP = c.schemaFromFloatingIPPricing(source.FloatingIP)
-	var schemaPricingFloatingIPTypeList []schema.PricingFloatingIPType
 	if source.FloatingIPs != nil {
-		schemaPricingFloatingIPTypeList = make([]schema.PricingFloatingIPType, len(source.FloatingIPs))
+		schemaPricing.FloatingIPs = make([]schema.PricingFloatingIPType, len(source.FloatingIPs))
 		for i := 0; i < len(source.FloatingIPs); i++ {
-			schemaPricingFloatingIPTypeList[i] = c.schemaFromFloatingIPTypePricing(source.FloatingIPs[i])
+			schemaPricing.FloatingIPs[i] = c.schemaFromFloatingIPTypePricing(source.FloatingIPs[i])
 		}
 	}
-	schemaPricing.FloatingIPs = schemaPricingFloatingIPTypeList
-	var schemaPricingPrimaryIPList []schema.PricingPrimaryIP
 	if source.PrimaryIPs != nil {
-		schemaPricingPrimaryIPList = make([]schema.PricingPrimaryIP, len(source.PrimaryIPs))
+		schemaPricing.PrimaryIPs = make([]schema.PricingPrimaryIP, len(source.PrimaryIPs))
 		for j := 0; j < len(source.PrimaryIPs); j++ {
-			schemaPricingPrimaryIPList[j] = c.schemaFromPrimaryIPPricing(source.PrimaryIPs[j])
+			schemaPricing.PrimaryIPs[j] = c.schemaFromPrimaryIPPricing(source.PrimaryIPs[j])
 		}
 	}
-	schemaPricing.PrimaryIPs = schemaPricingPrimaryIPList
 	schemaPricing.Traffic = c.schemaFromTrafficPricing(source.Traffic)
 	schemaPricing.ServerBackup = c.hcloudServerBackupPricingToSchemaPricingServerBackup(source.ServerBackup)
-	var schemaPricingServerTypeList []schema.PricingServerType
 	if source.ServerTypes != nil {
-		schemaPricingServerTypeList = make([]schema.PricingServerType, len(source.ServerTypes))
+		schemaPricing.ServerTypes = make([]schema.PricingServerType, len(source.ServerTypes))
 		for k := 0; k < len(source.ServerTypes); k++ {
-			schemaPricingServerTypeList[k] = c.schemaFromServerTypePricing(source.ServerTypes[k])
+			schemaPricing.ServerTypes[k] = c.schemaFromServerTypePricing(source.ServerTypes[k])
 		}
 	}
-	schemaPricing.ServerTypes = schemaPricingServerTypeList
-	var schemaPricingLoadBalancerTypeList []schema.PricingLoadBalancerType
 	if source.LoadBalancerTypes != nil {
-		schemaPricingLoadBalancerTypeList = make([]schema.PricingLoadBalancerType, len(source.LoadBalancerTypes))
+		schemaPricing.LoadBalancerTypes = make([]schema.PricingLoadBalancerType, len(source.LoadBalancerTypes))
 		for l := 0; l < len(source.LoadBalancerTypes); l++ {
-			schemaPricingLoadBalancerTypeList[l] = c.schemaFromLoadBalancerTypePricing(source.LoadBalancerTypes[l])
+			schemaPricing.LoadBalancerTypes[l] = c.schemaFromLoadBalancerTypePricing(source.LoadBalancerTypes[l])
 		}
 	}
-	schemaPricing.LoadBalancerTypes = schemaPricingLoadBalancerTypeList
 	schemaPricing.Volume = c.schemaFromVolumePricing(source.Volume)
 	return schemaPricing
 }
@@ -1059,6 +978,44 @@ func (c *converterImpl) SchemaFromPrimaryIP(source *PrimaryIP) schema.PrimaryIP
 	}
 	return schemaPrimaryIP
 }
+func (c *converterImpl) SchemaFromPrimaryIPAssignOpts(source PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest {
+	var schemaPrimaryIPActionAssignRequest schema.PrimaryIPActionAssignRequest
+	schemaPrimaryIPActionAssignRequest.AssigneeID = source.AssigneeID
+	schemaPrimaryIPActionAssignRequest.AssigneeType = source.AssigneeType
+	return schemaPrimaryIPActionAssignRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPChangeDNSPtrOpts(source PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest {
+	var schemaPrimaryIPActionChangeDNSPtrRequest schema.PrimaryIPActionChangeDNSPtrRequest
+	schemaPrimaryIPActionChangeDNSPtrRequest.IP = source.IP
+	pString := source.DNSPtr
+	schemaPrimaryIPActionChangeDNSPtrRequest.DNSPtr = &pString
+	return schemaPrimaryIPActionChangeDNSPtrRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPChangeProtectionOpts(source PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest {
+	var schemaPrimaryIPActionChangeProtectionRequest schema.PrimaryIPActionChangeProtectionRequest
+	schemaPrimaryIPActionChangeProtectionRequest.Delete = source.Delete
+	return schemaPrimaryIPActionChangeProtectionRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPCreateOpts(source PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest {
+	var schemaPrimaryIPCreateRequest schema.PrimaryIPCreateRequest
+	schemaPrimaryIPCreateRequest.Name = source.Name
+	schemaPrimaryIPCreateRequest.Type = string(source.Type)
+	schemaPrimaryIPCreateRequest.AssigneeType = source.AssigneeType
+	schemaPrimaryIPCreateRequest.AssigneeID = source.AssigneeID
+	schemaPrimaryIPCreateRequest.Labels = source.Labels
+	schemaPrimaryIPCreateRequest.AutoDelete = source.AutoDelete
+	schemaPrimaryIPCreateRequest.Datacenter = source.Datacenter
+	return schemaPrimaryIPCreateRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPUpdateOpts(source PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest {
+	var schemaPrimaryIPUpdateRequest schema.PrimaryIPUpdateRequest
+	schemaPrimaryIPUpdateRequest.Name = source.Name
+	if source.Labels != nil {
+		schemaPrimaryIPUpdateRequest.Labels = (*source.Labels)
+	}
+	schemaPrimaryIPUpdateRequest.AutoDelete = source.AutoDelete
+	return schemaPrimaryIPUpdateRequest
+}
 func (c *converterImpl) SchemaFromSSHKey(source *SSHKey) schema.SSHKey {
 	var schemaSSHKey schema.SSHKey
 	if source != nil {
@@ -1082,14 +1039,12 @@ func (c *converterImpl) SchemaFromServer(source *Server) schema.Server {
 		schemaServer2.Status = string((*source).Status)
 		schemaServer2.Created = c.timeTimeToTimeTime((*source).Created)
 		schemaServer2.PublicNet = c.SchemaFromServerPublicNet((*source).PublicNet)
-		var schemaServerPrivateNetList []schema.ServerPrivateNet
 		if (*source).PrivateNet != nil {
-			schemaServerPrivateNetList = make([]schema.ServerPrivateNet, len((*source).PrivateNet))
+			schemaServer2.PrivateNet = make([]schema.ServerPrivateNet, len((*source).PrivateNet))
 			for i := 0; i < len((*source).PrivateNet); i++ {
-				schemaServerPrivateNetList[i] = c.SchemaFromServerPrivateNet((*source).PrivateNet[i])
+				schemaServer2.PrivateNet[i] = c.SchemaFromServerPrivateNet((*source).PrivateNet[i])
 			}
 		}
-		schemaServer2.PrivateNet = schemaServerPrivateNetList
 		schemaServer2.ServerType = c.SchemaFromServerType((*source).ServerType)
 		schemaServer2.IncludedTraffic = (*source).IncludedTraffic
 		schemaServer2.OutgoingTraffic = mapZeroUint64ToNil((*source).OutgoingTraffic)
@@ -1102,24 +1057,20 @@ func (c *converterImpl) SchemaFromServer(source *Server) schema.Server {
 		schemaServer2.Image = c.pHcloudImageToPSchemaImage((*source).Image)
 		schemaServer2.Protection = c.hcloudServerProtectionToSchemaServerProtection((*source).Protection)
 		schemaServer2.Labels = (*source).Labels
-		var int64List []int64
 		if (*source).Volumes != nil {
-			int64List = make([]int64, len((*source).Volumes))
+			schemaServer2.Volumes = make([]int64, len((*source).Volumes))
 			for j := 0; j < len((*source).Volumes); j++ {
-				int64List[j] = int64FromVolume((*source).Volumes[j])
+				schemaServer2.Volumes[j] = int64FromVolume((*source).Volumes[j])
 			}
 		}
-		schemaServer2.Volumes = int64List
 		schemaServer2.PrimaryDiskSize = (*source).PrimaryDiskSize
 		schemaServer2.PlacementGroup = c.pHcloudPlacementGroupToPSchemaPlacementGroup((*source).PlacementGroup)
-		var int64List2 []int64
 		if (*source).LoadBalancers != nil {
-			int64List2 = make([]int64, len((*source).LoadBalancers))
+			schemaServer2.LoadBalancers = make([]int64, len((*source).LoadBalancers))
 			for k := 0; k < len((*source).LoadBalancers); k++ {
-				int64List2[k] = c.pHcloudLoadBalancerToInt64((*source).LoadBalancers[k])
+				schemaServer2.LoadBalancers[k] = c.pHcloudLoadBalancerToInt64((*source).LoadBalancers[k])
 			}
 		}
-		schemaServer2.LoadBalancers = int64List2
 		schemaServer = schemaServer2
 	}
 	return schemaServer
@@ -1128,14 +1079,12 @@ func (c *converterImpl) SchemaFromServerPrivateNet(source ServerPrivateNet) sche
 	var schemaServerPrivateNet schema.ServerPrivateNet
 	schemaServerPrivateNet.Network = c.pHcloudNetworkToInt64(source.Network)
 	schemaServerPrivateNet.IP = stringFromIP(source.IP)
-	var stringList []string
 	if source.Aliases != nil {
-		stringList = make([]string, len(source.Aliases))
+		schemaServerPrivateNet.AliasIPs = make([]string, len(source.Aliases))
 		for i := 0; i < len(source.Aliases); i++ {
-			stringList[i] = stringFromIP(source.Aliases[i])
+			schemaServerPrivateNet.AliasIPs[i] = stringFromIP(source.Aliases[i])
 		}
 	}
-	schemaServerPrivateNet.AliasIPs = stringList
 	schemaServerPrivateNet.MACAddress = source.MACAddress
 	return schemaServerPrivateNet
 }
@@ -1143,22 +1092,18 @@ func (c *converterImpl) SchemaFromServerPublicNet(source ServerPublicNet) schema
 	var schemaServerPublicNet schema.ServerPublicNet
 	schemaServerPublicNet.IPv4 = c.SchemaFromServerPublicNetIPv4(source.IPv4)
 	schemaServerPublicNet.IPv6 = c.SchemaFromServerPublicNetIPv6(source.IPv6)
-	var int64List []int64
 	if source.FloatingIPs != nil {
-		int64List = make([]int64, len(source.FloatingIPs))
+		schemaServerPublicNet.FloatingIPs = make([]int64, len(source.FloatingIPs))
 		for i := 0; i < len(source.FloatingIPs); i++ {
-			int64List[i] = int64FromFloatingIP(source.FloatingIPs[i])
+			schemaServerPublicNet.FloatingIPs[i] = int64FromFloatingIP(source.FloatingIPs[i])
 		}
 	}
-	schemaServerPublicNet.FloatingIPs = int64List
-	var schemaServerFirewallList []schema.ServerFirewall
 	if source.Firewalls != nil {
-		schemaServerFirewallList = make([]schema.ServerFirewall, len(source.Firewalls))
+		schemaServerPublicNet.Firewalls = make([]schema.ServerFirewall, len(source.Firewalls))
 		for j := 0; j < len(source.Firewalls); j++ {
-			schemaServerFirewallList[j] = serverFirewallSchemaFromFirewallStatus(source.Firewalls[j])
+			schemaServerPublicNet.Firewalls[j] = serverFirewallSchemaFromFirewallStatus(source.Firewalls[j])
 		}
 	}
-	schemaServerPublicNet.Firewalls = schemaServerFirewallList
 	return schemaServerPublicNet
 }
 func (c *converterImpl) SchemaFromServerPublicNetIPv4(source ServerPublicNetIPv4) schema.ServerPublicNetIPv4 {
@@ -1191,14 +1136,12 @@ func (c *converterImpl) SchemaFromServerType(source *ServerType) schema.ServerTy
 		schemaServerType2.CPUType = string((*source).CPUType)
 		schemaServerType2.Architecture = string((*source).Architecture)
 		schemaServerType2.IncludedTraffic = (*source).IncludedTraffic
-		var schemaPricingServerTypePriceList []schema.PricingServerTypePrice
 		if (*source).Pricings != nil {
-			schemaPricingServerTypePriceList = make([]schema.PricingServerTypePrice, len((*source).Pricings))
+			schemaServerType2.Prices = make([]schema.PricingServerTypePrice, len((*source).Pricings))
 			for i := 0; i < len((*source).Pricings); i++ {
-				schemaPricingServerTypePriceList[i] = c.schemaFromServerTypeLocationPricing((*source).Pricings[i])
+				schemaServerType2.Prices[i] = c.schemaFromServerTypeLocationPricing((*source).Pricings[i])
 			}
 		}
-		schemaServerType2.Prices = schemaPricingServerTypePriceList
 		schemaServerType2.Deprecated = isDeprecationNotNil((*source).DeprecatableResource.Deprecation)
 		schemaServerType2.DeprecatableResource = c.hcloudDeprecatableResourceToSchemaDeprecatableResource((*source).DeprecatableResource)
 		schemaServerType = schemaServerType2
@@ -1231,57 +1174,45 @@ func (c *converterImpl) ServerFromSchema(source schema.Server) *Server {
 	hcloudServer.Status = ServerStatus(source.Status)
 	hcloudServer.Created = c.timeTimeToTimeTime(source.Created)
 	hcloudServer.PublicNet = c.ServerPublicNetFromSchema(source.PublicNet)
-	var hcloudServerPrivateNetList []ServerPrivateNet
 	if source.PrivateNet != nil {
-		hcloudServerPrivateNetList = make([]ServerPrivateNet, len(source.PrivateNet))
+		hcloudServer.PrivateNet = make([]ServerPrivateNet, len(source.PrivateNet))
 		for i := 0; i < len(source.PrivateNet); i++ {
-			hcloudServerPrivateNetList[i] = c.ServerPrivateNetFromSchema(source.PrivateNet[i])
+			hcloudServer.PrivateNet[i] = c.ServerPrivateNetFromSchema(source.PrivateNet[i])
 		}
 	}
-	hcloudServer.PrivateNet = hcloudServerPrivateNetList
 	hcloudServer.ServerType = c.ServerTypeFromSchema(source.ServerType)
 	hcloudServer.Datacenter = c.DatacenterFromSchema(source.Datacenter)
 	hcloudServer.IncludedTraffic = source.IncludedTraffic
-	var xuint64 uint64
 	if source.OutgoingTraffic != nil {
-		xuint64 = *source.OutgoingTraffic
+		hcloudServer.OutgoingTraffic = *source.OutgoingTraffic
 	}
-	hcloudServer.OutgoingTraffic = xuint64
-	var xuint642 uint64
 	if source.IngoingTraffic != nil {
-		xuint642 = *source.IngoingTraffic
+		hcloudServer.IngoingTraffic = *source.IngoingTraffic
 	}
-	hcloudServer.IngoingTraffic = xuint642
-	var xstring string
 	if source.BackupWindow != nil {
-		xstring = *source.BackupWindow
+		hcloudServer.BackupWindow = *source.BackupWindow
 	}
-	hcloudServer.BackupWindow = xstring
 	hcloudServer.RescueEnabled = source.RescueEnabled
 	hcloudServer.Locked = source.Locked
 	hcloudServer.ISO = c.pSchemaISOToPHcloudISO(source.ISO)
 	hcloudServer.Image = c.pSchemaImageToPHcloudImage(source.Image)
 	hcloudServer.Protection = c.schemaServerProtectionToHcloudServerProtection(source.Protection)
 	hcloudServer.Labels = source.Labels
-	var pHcloudVolumeList []*Volume
 	if source.Volumes != nil {
-		pHcloudVolumeList = make([]*Volume, len(source.Volumes))
+		hcloudServer.Volumes = make([]*Volume, len(source.Volumes))
 		for j := 0; j < len(source.Volumes); j++ {
-			pHcloudVolumeList[j] = volumeFromInt64(source.Volumes[j])
+			hcloudServer.Volumes[j] = volumeFromInt64(source.Volumes[j])
 		}
 	}
-	hcloudServer.Volumes = pHcloudVolumeList
 	hcloudServer.PrimaryDiskSize = source.PrimaryDiskSize
 	hcloudServer.PlacementGroup = c.pSchemaPlacementGroupToPHcloudPlacementGroup(source.PlacementGroup)
-	var pHcloudLoadBalancerList []*LoadBalancer
 	if source.LoadBalancers != nil {
-		pHcloudLoadBalancerList = make([]*LoadBalancer, len(source.LoadBalancers))
+		hcloudServer.LoadBalancers = make([]*LoadBalancer, len(source.LoadBalancers))
 		for k := 0; k < len(source.LoadBalancers); k++ {
 			hcloudLoadBalancer := loadBalancerFromInt64(source.LoadBalancers[k])
-			pHcloudLoadBalancerList[k] = &hcloudLoadBalancer
+			hcloudServer.LoadBalancers[k] = &hcloudLoadBalancer
 		}
 	}
-	hcloudServer.LoadBalancers = pHcloudLoadBalancerList
 	return &hcloudServer
 }
 func (c *converterImpl) ServerMetricsFromSchema(source *schema.ServerGetMetricsResponse) (*ServerMetrics, error) {
@@ -1291,18 +1222,16 @@ func (c *converterImpl) ServerMetricsFromSchema(source *schema.ServerGetMetricsR
 		hcloudServerMetrics.Start = c.timeTimeToTimeTime((*source).Metrics.Start)
 		hcloudServerMetrics.End = c.timeTimeToTimeTime((*source).Metrics.End)
 		hcloudServerMetrics.Step = (*source).Metrics.Step
-		var mapStringHcloudServerMetricsValueList map[string][]ServerMetricsValue
 		if (*source).Metrics.TimeSeries != nil {
-			mapStringHcloudServerMetricsValueList = make(map[string][]ServerMetricsValue, len((*source).Metrics.TimeSeries))
+			hcloudServerMetrics.TimeSeries = make(map[string][]ServerMetricsValue, len((*source).Metrics.TimeSeries))
 			for key, value := range (*source).Metrics.TimeSeries {
 				hcloudServerMetricsValueList, err := serverMetricsTimeSeriesFromSchema(value)
 				if err != nil {
 					return nil, err
 				}
-				mapStringHcloudServerMetricsValueList[key] = hcloudServerMetricsValueList
+				hcloudServerMetrics.TimeSeries[key] = hcloudServerMetricsValueList
 			}
 		}
-		hcloudServerMetrics.TimeSeries = mapStringHcloudServerMetricsValueList
 		pHcloudServerMetrics = &hcloudServerMetrics
 	}
 	return pHcloudServerMetrics, nil
@@ -1312,14 +1241,12 @@ func (c *converterImpl) ServerPrivateNetFromSchema(source schema.ServerPrivateNe
 	hcloudNetwork := networkFromInt64(source.Network)
 	hcloudServerPrivateNet.Network = &hcloudNetwork
 	hcloudServerPrivateNet.IP = ipFromString(source.IP)
-	var netIPList []net.IP
 	if source.AliasIPs != nil {
-		netIPList = make([]net.IP, len(source.AliasIPs))
+		hcloudServerPrivateNet.Aliases = make([]net.IP, len(source.AliasIPs))
 		for i := 0; i < len(source.AliasIPs); i++ {
-			netIPList[i] = ipFromString(source.AliasIPs[i])
+			hcloudServerPrivateNet.Aliases[i] = ipFromString(source.AliasIPs[i])
 		}
 	}
-	hcloudServerPrivateNet.Aliases = netIPList
 	hcloudServerPrivateNet.MACAddress = source.MACAddress
 	return hcloudServerPrivateNet
 }
@@ -1327,22 +1254,18 @@ func (c *converterImpl) ServerPublicNetFromSchema(source schema.ServerPublicNet)
 	var hcloudServerPublicNet ServerPublicNet
 	hcloudServerPublicNet.IPv4 = c.ServerPublicNetIPv4FromSchema(source.IPv4)
 	hcloudServerPublicNet.IPv6 = c.ServerPublicNetIPv6FromSchema(source.IPv6)
-	var pHcloudFloatingIPList []*FloatingIP
 	if source.FloatingIPs != nil {
-		pHcloudFloatingIPList = make([]*FloatingIP, len(source.FloatingIPs))
+		hcloudServerPublicNet.FloatingIPs = make([]*FloatingIP, len(source.FloatingIPs))
 		for i := 0; i < len(source.FloatingIPs); i++ {
-			pHcloudFloatingIPList[i] = floatingIPFromInt64(source.FloatingIPs[i])
+			hcloudServerPublicNet.FloatingIPs[i] = floatingIPFromInt64(source.FloatingIPs[i])
 		}
 	}
-	hcloudServerPublicNet.FloatingIPs = pHcloudFloatingIPList
-	var pHcloudServerFirewallStatusList []*ServerFirewallStatus
 	if source.Firewalls != nil {
-		pHcloudServerFirewallStatusList = make([]*ServerFirewallStatus, len(source.Firewalls))
+		hcloudServerPublicNet.Firewalls = make([]*ServerFirewallStatus, len(source.Firewalls))
 		for j := 0; j < len(source.Firewalls); j++ {
-			pHcloudServerFirewallStatusList[j] = firewallStatusFromSchemaServerFirewall(source.Firewalls[j])
+			hcloudServerPublicNet.Firewalls[j] = firewallStatusFromSchemaServerFirewall(source.Firewalls[j])
 		}
 	}
-	hcloudServerPublicNet.Firewalls = pHcloudServerFirewallStatusList
 	return hcloudServerPublicNet
 }
 func (c *converterImpl) ServerPublicNetIPv4FromSchema(source schema.ServerPublicNetIPv4) ServerPublicNetIPv4 {
@@ -1374,14 +1297,12 @@ func (c *converterImpl) ServerTypeFromSchema(source schema.ServerType) *ServerTy
 	hcloudServerType.CPUType = CPUType(source.CPUType)
 	hcloudServerType.Architecture = Architecture(source.Architecture)
 	hcloudServerType.IncludedTraffic = source.IncludedTraffic
-	var hcloudServerTypeLocationPricingList []ServerTypeLocationPricing
 	if source.Prices != nil {
-		hcloudServerTypeLocationPricingList = make([]ServerTypeLocationPricing, len(source.Prices))
+		hcloudServerType.Pricings = make([]ServerTypeLocationPricing, len(source.Prices))
 		for i := 0; i < len(source.Prices); i++ {
-			hcloudServerTypeLocationPricingList[i] = c.serverTypePricingFromSchema(source.Prices[i])
+			hcloudServerType.Pricings[i] = c.serverTypePricingFromSchema(source.Prices[i])
 		}
 	}
-	hcloudServerType.Pricings = hcloudServerTypeLocationPricingList
 	hcloudServerType.DeprecatableResource = c.schemaDeprecatableResourceToHcloudDeprecatableResource(source.DeprecatableResource)
 	return &hcloudServerType
 }
@@ -1390,12 +1311,10 @@ func (c *converterImpl) VolumeFromSchema(source schema.Volume) *Volume {
 	hcloudVolume.ID = source.ID
 	hcloudVolume.Name = source.Name
 	hcloudVolume.Status = VolumeStatus(source.Status)
-	var pHcloudServer *Server
 	if source.Server != nil {
 		hcloudServer := serverFromInt64(*source.Server)
-		pHcloudServer = &hcloudServer
+		hcloudVolume.Server = &hcloudServer
 	}
-	hcloudVolume.Server = pHcloudServer
 	hcloudVolume.Location = c.LocationFromSchema(source.Location)
 	hcloudVolume.Size = source.Size
 	hcloudVolume.Format = source.Format
@@ -1416,30 +1335,24 @@ func (c *converterImpl) hcloudCertificateUsedByRefToSchemaCertificateUsedByRef(s
 }
 func (c *converterImpl) hcloudDatacenterServerTypesToSchemaDatacenterServerTypes(source DatacenterServerTypes) schema.DatacenterServerTypes {
 	var schemaDatacenterServerTypes schema.DatacenterServerTypes
-	var int64List []int64
 	if source.Supported != nil {
-		int64List = make([]int64, len(source.Supported))
+		schemaDatacenterServerTypes.Supported = make([]int64, len(source.Supported))
 		for i := 0; i < len(source.Supported); i++ {
-			int64List[i] = int64FromServerType(source.Supported[i])
+			schemaDatacenterServerTypes.Supported[i] = int64FromServerType(source.Supported[i])
 		}
 	}
-	schemaDatacenterServerTypes.Supported = int64List
-	var int64List2 []int64
 	if source.AvailableForMigration != nil {
-		int64List2 = make([]int64, len(source.AvailableForMigration))
+		schemaDatacenterServerTypes.AvailableForMigration = make([]int64, len(source.AvailableForMigration))
 		for j := 0; j < len(source.AvailableForMigration); j++ {
-			int64List2[j] = int64FromServerType(source.AvailableForMigration[j])
+			schemaDatacenterServerTypes.AvailableForMigration[j] = int64FromServerType(source.AvailableForMigration[j])
 		}
 	}
-	schemaDatacenterServerTypes.AvailableForMigration = int64List2
-	var int64List3 []int64
 	if source.Available != nil {
-		int64List3 = make([]int64, len(source.Available))
+		schemaDatacenterServerTypes.Available = make([]int64, len(source.Available))
 		for k := 0; k < len(source.Available); k++ {
-			int64List3[k] = int64FromServerType(source.Available[k])
+			schemaDatacenterServerTypes.Available[k] = int64FromServerType(source.Available[k])
 		}
 	}
-	schemaDatacenterServerTypes.Available = int64List3
 	return schemaDatacenterServerTypes
 }
 func (c *converterImpl) hcloudDeprecatableResourceToSchemaDeprecatableResource(source DeprecatableResource) schema.DeprecatableResource {
@@ -1450,22 +1363,18 @@ func (c *converterImpl) hcloudDeprecatableResourceToSchemaDeprecatableResource(s
 func (c *converterImpl) hcloudFirewallRuleToSchemaFirewallRule(source FirewallRule) schema.FirewallRule {
 	var schemaFirewallRule schema.FirewallRule
 	schemaFirewallRule.Direction = string(source.Direction)
-	var stringList []string
 	if source.SourceIPs != nil {
-		stringList = make([]string, len(source.SourceIPs))
+		schemaFirewallRule.SourceIPs = make([]string, len(source.SourceIPs))
 		for i := 0; i < len(source.SourceIPs); i++ {
-			stringList[i] = stringFromIPNet(source.SourceIPs[i])
+			schemaFirewallRule.SourceIPs[i] = stringFromIPNet(source.SourceIPs[i])
 		}
 	}
-	schemaFirewallRule.SourceIPs = stringList
-	var stringList2 []string
 	if source.DestinationIPs != nil {
-		stringList2 = make([]string, len(source.DestinationIPs))
+		schemaFirewallRule.DestinationIPs = make([]string, len(source.DestinationIPs))
 		for j := 0; j < len(source.DestinationIPs); j++ {
-			stringList2[j] = stringFromIPNet(source.DestinationIPs[j])
+			schemaFirewallRule.DestinationIPs[j] = stringFromIPNet(source.DestinationIPs[j])
 		}
 	}
-	schemaFirewallRule.DestinationIPs = stringList2
 	schemaFirewallRule.Protocol = string(source.Protocol)
 	schemaFirewallRule.Port = source.Port
 	schemaFirewallRule.Description = source.Description
@@ -1474,22 +1383,18 @@ func (c *converterImpl) hcloudFirewallRuleToSchemaFirewallRule(source FirewallRu
 func (c *converterImpl) hcloudFirewallRuleToSchemaFirewallRuleRequest(source FirewallRule) schema.FirewallRuleRequest {
 	var schemaFirewallRuleRequest schema.FirewallRuleRequest
 	schemaFirewallRuleRequest.Direction = string(source.Direction)
-	var stringList []string
 	if source.SourceIPs != nil {
-		stringList = make([]string, len(source.SourceIPs))
+		schemaFirewallRuleRequest.SourceIPs = make([]string, len(source.SourceIPs))
 		for i := 0; i < len(source.SourceIPs); i++ {
-			stringList[i] = stringFromIPNet(source.SourceIPs[i])
+			schemaFirewallRuleRequest.SourceIPs[i] = stringFromIPNet(source.SourceIPs[i])
 		}
 	}
-	schemaFirewallRuleRequest.SourceIPs = stringList
-	var stringList2 []string
 	if source.DestinationIPs != nil {
-		stringList2 = make([]string, len(source.DestinationIPs))
+		schemaFirewallRuleRequest.DestinationIPs = make([]string, len(source.DestinationIPs))
 		for j := 0; j < len(source.DestinationIPs); j++ {
-			stringList2[j] = stringFromIPNet(source.DestinationIPs[j])
+			schemaFirewallRuleRequest.DestinationIPs[j] = stringFromIPNet(source.DestinationIPs[j])
 		}
 	}
-	schemaFirewallRuleRequest.DestinationIPs = stringList2
 	schemaFirewallRuleRequest.Protocol = string(source.Protocol)
 	schemaFirewallRuleRequest.Port = source.Port
 	schemaFirewallRuleRequest.Description = source.Description
@@ -1563,14 +1468,12 @@ func (c *converterImpl) hcloudLoadBalancerServiceHTTPToPSchemaLoadBalancerServic
 	var schemaLoadBalancerServiceHTTP schema.LoadBalancerServiceHTTP
 	schemaLoadBalancerServiceHTTP.CookieName = source.CookieName
 	schemaLoadBalancerServiceHTTP.CookieLifetime = intSecondsFromDuration(source.CookieLifetime)
-	var int64List []int64
 	if source.Certificates != nil {
-		int64List = make([]int64, len(source.Certificates))
+		schemaLoadBalancerServiceHTTP.Certificates = make([]int64, len(source.Certificates))
 		for i := 0; i < len(source.Certificates); i++ {
-			int64List[i] = int64FromCertificate(source.Certificates[i])
+			schemaLoadBalancerServiceHTTP.Certificates[i] = int64FromCertificate(source.Certificates[i])
 		}
 	}
-	schemaLoadBalancerServiceHTTP.Certificates = int64List
 	schemaLoadBalancerServiceHTTP.RedirectHTTP = source.RedirectHTTP
 	schemaLoadBalancerServiceHTTP.StickySessions = source.StickySessions
 	return &schemaLoadBalancerServiceHTTP
@@ -1619,12 +1522,10 @@ func (c *converterImpl) intISOFromSchema(source schema.ISO) ISO {
 	hcloudISO.Name = source.Name
 	hcloudISO.Description = source.Description
 	hcloudISO.Type = ISOType(source.Type)
-	var pHcloudArchitecture *Architecture
 	if source.Architecture != nil {
 		hcloudArchitecture := Architecture(*source.Architecture)
-		pHcloudArchitecture = &hcloudArchitecture
+		hcloudISO.Architecture = &hcloudArchitecture
 	}
-	hcloudISO.Architecture = pHcloudArchitecture
 	var pTimeTime *time.Time
 	if source.DeprecatableResource.Deprecation != nil {
 		pTimeTime = &source.DeprecatableResource.Deprecation.UnavailableAfter
@@ -1712,12 +1613,10 @@ func (c *converterImpl) pHcloudISOToPSchemaISO(source *ISO) *schema.ISO {
 		schemaISO.Name = (*source).Name
 		schemaISO.Description = (*source).Description
 		schemaISO.Type = string((*source).Type)
-		var pString *string
 		if (*source).Architecture != nil {
 			xstring := string(*(*source).Architecture)
-			pString = &xstring
+			schemaISO.Architecture = &xstring
 		}
-		schemaISO.Architecture = pString
 		schemaISO.DeprecatableResource = c.hcloudDeprecatableResourceToSchemaDeprecatableResource((*source).DeprecatableResource)
 		pSchemaISO = &schemaISO
 	}
@@ -1736,12 +1635,10 @@ func (c *converterImpl) pHcloudLoadBalancerAddServiceOptsHTTPToPSchemaLoadBalanc
 	if source != nil {
 		var schemaLoadBalancerActionAddServiceRequestHTTP schema.LoadBalancerActionAddServiceRequestHTTP
 		schemaLoadBalancerActionAddServiceRequestHTTP.CookieName = (*source).CookieName
-		var pInt *int
 		if (*source).CookieLifetime != nil {
 			xint := intSecondsFromDuration(*(*source).CookieLifetime)
-			pInt = &xint
+			schemaLoadBalancerActionAddServiceRequestHTTP.CookieLifetime = &xint
 		}
-		schemaLoadBalancerActionAddServiceRequestHTTP.CookieLifetime = pInt
 		schemaLoadBalancerActionAddServiceRequestHTTP.Certificates = int64SlicePtrFromCertificatePtrSlice((*source).Certificates)
 		schemaLoadBalancerActionAddServiceRequestHTTP.RedirectHTTP = (*source).RedirectHTTP
 		schemaLoadBalancerActionAddServiceRequestHTTP.StickySessions = (*source).StickySessions
@@ -1768,18 +1665,14 @@ func (c *converterImpl) pHcloudLoadBalancerAddServiceOptsHealthCheckToPSchemaLoa
 		var schemaLoadBalancerActionAddServiceRequestHealthCheck schema.LoadBalancerActionAddServiceRequestHealthCheck
 		schemaLoadBalancerActionAddServiceRequestHealthCheck.Protocol = string((*source).Protocol)
 		schemaLoadBalancerActionAddServiceRequestHealthCheck.Port = (*source).Port
-		var pInt *int
 		if (*source).Interval != nil {
 			xint := intSecondsFromDuration(*(*source).Interval)
-			pInt = &xint
+			schemaLoadBalancerActionAddServiceRequestHealthCheck.Interval = &xint
 		}
-		schemaLoadBalancerActionAddServiceRequestHealthCheck.Interval = pInt
-		var pInt2 *int
 		if (*source).Timeout != nil {
 			xint2 := intSecondsFromDuration(*(*source).Timeout)
-			pInt2 = &xint2
+			schemaLoadBalancerActionAddServiceRequestHealthCheck.Timeout = &xint2
 		}
-		schemaLoadBalancerActionAddServiceRequestHealthCheck.Timeout = pInt2
 		schemaLoadBalancerActionAddServiceRequestHealthCheck.Retries = (*source).Retries
 		schemaLoadBalancerActionAddServiceRequestHealthCheck.HTTP = c.pHcloudLoadBalancerAddServiceOptsHealthCheckHTTPToPSchemaLoadBalancerActionAddServiceRequestHealthCheckHTTP((*source).HTTP)
 		pSchemaLoadBalancerActionAddServiceRequestHealthCheck = &schemaLoadBalancerActionAddServiceRequestHealthCheck
@@ -1800,12 +1693,10 @@ func (c *converterImpl) pHcloudLoadBalancerCreateOptsServiceHTTPToPSchemaLoadBal
 	if source != nil {
 		var schemaLoadBalancerCreateRequestServiceHTTP schema.LoadBalancerCreateRequestServiceHTTP
 		schemaLoadBalancerCreateRequestServiceHTTP.CookieName = (*source).CookieName
-		var pInt *int
 		if (*source).CookieLifetime != nil {
 			xint := intSecondsFromDuration(*(*source).CookieLifetime)
-			pInt = &xint
+			schemaLoadBalancerCreateRequestServiceHTTP.CookieLifetime = &xint
 		}
-		schemaLoadBalancerCreateRequestServiceHTTP.CookieLifetime = pInt
 		schemaLoadBalancerCreateRequestServiceHTTP.Certificates = int64SlicePtrFromCertificatePtrSlice((*source).Certificates)
 		schemaLoadBalancerCreateRequestServiceHTTP.RedirectHTTP = (*source).RedirectHTTP
 		schemaLoadBalancerCreateRequestServiceHTTP.StickySessions = (*source).StickySessions
@@ -1832,18 +1723,14 @@ func (c *converterImpl) pHcloudLoadBalancerCreateOptsServiceHealthCheckToPSchema
 		var schemaLoadBalancerCreateRequestServiceHealthCheck schema.LoadBalancerCreateRequestServiceHealthCheck
 		schemaLoadBalancerCreateRequestServiceHealthCheck.Protocol = string((*source).Protocol)
 		schemaLoadBalancerCreateRequestServiceHealthCheck.Port = (*source).Port
-		var pInt *int
 		if (*source).Interval != nil {
 			xint := intSecondsFromDuration(*(*source).Interval)
-			pInt = &xint
+			schemaLoadBalancerCreateRequestServiceHealthCheck.Interval = &xint
 		}
-		schemaLoadBalancerCreateRequestServiceHealthCheck.Interval = pInt
-		var pInt2 *int
 		if (*source).Timeout != nil {
 			xint2 := intSecondsFromDuration(*(*source).Timeout)
-			pInt2 = &xint2
+			schemaLoadBalancerCreateRequestServiceHealthCheck.Timeout = &xint2
 		}
-		schemaLoadBalancerCreateRequestServiceHealthCheck.Timeout = pInt2
 		schemaLoadBalancerCreateRequestServiceHealthCheck.Retries = (*source).Retries
 		schemaLoadBalancerCreateRequestServiceHealthCheck.HTTP = c.pHcloudLoadBalancerCreateOptsServiceHealthCheckHTTPToPSchemaLoadBalancerCreateRequestServiceHealthCheckHTTP((*source).HTTP)
 		pSchemaLoadBalancerCreateRequestServiceHealthCheck = &schemaLoadBalancerCreateRequestServiceHealthCheck
@@ -1896,17 +1783,25 @@ func (c *converterImpl) pHcloudLoadBalancerToInt64(source *LoadBalancer) int64 {
 	}
 	return xint64
 }
+func (c *converterImpl) pHcloudLoadBalancerTypeToSchemaIDOrName(source *LoadBalancerType) schema.IDOrName {
+	var schemaIDOrName schema.IDOrName
+	if source != nil {
+		var schemaIDOrName2 schema.IDOrName
+		schemaIDOrName2.ID = (*source).ID
+		schemaIDOrName2.Name = (*source).Name
+		schemaIDOrName = schemaIDOrName2
+	}
+	return schemaIDOrName
+}
 func (c *converterImpl) pHcloudLoadBalancerUpdateServiceOptsHTTPToPSchemaLoadBalancerActionUpdateServiceRequestHTTP(source *LoadBalancerUpdateServiceOptsHTTP) *schema.LoadBalancerActionUpdateServiceRequestHTTP {
 	var pSchemaLoadBalancerActionUpdateServiceRequestHTTP *schema.LoadBalancerActionUpdateServiceRequestHTTP
 	if source != nil {
 		var schemaLoadBalancerActionUpdateServiceRequestHTTP schema.LoadBalancerActionUpdateServiceRequestHTTP
 		schemaLoadBalancerActionUpdateServiceRequestHTTP.CookieName = (*source).CookieName
-		var pInt *int
 		if (*source).CookieLifetime != nil {
 			xint := intSecondsFromDuration(*(*source).CookieLifetime)
-			pInt = &xint
+			schemaLoadBalancerActionUpdateServiceRequestHTTP.CookieLifetime = &xint
 		}
-		schemaLoadBalancerActionUpdateServiceRequestHTTP.CookieLifetime = pInt
 		schemaLoadBalancerActionUpdateServiceRequestHTTP.Certificates = int64SlicePtrFromCertificatePtrSlice((*source).Certificates)
 		schemaLoadBalancerActionUpdateServiceRequestHTTP.RedirectHTTP = (*source).RedirectHTTP
 		schemaLoadBalancerActionUpdateServiceRequestHTTP.StickySessions = (*source).StickySessions
@@ -1933,18 +1828,14 @@ func (c *converterImpl) pHcloudLoadBalancerUpdateServiceOptsHealthCheckToPSchema
 		var schemaLoadBalancerActionUpdateServiceRequestHealthCheck schema.LoadBalancerActionUpdateServiceRequestHealthCheck
 		schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Protocol = stringPtrFromLoadBalancerServiceProtocol((*source).Protocol)
 		schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Port = (*source).Port
-		var pInt *int
 		if (*source).Interval != nil {
 			xint := intSecondsFromDuration(*(*source).Interval)
-			pInt = &xint
+			schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Interval = &xint
 		}
-		schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Interval = pInt
-		var pInt2 *int
 		if (*source).Timeout != nil {
 			xint2 := intSecondsFromDuration(*(*source).Timeout)
-			pInt2 = &xint2
+			schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Timeout = &xint2
 		}
-		schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Timeout = pInt2
 		schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Retries = (*source).Retries
 		schemaLoadBalancerActionUpdateServiceRequestHealthCheck.HTTP = c.pHcloudLoadBalancerUpdateServiceOptsHealthCheckHTTPToPSchemaLoadBalancerActionUpdateServiceRequestHealthCheckHTTP((*source).HTTP)
 		pSchemaLoadBalancerActionUpdateServiceRequestHealthCheck = &schemaLoadBalancerActionUpdateServiceRequestHealthCheck
@@ -2085,35 +1976,27 @@ func (c *converterImpl) pSchemaImageToPHcloudImage(source *schema.Image) *Image
 	if source != nil {
 		var hcloudImage Image
 		hcloudImage.ID = (*source).ID
-		var xstring string
 		if (*source).Name != nil {
-			xstring = *(*source).Name
+			hcloudImage.Name = *(*source).Name
 		}
-		hcloudImage.Name = xstring
 		hcloudImage.Type = ImageType((*source).Type)
 		hcloudImage.Status = ImageStatus((*source).Status)
 		hcloudImage.Description = (*source).Description
-		var xfloat32 float32
 		if (*source).ImageSize != nil {
-			xfloat32 = *(*source).ImageSize
+			hcloudImage.ImageSize = *(*source).ImageSize
 		}
-		hcloudImage.ImageSize = xfloat32
 		hcloudImage.DiskSize = (*source).DiskSize
 		hcloudImage.Created = c.pTimeTimeToTimeTime((*source).Created)
 		hcloudImage.CreatedFrom = c.pSchemaImageCreatedFromToPHcloudServer((*source).CreatedFrom)
-		var pHcloudServer *Server
 		if (*source).BoundTo != nil {
 			hcloudServer := serverFromInt64(*(*source).BoundTo)
-			pHcloudServer = &hcloudServer
+			hcloudImage.BoundTo = &hcloudServer
 		}
-		hcloudImage.BoundTo = pHcloudServer
 		hcloudImage.RapidDeploy = (*source).RapidDeploy
 		hcloudImage.OSFlavor = (*source).OSFlavor
-		var xstring2 string
 		if (*source).OSVersion != nil {
-			xstring2 = *(*source).OSVersion
+			hcloudImage.OSVersion = *(*source).OSVersion
 		}
-		hcloudImage.OSVersion = xstring2
 		hcloudImage.Architecture = Architecture((*source).Architecture)
 		hcloudImage.Protection = c.schemaImageProtectionToHcloudImageProtection((*source).Protection)
 		hcloudImage.Deprecated = c.pTimeTimeToTimeTime((*source).Deprecated)
@@ -2129,14 +2012,12 @@ func (c *converterImpl) pSchemaLoadBalancerServiceHTTPToHcloudLoadBalancerServic
 		var hcloudLoadBalancerServiceHTTP2 LoadBalancerServiceHTTP
 		hcloudLoadBalancerServiceHTTP2.CookieName = (*source).CookieName
 		hcloudLoadBalancerServiceHTTP2.CookieLifetime = durationFromIntSeconds((*source).CookieLifetime)
-		var pHcloudCertificateList []*Certificate
 		if (*source).Certificates != nil {
-			pHcloudCertificateList = make([]*Certificate, len((*source).Certificates))
+			hcloudLoadBalancerServiceHTTP2.Certificates = make([]*Certificate, len((*source).Certificates))
 			for i := 0; i < len((*source).Certificates); i++ {
-				pHcloudCertificateList[i] = certificateFromInt64((*source).Certificates[i])
+				hcloudLoadBalancerServiceHTTP2.Certificates[i] = certificateFromInt64((*source).Certificates[i])
 			}
 		}
-		hcloudLoadBalancerServiceHTTP2.Certificates = pHcloudCertificateList
 		hcloudLoadBalancerServiceHTTP2.RedirectHTTP = (*source).RedirectHTTP
 		hcloudLoadBalancerServiceHTTP2.StickySessions = (*source).StickySessions
 		hcloudLoadBalancerServiceHTTP = hcloudLoadBalancerServiceHTTP2
@@ -2217,30 +2098,24 @@ func (c *converterImpl) schemaCertificateUsedByRefToHcloudCertificateUsedByRef(s
 }
 func (c *converterImpl) schemaDatacenterServerTypesToHcloudDatacenterServerTypes(source schema.DatacenterServerTypes) DatacenterServerTypes {
 	var hcloudDatacenterServerTypes DatacenterServerTypes
-	var pHcloudServerTypeList []*ServerType
 	if source.Supported != nil {
-		pHcloudServerTypeList = make([]*ServerType, len(source.Supported))
+		hcloudDatacenterServerTypes.Supported = make([]*ServerType, len(source.Supported))
 		for i := 0; i < len(source.Supported); i++ {
-			pHcloudServerTypeList[i] = serverTypeFromInt64(source.Supported[i])
+			hcloudDatacenterServerTypes.Supported[i] = serverTypeFromInt64(source.Supported[i])
 		}
 	}
-	hcloudDatacenterServerTypes.Supported = pHcloudServerTypeList
-	var pHcloudServerTypeList2 []*ServerType
 	if source.AvailableForMigration != nil {
-		pHcloudServerTypeList2 = make([]*ServerType, len(source.AvailableForMigration))
+		hcloudDatacenterServerTypes.AvailableForMigration = make([]*ServerType, len(source.AvailableForMigration))
 		for j := 0; j < len(source.AvailableForMigration); j++ {
-			pHcloudServerTypeList2[j] = serverTypeFromInt64(source.AvailableForMigration[j])
+			hcloudDatacenterServerTypes.AvailableForMigration[j] = serverTypeFromInt64(source.AvailableForMigration[j])
 		}
 	}
-	hcloudDatacenterServerTypes.AvailableForMigration = pHcloudServerTypeList2
-	var pHcloudServerTypeList3 []*ServerType
 	if source.Available != nil {
-		pHcloudServerTypeList3 = make([]*ServerType, len(source.Available))
+		hcloudDatacenterServerTypes.Available = make([]*ServerType, len(source.Available))
 		for k := 0; k < len(source.Available); k++ {
-			pHcloudServerTypeList3[k] = serverTypeFromInt64(source.Available[k])
+			hcloudDatacenterServerTypes.Available[k] = serverTypeFromInt64(source.Available[k])
 		}
 	}
-	hcloudDatacenterServerTypes.Available = pHcloudServerTypeList3
 	return hcloudDatacenterServerTypes
 }
 func (c *converterImpl) schemaDeprecatableResourceToHcloudDeprecatableResource(source schema.DeprecatableResource) DeprecatableResource {
@@ -2258,22 +2133,18 @@ func (c *converterImpl) schemaFirewallResourceToHcloudFirewallResource(source sc
 func (c *converterImpl) schemaFirewallRuleToHcloudFirewallRule(source schema.FirewallRule) FirewallRule {
 	var hcloudFirewallRule FirewallRule
 	hcloudFirewallRule.Direction = FirewallRuleDirection(source.Direction)
-	var netIPNetList []net.IPNet
 	if source.SourceIPs != nil {
-		netIPNetList = make([]net.IPNet, len(source.SourceIPs))
+		hcloudFirewallRule.SourceIPs = make([]net.IPNet, len(source.SourceIPs))
 		for i := 0; i < len(source.SourceIPs); i++ {
-			netIPNetList[i] = ipNetFromString(source.SourceIPs[i])
+			hcloudFirewallRule.SourceIPs[i] = ipNetFromString(source.SourceIPs[i])
 		}
 	}
-	hcloudFirewallRule.SourceIPs = netIPNetList
-	var netIPNetList2 []net.IPNet
 	if source.DestinationIPs != nil {
-		netIPNetList2 = make([]net.IPNet, len(source.DestinationIPs))
+		hcloudFirewallRule.DestinationIPs = make([]net.IPNet, len(source.DestinationIPs))
 		for j := 0; j < len(source.DestinationIPs); j++ {
-			netIPNetList2[j] = ipNetFromString(source.DestinationIPs[j])
+			hcloudFirewallRule.DestinationIPs[j] = ipNetFromString(source.DestinationIPs[j])
 		}
 	}
-	hcloudFirewallRule.DestinationIPs = netIPNetList2
 	hcloudFirewallRule.Protocol = FirewallRuleProtocol(source.Protocol)
 	hcloudFirewallRule.Port = source.Port
 	hcloudFirewallRule.Description = source.Description
@@ -2298,14 +2169,12 @@ func (c *converterImpl) schemaFromFloatingIPTypeLocationPricing(source FloatingI
 func (c *converterImpl) schemaFromFloatingIPTypePricing(source FloatingIPTypePricing) schema.PricingFloatingIPType {
 	var schemaPricingFloatingIPType schema.PricingFloatingIPType
 	schemaPricingFloatingIPType.Type = string(source.Type)
-	var schemaPricingFloatingIPTypePriceList []schema.PricingFloatingIPTypePrice
 	if source.Pricings != nil {
-		schemaPricingFloatingIPTypePriceList = make([]schema.PricingFloatingIPTypePrice, len(source.Pricings))
+		schemaPricingFloatingIPType.Prices = make([]schema.PricingFloatingIPTypePrice, len(source.Pricings))
 		for i := 0; i < len(source.Pricings); i++ {
-			schemaPricingFloatingIPTypePriceList[i] = c.schemaFromFloatingIPTypeLocationPricing(source.Pricings[i])
+			schemaPricingFloatingIPType.Prices[i] = c.schemaFromFloatingIPTypeLocationPricing(source.Pricings[i])
 		}
 	}
-	schemaPricingFloatingIPType.Prices = schemaPricingFloatingIPTypePriceList
 	return schemaPricingFloatingIPType
 }
 func (c *converterImpl) schemaFromImagePricing(source ImagePricing) schema.PricingImage {
@@ -2319,41 +2188,33 @@ func (c *converterImpl) schemaFromLoadBalancerTypePricing(source LoadBalancerTyp
 	if source.LoadBalancerType != nil {
 		pInt64 = &source.LoadBalancerType.ID
 	}
-	var xint64 int64
 	if pInt64 != nil {
-		xint64 = *pInt64
+		schemaPricingLoadBalancerType.ID = *pInt64
 	}
-	schemaPricingLoadBalancerType.ID = xint64
 	var pString *string
 	if source.LoadBalancerType != nil {
 		pString = &source.LoadBalancerType.Name
 	}
-	var xstring string
 	if pString != nil {
-		xstring = *pString
+		schemaPricingLoadBalancerType.Name = *pString
 	}
-	schemaPricingLoadBalancerType.Name = xstring
-	var schemaPricingLoadBalancerTypePriceList []schema.PricingLoadBalancerTypePrice
 	if source.Pricings != nil {
-		schemaPricingLoadBalancerTypePriceList = make([]schema.PricingLoadBalancerTypePrice, len(source.Pricings))
+		schemaPricingLoadBalancerType.Prices = make([]schema.PricingLoadBalancerTypePrice, len(source.Pricings))
 		for i := 0; i < len(source.Pricings); i++ {
-			schemaPricingLoadBalancerTypePriceList[i] = c.SchemaFromLoadBalancerTypeLocationPricing(source.Pricings[i])
+			schemaPricingLoadBalancerType.Prices[i] = c.SchemaFromLoadBalancerTypeLocationPricing(source.Pricings[i])
 		}
 	}
-	schemaPricingLoadBalancerType.Prices = schemaPricingLoadBalancerTypePriceList
 	return schemaPricingLoadBalancerType
 }
 func (c *converterImpl) schemaFromPrimaryIPPricing(source PrimaryIPPricing) schema.PricingPrimaryIP {
 	var schemaPricingPrimaryIP schema.PricingPrimaryIP
 	schemaPricingPrimaryIP.Type = source.Type
-	var schemaPricingPrimaryIPTypePriceList []schema.PricingPrimaryIPTypePrice
 	if source.Pricings != nil {
-		schemaPricingPrimaryIPTypePriceList = make([]schema.PricingPrimaryIPTypePrice, len(source.Pricings))
+		schemaPricingPrimaryIP.Prices = make([]schema.PricingPrimaryIPTypePrice, len(source.Pricings))
 		for i := 0; i < len(source.Pricings); i++ {
-			schemaPricingPrimaryIPTypePriceList[i] = c.schemaFromPrimaryIPTypePricing(source.Pricings[i])
+			schemaPricingPrimaryIP.Prices[i] = c.schemaFromPrimaryIPTypePricing(source.Pricings[i])
 		}
 	}
-	schemaPricingPrimaryIP.Prices = schemaPricingPrimaryIPTypePriceList
 	return schemaPricingPrimaryIP
 }
 func (c *converterImpl) schemaFromPrimaryIPTypePricing(source PrimaryIPTypePricing) schema.PricingPrimaryIPTypePrice {
@@ -2369,6 +2230,8 @@ func (c *converterImpl) schemaFromServerTypeLocationPricing(source ServerTypeLoc
 	schemaPricingServerTypePrice.Location = c.pHcloudLocationToString(source.Location)
 	schemaPricingServerTypePrice.PriceHourly = c.hcloudPriceToSchemaPrice(source.Hourly)
 	schemaPricingServerTypePrice.PriceMonthly = c.hcloudPriceToSchemaPrice(source.Monthly)
+	schemaPricingServerTypePrice.IncludedTraffic = source.IncludedTraffic
+	schemaPricingServerTypePrice.PricePerTBTraffic = c.hcloudPriceToSchemaPrice(source.PerTBTraffic)
 	return schemaPricingServerTypePrice
 }
 func (c *converterImpl) schemaFromServerTypePricing(source ServerTypePricing) schema.PricingServerType {
@@ -2377,28 +2240,22 @@ func (c *converterImpl) schemaFromServerTypePricing(source ServerTypePricing) sc
 	if source.ServerType != nil {
 		pInt64 = &source.ServerType.ID
 	}
-	var xint64 int64
 	if pInt64 != nil {
-		xint64 = *pInt64
+		schemaPricingServerType.ID = *pInt64
 	}
-	schemaPricingServerType.ID = xint64
 	var pString *string
 	if source.ServerType != nil {
 		pString = &source.ServerType.Name
 	}
-	var xstring string
 	if pString != nil {
-		xstring = *pString
+		schemaPricingServerType.Name = *pString
 	}
-	schemaPricingServerType.Name = xstring
-	var schemaPricingServerTypePriceList []schema.PricingServerTypePrice
 	if source.Pricings != nil {
-		schemaPricingServerTypePriceList = make([]schema.PricingServerTypePrice, len(source.Pricings))
+		schemaPricingServerType.Prices = make([]schema.PricingServerTypePrice, len(source.Pricings))
 		for i := 0; i < len(source.Pricings); i++ {
-			schemaPricingServerTypePriceList[i] = c.schemaFromServerTypeLocationPricing(source.Pricings[i])
+			schemaPricingServerType.Prices[i] = c.schemaFromServerTypeLocationPricing(source.Pricings[i])
 		}
 	}
-	schemaPricingServerType.Prices = schemaPricingServerTypePriceList
 	return schemaPricingServerType
 }
 func (c *converterImpl) schemaFromTrafficPricing(source TrafficPricing) schema.PricingTraffic {
@@ -2484,6 +2341,8 @@ func (c *converterImpl) serverTypePricingFromSchema(source schema.PricingServerT
 	hcloudServerTypeLocationPricing.Location = &hcloudLocation
 	hcloudServerTypeLocationPricing.Hourly = c.PriceFromSchema(source.PriceHourly)
 	hcloudServerTypeLocationPricing.Monthly = c.PriceFromSchema(source.PriceMonthly)
+	hcloudServerTypeLocationPricing.IncludedTraffic = source.IncludedTraffic
+	hcloudServerTypeLocationPricing.PerTBTraffic = c.PriceFromSchema(source.PricePerTBTraffic)
 	return hcloudServerTypeLocationPricing
 }
 func (c *converterImpl) timeTimeToTimeTime(source time.Time) time.Time {
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go
index 82038a30cbacb28023630ac51d69aaf60e48a72a..f249f7831bde5a147149e8af9fd2f47998bdcf6e 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go
@@ -62,9 +62,8 @@ type IServerClient interface {
 	AttachISO(ctx context.Context, server *Server, iso *ISO) (*Action, *Response, error)
 	// DetachISO detaches the currently attached ISO from a server.
 	DetachISO(ctx context.Context, server *Server) (*Action, *Response, error)
-	// EnableBackup enables backup for a server. Pass in an empty backup window to let the
-	// API pick a window for you. See the API documentation at docs.hetzner.cloud for a list
-	// of valid backup windows.
+	// EnableBackup enables backup for a server.
+	// The window parameter is deprecated and will be ignored.
 	EnableBackup(ctx context.Context, server *Server, window string) (*Action, *Response, error)
 	// DisableBackup disables backup for a server.
 	DisableBackup(ctx context.Context, server *Server) (*Action, *Response, error)
diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go
index d546afab12230099e473143e463a5675625cd537..a1ba45088c80c7182496aa6a3cde80291457a9bd 100644
--- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go
+++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go
@@ -95,7 +95,9 @@ func newManager() (*hetznerManager, error) {
 		hcloud.WithToken(token),
 		hcloud.WithHTTPClient(httpClient),
 		hcloud.WithApplication("cluster-autoscaler", version.ClusterAutoscalerVersion),
-		hcloud.WithPollBackoffFunc(hcloud.ExponentialBackoff(2, 500*time.Millisecond)),
+		hcloud.WithPollOpts(hcloud.PollOpts{
+			BackoffFunc: hcloud.ExponentialBackoff(2, 500*time.Millisecond),
+		}),
 		hcloud.WithDebugWriter(&debugWriter{}),
 	}
 
@@ -252,7 +254,7 @@ func (m *hetznerManager) deleteByNode(node *apiv1.Node) error {
 }
 
 func (m *hetznerManager) deleteServer(server *hcloud.Server) error {
-	_, err := m.client.Server.Delete(m.apiCallContext, server)
+	_, _, err := m.client.Server.DeleteWithResult(m.apiCallContext, server)
 	return err
 }