cloudflare/cloudflared · error

API errors: %s

Error message

API errors: %s

What it means

cfapi/base_client.go's (*response).checkErrors converts the errors array of a Cloudflare API response envelope into a Go error. When the envelope contains exactly one error it returns that structured apiError; when there are multiple, it concatenates all their messages with "; " separators into a single "API errors: ..." error. This surfaces validation/rejection details returned by the Cloudflare API for a failed request.

Source

Thrown at cfapi/base_client.go:209

type Pagination struct {
	Count      int `json:"count,omitempty"`
	Page       int `json:"page,omitempty"`
	PerPage    int `json:"per_page,omitempty"`
	TotalCount int `json:"total_count,omitempty"`
}

func (r *response) checkErrors() error {
	if len(r.Errors) == 0 {
		return nil
	}
	if len(r.Errors) == 1 {
		return r.Errors[0]
	}
	var messagesBuilder strings.Builder
	for _, e := range r.Errors {
		messagesBuilder.WriteString(fmt.Sprintf("%s; ", e))
	}
	return fmt.Errorf("API errors: %s", messagesBuilder.String())
}

type apiError struct {
	Code    json.Number `json:"code,omitempty"`
	Message string      `json:"message,omitempty"`
}

func (e apiError) Error() string {
	return fmt.Sprintf("code: %v, reason: %s", e.Code, e.Message)
}

func (r *RESTClient) statusCodeToError(op string, resp *http.Response) error {
	if resp.Header.Get("Content-Type") == "application/json" {
		var errorsResp response
		if json.NewDecoder(resp.Body).Decode(&errorsResp) == nil {
			if err := errorsResp.checkErrors(); err != nil {
				return errors.Errorf("Failed to %s: %s", op, err)
			}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Parse the joined message — each ';'-separated entry is one API error; fix the reported fields/IDs and retry.
  2. Inspect HTTP status and the structured apiError (Code/Message) via statusCodeToError for the primary cause.
  3. Validate request payloads (zone IDs, record names, TTLs) against Cloudflare API docs before sending.
  4. Check API token permissions/scopes if messages indicate authentication/authorization failures.
  5. Apply backoff and retry if errors indicate transient rate limiting (error code 9xx/rate limit headers).

Example fix

// before: log-and-continue on envelope errors
resp, _ := client.ZoneDetails(ctx, zoneID)
// after: surface compound API errors
resp, err := client.ZoneDetails(ctx, zoneID)
if err != nil {
	var apiErr *cfapi.apiError
	if errors.As(err, &apiErr) {
		log.Error().Str("code", apiErr.Code.String()).Msg(apiErr.Message)
	}
	return fmt.Errorf("cloudflare api call failed: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate identifiers before the API call
if zoneID == "" {
    return errors.New("zone ID must not be empty")
}
if !strings.Contains(accountTag, "-") && len(accountTag) != 32 {
    return fmt.Errorf("account tag %q is not a valid identifier", accountTag)
}

Type guard

func isCompoundAPIError(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "API errors: ")
}

Try / catch

err := resp.checkErrors()
if err != nil {
    if isCompoundAPIError(err) {
        for _, part := range strings.Split(strings.TrimPrefix(err.Error(), "API errors: "), "; ") {
            log.Warn().Msg("cloudflare api error: " + part)
        }
    }
    return fmt.Errorf("cloudflare request rejected: %w", err)
}

Prevention

When it happens

Trigger: checkErrors is invoked from parseResponseEnvelope and statusCodeToError whenever the decoded response envelope has len(Errors) > 1 — the Cloudflare API returned HTTP 2xx/4xx/5xx with multiple error entries (e.g. several validation failures for one request).

Common situations: Batching operations (DNS record updates, tunnel config changes) where several fields fail validation at once; an invalid API token triggering multiple authorization error entries; rate/plan limits reported alongside a validation error; malformed zone or account IDs producing compound errors.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/0396036f90e15303. Report an issue: GitHub.