larksuite/cli · error

failed to parse TAT response (HTTP %d): %w

Error message

failed to parse TAT response (HTTP %d): %w

What it means

FetchTAT fetched a Tenant Access Token response but json.Unmarshal failed on the body, so it cannot parse the JSON envelope into tatResponse. The message embeds the HTTP status of the underlying response and the JSON error. Per the source comment, the body is deliberately left untyped so probe callers treat it as ambiguous noise.

Source

Thrown at internal/credential/tat_fetch.go:105

		if rateLimitErr == nil {
			rateLimitErr = errs.NewAPIError(errs.SubtypeRateLimit, "TAT endpoint rate limited (HTTP 429)").
				WithCode(http.StatusTooManyRequests).
				WithRetryable()
		}
		if retryAfter := tatRetryAfterSeconds(resp.Header); retryAfter > 0 {
			rateLimitErr.RetryAfterSeconds = retryAfter
			rateLimitErr.Hint = fmt.Sprintf("wait at least %d seconds before retrying; if throttling continues, use exponential backoff with jitter", retryAfter)
		} else {
			rateLimitErr.Hint = "use exponential backoff with jitter when retrying"
		}
		return "", rateLimitErr
	}

	var result tatResponse
	if err := json.Unmarshal(body, &result); err != nil {
		// An unparseable body is ambiguous (covers non-JSON error pages and
		// truncated payloads); stay untyped so probe callers treat it as noise.
		return "", fmt.Errorf("failed to parse TAT response (HTTP %d): %w", resp.StatusCode, err)
	}

	if result.Code == 0 && result.AccessToken != "" {
		return result.AccessToken, nil
	}

	// Transient/server-side failures stay untyped so probe callers stay silent and
	// retryers can back off; only deterministic client rejections are typed. Covers
	// 5xx and the OAuth transient error strings (server_error,
	// temporarily_unavailable, slow_down). HTTP 429 was already returned above
	// as a typed rate-limit error with retry guidance and an upstream delay when available.
	if resp.StatusCode >= 500 ||
		result.Error == "server_error" || result.Error == "temporarily_unavailable" ||
		result.Error == "slow_down" {
		return "", fmt.Errorf("TAT endpoint transient failure (HTTP %d, code=%d, error=%q): %s",
			resp.StatusCode, result.Code, result.Error, result.ErrorDescription)
	}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Check network path to the TAT endpoint: print/capture the raw body and HTTP status to see whether a proxy, WAF, or gateway page is being returned instead of JSON.
  2. Verify the configured base URL / endpoint host for the token endpoint is the correct Lark/Feishu API domain.
  3. Retry the request; if truncated payloads recur, check for TLS/proxy issues or reduced timeouts cutting responses short.
  4. If a proxy is required, ensure HTTP(S)_PROXY settings are correctly configured for the process.

Example fix

// before (caller sees only the wrapped error)
token, err := credential.FetchTAT(ctx, cfg)

// after (surface the raw body for diagnosis when parsing fails)
token, err := credential.FetchTAT(ctx, cfg)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse TAT response") {
        log.Printf("TAT parse failure, check proxy/gateway for non-JSON response (status embedded in error): %v", err)
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// Optionally pre-flight the endpoint shape:
resp, err := http.Post(tokenURL, "application/json", body)
if err == nil && resp.StatusCode == 200 {
    var probe struct{ AccessToken string `json:"access_token"` }
    raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<16))
    if json.Unmarshal(raw, &probe) != nil || !strings.HasPrefix(strings.TrimSpace(string(raw)), "{") {
        log.Printf("endpoint returning non-JSON body: %.200s", raw)
    }
}

Try / catch

token, err := credential.FetchTAT(ctx, cfg)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse TAT response") {
        // ambiguous: likely proxy/gateway noise — retry after short delay
        time.Sleep(time.Second)
        token, err = credential.FetchTAT(ctx, cfg)
    }
    if err != nil { return fmt.Errorf("obtain TAT: %w", err) }
}

Prevention

When it happens

Trigger: The TAT endpoint returned a body that is not valid JSON or is truncated: e.g. an HTML error page from a proxy/gateway, an empty body, or a connection cut mid-response, regardless of HTTP status.

Common situations: Corporate proxies or WAFs intercepting the token endpoint and returning HTML login/block pages; Lark gateway 502/503 pages in HTML; network interruption truncating the response; pointing the client at a wrong base URL that serves a redirect page.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/128aae7ae56a131a. Report an issue: GitHub.