grafana/k6 · error

a /v1 suffix is expected in the Cloud service's BaseURL path

Error message

a /v1 suffix is expected in the Cloud service's BaseURL path

What it means

Returned by deriveMetricsURL (output/cloud/expv2/metrics_client.go:41-43), which builds the v2 metrics-ingestion URL from the Cloud service base URL. The cloudapi.Client hard-codes /v1 for test lifecycle management, so the expv2 output requires the base URL to end in /v1 and derives ingestion as <trimmed>/v2/metrics/<testRunID> (line 47). A base URL without the /v1 suffix means the API-version convention was violated and URL derivation would be wrong. Reached from Output.Start (output.go:134) when neither metricsHTTPClient nor metricsURL was injected; the error surfaces wrapped as 'failed to derive the metrics push URL'.

Source

Thrown at output/cloud/expv2/metrics_client.go:42

// the collected metrics from the Cloud output
// to the remote service.
type metricsClient struct {
	httpClient metricsHTTPClient
	url        string
}

// deriveMetricsURL builds the v2 metrics-ingestion URL from the Cloud
// service base URL, used when no explicit push URL was provided.
//
// The cloudapi.Client works across different versions of the API: test
// lifecycle management is under /v1 while metrics ingestion is /v2.
// The client has /v1 hard-coded, so we trim it and replace with /v2.
// A versioned client would be better but it would require a breaking
// change, and other services (e.g. k6-operator) depend on it, so we want
// to stabilize the API first.
func deriveMetricsURL(baseURL, testRunID string) (string, error) {
	if !strings.HasSuffix(baseURL, "/v1") {
		return "", errors.New("a /v1 suffix is expected in the Cloud service's BaseURL path")
	}
	if testRunID == "" {
		return "", errors.New("TestRunID of the test is required")
	}
	return strings.TrimSuffix(baseURL, "/v1") + "/v2/metrics/" + testRunID, nil
}

// newMetricsClientWithURL builds a metricsClient with an explicit push URL.
// It is the single metricsClient constructor: callers that need the
// host-derived URL compute it via deriveMetricsURL first.
func newMetricsClientWithURL(c metricsHTTPClient, url string) (*metricsClient, error) {
	if url == "" {
		return nil, errors.New("metrics push URL is required")
	}
	return &metricsClient{
		httpClient: c,
		url:        url,
	}, nil

View on GitHub (pinned to 93accf6570)

Solutions

  1. Append /v1 to the host, e.g. K6_CLOUD_HOST=https://cloudutils.grafana.com/v1
  2. If the endpoint genuinely has a different versioning scheme, bypass derivation by providing an explicit push URL: set both K6_CLOUD_METRICS_PUSH_URL and K6_CLOUD_TEST_RUN_TOKEN (output.go requires them together)

Example fix

# before
K6_CLOUD_HOST=https://cloudutils.grafana.com k6 cloud script.js

# after
K6_CLOUD_HOST=https://cloudutils.grafana.com/v1 k6 cloud script.js
Defensive patterns

Strategy: validation

Validate before calling

# shell: assert the /v1 suffix before running
H="${K6_CLOUD_HOST:-https://cloudutils.grafana.com/v1}"
case "$H" in */v1) ;; *) echo "K6_CLOUD_HOST must end with /v1"; exit 1;; esac
export K6_CLOUD_HOST="$H"

Prevention

When it happens

Trigger: Setting K6_CLOUD_HOST=https://api.cloud.k6.io (no /v1), or a custom/proxy Cloud host like https://cloud.internal.example. Defaults such as https://cloudutils.grafana.com/v1 and https://api.k6.io/v1 already carry the suffix.

Common situations: Pointing k6 at Grafana Cloud or a self-hosted/proxied Cloud endpoint and forgetting the /v1 path; env var typos; on-prem gateway URLs rewritten to strip path prefixes.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/952960bd1d437ec5. Report an issue: GitHub.