anomalyco/sst · error

Cloudflare API error: %s

Error message

Cloudflare API error: %s

What it means

This error aggregates the message fields of the errors array returned by the Cloudflare API when the DNS record create/update request fails (non-2xx status or success:false). The library joins all error messages with "; " so the developer sees exactly what Cloudflare rejected. Note that "already exists" errors are intercepted earlier and silently return "existing-record".

Source

Thrown at pkg/server/resource/cloudflare-dns-record.go:174

	if resp.StatusCode < 200 || resp.StatusCode >= 300 || !apiResponse.Success {
		// Check for "already exists" in error messages
		if len(apiResponse.Errors) > 0 {
			for _, cfError := range apiResponse.Errors {
				// Check if message contains "already exists" regardless of error code
				if strings.Contains(strings.ToLower(cfError.Message), "already exists") {
					return "existing-record", nil
				}
			}
		}
		
		// If not an "already exists" error, return the error information
		errorMsgs := []string{}
		for _, cfError := range apiResponse.Errors {
			errorMsgs = append(errorMsgs, fmt.Sprintf("%s", cfError.Message))
		}
		
		if len(errorMsgs) > 0 {
			return "", fmt.Errorf("Cloudflare API error: %s", strings.Join(errorMsgs, "; "))
		}
		
		// If we couldn't determine a specific error, return the raw response
		return "", fmt.Errorf("failed to create DNS record, status: %d, response: %s", resp.StatusCode, string(body))
	}
	
	// Success - return the record ID
	return apiResponse.Result.Id, nil
}

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Read the joined message(s) in the error — they name the exact Cloudflare error code and cause
  2. Verify the API token has Zone.DNS Edit permission for the target zone
  3. Confirm the zone_id in the request URL matches the zone owning the record name
  4. Fix the record payload: content must match the type (e.g. an IP for A records, a hostname for CNAME)
  5. Resolve naming conflicts — CNAME cannot coexist with other records at the same name
Defensive patterns

Strategy: type-guard

Validate before calling

// validate record shape before calling the API
func validateRecord(recType, name, value string) error {
    switch recType {
    case "A":
        ip := net.ParseIP(value)
        if ip == nil || ip.To4() == nil {
            return fmt.Errorf("A record content must be an IPv4 address, got %q", value)
        }
    case "AAAA":
        ip := net.ParseIP(value)
        if ip == nil || ip.To4() != nil {
            return fmt.Errorf("AAAA record content must be an IPv6 address, got %q", value)
        }
    }
    return nil
}

Type guard

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

Try / catch

_, err := record.Create(input, &out)
if err != nil {
    if strings.Contains(err.Error(), "already exists") {
        return nil // idempotent
    }
    if isCloudflareAPIError(err) {
        return fmt.Errorf("cloudflare rejected the record: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Cloudflare responds success:false (or non-2xx) with a populated errors array — e.g. invalid record content (code 1004), invalid DNS record type/value mismatch, DNS validation errors, zone not found (code 7003), authentication error (code 9109), or record names exceeding limits.

Common situations: Passing a CNAME name that collides with an existing record; an A/AAAA record with a hostname as content; using an API token without DNS edit permission for the zone; wrong zone_id in the URL; CAA/SRV records missing required fields (the special-cased payload path).

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/016f1ca8010f6132. Report an issue: GitHub.