Cannot use Update DNS Record API with A records

Hello, I’m seeing weird behavior with a script I’m writing to sync dynamic DNS info to my domains on Cloudflare.

Using the Cloudflare API, I can successfully update DNS records with the CNAME type (to update subdomains), however when I try to UPDATE (via a put or patch) an A record, it gives me a “Error (81057): Record already exists.”

Which… yes, it does exist, but I’m not trying to create it, I’m trying to update it with a new IP address.
Is there an extra permission my API key needs to update A records? The same exact code works for CNAME records.

Can A records not be updated? Should I be deleting / recreating them?

Can you share your code for updating?

1 Like

Sure. this is a Golang app, this is the function that executes the API call:

func (c *CloudFlareAPI) UpdateDNSRecord(zoneId, recordId string, body DNSRecordBody) (err error) {
	url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneId, recordId)

	var encoded []byte
	encoded, err = json.Marshal(body)
	if err != nil {
		logger.Error("couldn't marshal updatednsrecord body: %s", err)
		return
	}
	encodedBytes := bytes.NewReader(encoded)

	var req *http.Request
	req, err = http.NewRequest(http.MethodPatch, url, encodedBytes)
	if err != nil {
		logger.Error("couldn't create request for updatednsrecord: %s", err)
		return
	}

	// Update auth headers
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.Token))
	req.Header.Add("Content-Type", "application/json")

	var resp *http.Response
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		logger.Error("couldn't updatednsrecord: %s", err)
		return
	}

	var respBytes []byte
	respBytes, err = io.ReadAll(resp.Body)
	if err != nil {
		logger.Error("couldn't read updatednsrecord response: %s", err)
		return
	}

	var cfResponse CloudFlareDNSRecordResponse
	err = json.Unmarshal(respBytes, &cfResponse)
	if err != nil {
		logger.Error("couldn't unmarshal updatednsrecord response: %s", err)
		return
	}

	for _, m := range cfResponse.Messages {
		logger.Info("Message (%d): %s", m.Code, m.Message)
	}
	for _, m := range cfResponse.Errors {
		logger.Warn("Error (%d): %s", m.Code, m.Message)
	}

	if !cfResponse.Success {
		err = errors.New("UpdateDNSRecord failed, see log")
	}

	return
}

I’ve confirmed that zoneId and recordId are valid and equal to the zone/record I want to update, this is the structure of DNSRecordBody

type DNSRecordBody struct {
	Content string   `json:"content"`
	Name    string   `json:"name"`
	Proxied bool     `json:"proxied"`
	Type    string   `json:"type"`
	Comment string   `json:"comment"`
	Tags    []string `json:"tags"`
	TTL     int64    `json:"ttl"`
}

And here are the values I was trying to update with:

{
   "content": "my-ip-redacted",
   "name": "mydomain.com",
   "proxied": true,
   "type": "A",
   "comment": "Updated via automation at (timestamp)",
   "tags": null,
   "ttl": 1
}

Update:

Added some logic to delete the existing A record.

The response from the delete endpoint is

{ "result": null }

No idea what the problem is, the API can’t delete or edit A records.
I manually deleted the A record from the UI and the API WAS able to create it.

I thought maybe it was a permission thing but the only options for my API key are Read and Edit and its set to:

Zone, DNS, Edit

Final Update:

Figured it out, for anyone that makes a silly mistake like I did.

Turns out I was using the incorrect dnsRecordId, because I was only matching by name and not by name and type, turns out I was trying to update the TXT record for email forwarding instead of the A record containing my IP address.

The delete was failing because I had email forwarding turned on, and the API was refusing to delete the record as a result.

TLDR; always check the error messages that are coming back.