hashicorp/terraform · error

Failed to create API client

Error message

Failed to create API client: %s

What it means

Thrown by `terraform login` after `tfe.NewClient(cfg)` returns a non-nil error while constructing an HCP Terraform / Terraform Enterprise (TFE) API client from a user-supplied token. The `tfe.Config` was built from the discovered service address, base path, and the token the user typed at the interactive prompt, but the go-tfe client rejected the configuration as malformed. This is a client-side construction failure, not an HTTP request failure — the token has not yet been sent to any server.

Solutions

  1. Verify the target hostname is a real HCP Terraform or Terraform Enterprise deployment by opening `https://<hostname>/.well-known/terraform.json` in a browser and confirming a `tfe.vN` key with a valid URL.
  2. Re-run `terraform login <hostname>` and ensure the hostname is spelled correctly and reachable from your machine.
  3. If behind a proxy or custom cert, confirm `HTTPS_PROXY` / `SSL_CERT_FILE` are set so service discovery succeeds before token entry.
  4. Inspect the wrapped `%s` detail — it usually names the exact config field go-tfe rejected.

Example fix

// before: hostname with typo
terraform login app.terraform.io
// after
terraform login app.terraform.io  // correct hostname
// or, for TFE:
terraform login tfe.corp.example.com
Defensive patterns

Strategy: validation

Validate before calling

// Validate the hostname and service discovery BEFORE token entry.
// Run this prior to `terraform login` to confirm the host is real.
doc, err := disco.New().Discover(ctx, "https://"+hostname)
if err != nil || doc.TerraformServiceDiscovered() == nil {
    return fmt.Errorf("host %s does not advertise a TFE service; fix discovery before login", hostname)
}

Type guard

null

Try / catch

// In Go code wrapping `terraform login` programmatically:
diags := loginCmd.Run(args)
for _, d := range diags {
    if strings.Contains(d.Description().Summary, "Failed to create API client") {
        // surface the wrapped %s detail to guide the user to discovery/URL issues
    }
}

Prevention

When it happens

Trigger: `tfe.NewClient` fails when the `Address`/`BasePath` derived from the host's service discovery document produces an invalid URL (e.g. an empty or non-HTTP(S) address), when the token string is empty after `strings.TrimSpace`, or when go-tfe's internal validation of the config struct rejects it. The error wraps the underlying message with `%s`.

Common situations: Logging in to a private TFE instance whose service-discovery `.well-known/terraform.json` returns a malformed `tfe.vN` service endpoint; a misconfigured custom hostname alias that resolves to a non-TFE service; running `terraform login` against a host that does not actually advertise the `tfe.vN` service but the code path reached token entry anyway.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/795fa4864d595c4b. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/login.go:657

		Id:     "token",
		Query:  fmt.Sprintf("Token for %s:", hostname.ForDisplay()),
		Secret: true,
	})
	if err != nil {
		diags := diags.Append(fmt.Errorf("Failed to retrieve token: %s", err))
		return "", diags
	}

	token = strings.TrimSpace(token)
	cfg := &tfe.Config{
		Address:  service.String(),
		BasePath: service.Path,
		Token:    token,
		Headers:  make(http.Header),
	}
	client, err := tfe.NewClient(cfg)
	if err != nil {
		diags = diags.Append(fmt.Errorf("Failed to create API client: %s", err))
		return "", diags
	}
	user, err := client.Users.ReadCurrent(context.Background())
	if err == tfe.ErrUnauthorized {
		diags = diags.Append(fmt.Errorf("Token is invalid: %s", err))
		return "", diags
	} else if err != nil {
		diags = diags.Append(fmt.Errorf("Failed to retrieve user account details: %s", err))
		return "", diags
	}
	c.Ui.Output(fmt.Sprintf(c.Colorize().Color("\nRetrieved token for user [bold]%s[reset]\n"), user.Username))

	return svcauth.HostCredentialsToken(token), nil
}

func (c *LoginCommand) interactiveContextConsent(hostname svchost.Hostname, grantType disco.OAuthGrantType, credsCtx *loginCredentialsContext) (bool, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	mechanism := "OAuth"

View on GitHub (pinned to d32a084675)