Repository: go-acme/lego
Pull request #1945 (2023-06-27 17:11:08)

Add DNS provider for Efficient IP
- [x] have a homogeneous design with the other providers
- [x] pass the linter
- [x] do `go mod tidy`
- [x] add tests (units)
- [x] add tests ("live")
- [x] add a [provider descriptor](https://github.com/go-acme/lego/blob/master/providers/dns/cloudflare/cloudflare.toml)
- [x] generate CLI help, documentation, and readme.
- [x] be able to do: (and put the output of this command to a comment)
```console
$ EFFICIENTIP_USERNAME="user" \
EFFICIENTIP_PASSWORD="secret" \
EFFICIENTIP_HOSTNAME="ipam.example.com" \
EFFICIENTIP_DNS_NAME="dns.smart" \
./dist/lego -m mail@example.com --dns efficientip -d *.lego.example.com -d lego.example.com --dns.resolvers="1.1.1.1" -s https://acme-staging-v02.api.letsencrypt.org/directory run
2023/06/27 19:00:23 No key found for account mail@example.com. Generating a P256 key.
2023/06/27 19:00:23 Saved key to /home/ldez/sources/go/src/github.com/go-acme/lego/.lego/accounts/acme-staging-v02.api.letsencrypt.org/mail@example.com/keys/mail@example.com.key
2023/06/27 19:00:23 Please review the TOS at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf
Do you accept the TOS? Y/n
y
2023/06/27 19:00:26 [INFO] acme: Registering account for mail@example.com
!!!! HEADS UP !!!!

Your account credentials have been saved in your Let's Encrypt
configuration directory at "./.lego/accounts".

You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from Let's Encrypt so making regular
backups of this folder is ideal.
2023/06/27 19:00:26 [INFO] [*.lego.example.com, lego.example.com] acme: Obtaining bundled SAN certificate
2023/06/27 19:00:27 [INFO] [*.lego.example.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/7069071614
2023/06/27 19:00:27 [INFO] [lego.example.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/7069071624
2023/06/27 19:00:27 [INFO] [*.lego.example.com] acme: use dns-01 solver
2023/06/27 19:00:27 [INFO] [lego.example.com] acme: Could not find solver for: tls-alpn-01
2023/06/27 19:00:27 [INFO] [lego.example.com] acme: Could not find solver for: http-01
2023/06/27 19:00:27 [INFO] [lego.example.com] acme: use dns-01 solver
2023/06/27 19:00:27 [INFO] [*.lego.example.com] acme: Preparing to solve DNS-01
2023/06/27 19:00:27 [INFO] [lego.example.com] acme: Preparing to solve DNS-01
2023/06/27 19:00:27 [INFO] [*.lego.example.com] acme: Trying to solve DNS-01
2023/06/27 19:00:27 [INFO] [*.lego.example.com] acme: Checking DNS record propagation using [1.1.1.1:53]
2023/06/27 19:00:29 [INFO] Wait for propagation [timeout: 5m0s, interval: 2s]
2023/06/27 19:00:29 [INFO] [*.lego.example.com] acme: Waiting for DNS record propagation.
2023/06/27 19:00:31 [INFO] [*.lego.example.com] acme: Waiting for DNS record propagation.
2023/06/27 19:00:33 [INFO] [*.lego.example.com] acme: Waiting for DNS record propagation.
2023/06/27 19:00:42 [INFO] [*.lego.example.com] The server validated our request
2023/06/27 19:00:42 [INFO] [lego.example.com] acme: Trying to solve DNS-01
2023/06/27 19:00:42 [INFO] [lego.example.com] acme: Checking DNS record propagation using [1.1.1.1:53]
2023/06/27 19:00:44 [INFO] Wait for propagation [timeout: 5m0s, interval: 2s]
2023/06/27 19:00:47 [INFO] [lego.example.com] The server validated our request
2023/06/27 19:00:47 [INFO] [*.lego.example.com] acme: Cleaning DNS-01 challenge
2023/06/27 19:00:47 [INFO] [lego.example.com] acme: Cleaning DNS-01 challenge
2023/06/27 19:00:47 [INFO] [*.lego.example.com, lego.example.com] acme: Validations succeeded; requesting certificates
2023/06/27 19:00:48 [INFO] Wait for certificate [timeout: 30s, interval: 500ms]
2023/06/27 19:00:49 [INFO] [*.lego.example.com] Server responded with a certificate.
```

Note the wildcard domain is important.

Closes #1892



Patch:
diff --git a/README.md b/README.md
index a927691c..8f70132f 100644
--- a/README.md
+++ b/README.md
@@ -62,27 +62,27 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
 | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/)               | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/)                | [dnsHome.de](https://go-acme.github.io/lego/dns/dnshomede/)                     | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/)                        |
 | [DNSPod (deprecated)](https://go-acme.github.io/lego/dns/dnspod/)               | [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/)            | [Domeneshop](https://go-acme.github.io/lego/dns/domeneshop/)                    | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/)                      |
 | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/)                         | [Dyn](https://go-acme.github.io/lego/dns/dyn/)                                  | [Dynu](https://go-acme.github.io/lego/dns/dynu/)                                | [EasyDNS](https://go-acme.github.io/lego/dns/easydns/)                          |
-| [Epik](https://go-acme.github.io/lego/dns/epik/)                                | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/)                        | [External program](https://go-acme.github.io/lego/dns/exec/)                    | [freemyip.com](https://go-acme.github.io/lego/dns/freemyip/)                    |
-| [G-Core Labs](https://go-acme.github.io/lego/dns/gcore/)                        | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/)              | [Gandi](https://go-acme.github.io/lego/dns/gandi/)                              | [Glesys](https://go-acme.github.io/lego/dns/glesys/)                            |
-| [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/)                         | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/)                      | [Google Domains](https://go-acme.github.io/lego/dns/googledomains/)             | [Hetzner](https://go-acme.github.io/lego/dns/hetzner/)                          |
-| [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/)                     | [Hosttech](https://go-acme.github.io/lego/dns/hosttech/)                        | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/)                     | [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/)         |
-| [HyperOne](https://go-acme.github.io/lego/dns/hyperone/)                        | [IBM Cloud (SoftLayer)](https://go-acme.github.io/lego/dns/ibmcloud/)           | [IIJ DNS Platform Service](https://go-acme.github.io/lego/dns/iijdpf/)          | [Infoblox](https://go-acme.github.io/lego/dns/infoblox/)                        |
-| [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/)                    | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/)            | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/)                   | [INWX](https://go-acme.github.io/lego/dns/inwx/)                                |
-| [Ionos](https://go-acme.github.io/lego/dns/ionos/)                              | [iwantmyname](https://go-acme.github.io/lego/dns/iwantmyname/)                  | [Joker](https://go-acme.github.io/lego/dns/joker/)                              | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/)               |
-| [Liara](https://go-acme.github.io/lego/dns/liara/)                              | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/)                       | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/)                     | [Loopia](https://go-acme.github.io/lego/dns/loopia/)                            |
-| [LuaDNS](https://go-acme.github.io/lego/dns/luadns/)                            | [Manual](https://go-acme.github.io/lego/dns/manual/)                            | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/)                         | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/)                |
-| [Name.com](https://go-acme.github.io/lego/dns/namedotcom/)                      | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/)                      | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/)                        | [NearlyFreeSpeech.NET](https://go-acme.github.io/lego/dns/nearlyfreespeech/)    |
-| [Netcup](https://go-acme.github.io/lego/dns/netcup/)                            | [Netlify](https://go-acme.github.io/lego/dns/netlify/)                          | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/)                    | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/)                        |
-| [Njalla](https://go-acme.github.io/lego/dns/njalla/)                            | [Nodion](https://go-acme.github.io/lego/dns/nodion/)                            | [NS1](https://go-acme.github.io/lego/dns/ns1/)                                  | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/)                   |
-| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/)                 | [OVH](https://go-acme.github.io/lego/dns/ovh/)                                  | [plesk.com](https://go-acme.github.io/lego/dns/plesk/)                          | [Porkbun](https://go-acme.github.io/lego/dns/porkbun/)                          |
-| [PowerDNS](https://go-acme.github.io/lego/dns/pdns/)                            | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/)                      | [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/)                      | [reg.ru](https://go-acme.github.io/lego/dns/regru/)                             |
-| [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/)                          | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/)                  | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/)                 | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/)                        |
-| [Selectel](https://go-acme.github.io/lego/dns/selectel/)                        | [Servercow](https://go-acme.github.io/lego/dns/servercow/)                      | [Simply.com](https://go-acme.github.io/lego/dns/simply/)                        | [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              |
-| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/)           | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/)                   |
-| [Ultradns](https://go-acme.github.io/lego/dns/ultradns/)                        | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/)                    | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          | [Vercel](https://go-acme.github.io/lego/dns/vercel/)                            |
-| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/)                         | [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            |
-| [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [Websupport](https://go-acme.github.io/lego/dns/websupport/)                    | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/)                 |
-| [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/)                        | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |                                                                                 |
+| [Efficient IP](https://go-acme.github.io/lego/dns/efficientip/)                 | [Epik](https://go-acme.github.io/lego/dns/epik/)                                | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/)                        | [External program](https://go-acme.github.io/lego/dns/exec/)                    |
+| [freemyip.com](https://go-acme.github.io/lego/dns/freemyip/)                    | [G-Core Labs](https://go-acme.github.io/lego/dns/gcore/)                        | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/)              | [Gandi](https://go-acme.github.io/lego/dns/gandi/)                              |
+| [Glesys](https://go-acme.github.io/lego/dns/glesys/)                            | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/)                         | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/)                      | [Google Domains](https://go-acme.github.io/lego/dns/googledomains/)             |
+| [Hetzner](https://go-acme.github.io/lego/dns/hetzner/)                          | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/)                     | [Hosttech](https://go-acme.github.io/lego/dns/hosttech/)                        | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/)                     |
+| [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/)         | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/)                        | [IBM Cloud (SoftLayer)](https://go-acme.github.io/lego/dns/ibmcloud/)           | [IIJ DNS Platform Service](https://go-acme.github.io/lego/dns/iijdpf/)          |
+| [Infoblox](https://go-acme.github.io/lego/dns/infoblox/)                        | [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/)                    | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/)            | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/)                   |
+| [INWX](https://go-acme.github.io/lego/dns/inwx/)                                | [Ionos](https://go-acme.github.io/lego/dns/ionos/)                              | [iwantmyname](https://go-acme.github.io/lego/dns/iwantmyname/)                  | [Joker](https://go-acme.github.io/lego/dns/joker/)                              |
+| [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/)               | [Liara](https://go-acme.github.io/lego/dns/liara/)                              | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/)                       | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/)                     |
+| [Loopia](https://go-acme.github.io/lego/dns/loopia/)                            | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/)                            | [Manual](https://go-acme.github.io/lego/dns/manual/)                            | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/)                         |
+| [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/)                | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/)                      | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/)                      | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/)                        |
+| [NearlyFreeSpeech.NET](https://go-acme.github.io/lego/dns/nearlyfreespeech/)    | [Netcup](https://go-acme.github.io/lego/dns/netcup/)                            | [Netlify](https://go-acme.github.io/lego/dns/netlify/)                          | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/)                    |
+| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/)                        | [Njalla](https://go-acme.github.io/lego/dns/njalla/)                            | [Nodion](https://go-acme.github.io/lego/dns/nodion/)                            | [NS1](https://go-acme.github.io/lego/dns/ns1/)                                  |
+| [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/)                   | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/)                 | [OVH](https://go-acme.github.io/lego/dns/ovh/)                                  | [plesk.com](https://go-acme.github.io/lego/dns/plesk/)                          |
+| [Porkbun](https://go-acme.github.io/lego/dns/porkbun/)                          | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/)                            | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/)                      | [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/)                      |
+| [reg.ru](https://go-acme.github.io/lego/dns/regru/)                             | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/)                          | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/)                  | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/)                 |
+| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/)                        | [Selectel](https://go-acme.github.io/lego/dns/selectel/)                        | [Servercow](https://go-acme.github.io/lego/dns/servercow/)                      | [Simply.com](https://go-acme.github.io/lego/dns/simply/)                        |
+| [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/)           | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          |
+| [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/)                   | [Ultradns](https://go-acme.github.io/lego/dns/ultradns/)                        | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/)                    | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          |
+| [Vercel](https://go-acme.github.io/lego/dns/vercel/)                            | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/)                         |
+| [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [Websupport](https://go-acme.github.io/lego/dns/websupport/)                    | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              |
+| [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/)                 | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/)                        | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |
 
 <!-- END DNS PROVIDERS LIST -->
 
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go
index 6454f40c..f7337b92 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -49,6 +49,7 @@ func allDNSCodes() string {
 		"dynu",
 		"easydns",
 		"edgedns",
+		"efficientip",
 		"epik",
 		"exec",
 		"exoscale",
@@ -889,6 +890,30 @@ func displayDNSHelp(w io.Writer, name string) error {
 		ew.writeln()
 		ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgedns`)
 
+	case "efficientip":
+		// generated from: providers/dns/efficientip/efficientip.toml
+		ew.writeln(`Configuration for Efficient IP.`)
+		ew.writeln(`Code:	'efficientip'`)
+		ew.writeln(`Since:	'v4.13.0'`)
+		ew.writeln()
+
+		ew.writeln(`Credentials:`)
+		ew.writeln(`	- "EFFICIENTIP_DNS_NAME":	DNS name (ex: dns.smart)`)
+		ew.writeln(`	- "EFFICIENTIP_HOSTNAME":	Hostname (ex: foo.example.com)`)
+		ew.writeln(`	- "EFFICIENTIP_PASSWORD":	Password`)
+		ew.writeln(`	- "EFFICIENTIP_USERNAME":	Username`)
+		ew.writeln()
+
+		ew.writeln(`Additional Configuration:`)
+		ew.writeln(`	- "EFFICIENTIP_HTTP_TIMEOUT":	API request timeout`)
+		ew.writeln(`	- "EFFICIENTIP_POLLING_INTERVAL":	Time between DNS propagation check`)
+		ew.writeln(`	- "EFFICIENTIP_PROPAGATION_TIMEOUT":	Maximum waiting time for DNS propagation`)
+		ew.writeln(`	- "EFFICIENTIP_TTL":	The TTL of the TXT record used for the DNS challenge`)
+		ew.writeln(`	- "EFFICIENTIP_VIEW_NAME":	View name (ex: external)`)
+
+		ew.writeln()
+		ew.writeln(`More information: https://go-acme.github.io/lego/dns/efficientip`)
+
 	case "epik":
 		// generated from: providers/dns/epik/epik.toml
 		ew.writeln(`Configuration for Epik.`)
diff --git a/docs/content/dns/zz_gen_efficientip.md b/docs/content/dns/zz_gen_efficientip.md
new file mode 100644
index 00000000..129499b5
--- /dev/null
+++ b/docs/content/dns/zz_gen_efficientip.md
@@ -0,0 +1,71 @@
+---
+title: "Efficient IP"
+date: 2019-03-03T16:39:46+01:00
+draft: false
+slug: efficientip
+dnsprovider:
+  since:    "v4.13.0"
+  code:     "efficientip"
+  url:      "https://efficientip.com/"
+---
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/efficientip/efficientip.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+
+
+Configuration for [Efficient IP](https://efficientip.com/).
+
+
+<!--more-->
+
+- Code: `efficientip`
+- Since: v4.13.0
+
+
+Here is an example bash command using the Efficient IP provider:
+
+```bash
+EFFICIENTIP_USERNAME="user" \
+EFFICIENTIP_PASSWORD="secret" \
+EFFICIENTIP_HOSTNAME="ipam.example.org" \
+EFFICIENTIP_DNS_NAME="dns.smart" \
+lego --email you@example.com --dns efficientip --domains my.example.org run
+```
+
+
+
+
+## Credentials
+
+| Environment Variable Name | Description |
+|-----------------------|-------------|
+| `EFFICIENTIP_DNS_NAME` | DNS name (ex: dns.smart) |
+| `EFFICIENTIP_HOSTNAME` | Hostname (ex: foo.example.com) |
+| `EFFICIENTIP_PASSWORD` | Password |
+| `EFFICIENTIP_USERNAME` | Username |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{< ref "dns#configuration-and-credentials" >}}).
+
+
+## Additional Configuration
+
+| Environment Variable Name | Description |
+|--------------------------------|-------------|
+| `EFFICIENTIP_HTTP_TIMEOUT` | API request timeout |
+| `EFFICIENTIP_POLLING_INTERVAL` | Time between DNS propagation check |
+| `EFFICIENTIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
+| `EFFICIENTIP_TTL` | The TTL of the TXT record used for the DNS challenge |
+| `EFFICIENTIP_VIEW_NAME` | View name (ex: external) |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{< ref "dns#configuration-and-credentials" >}}).
+
+
+
+
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/efficientip/efficientip.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml
index 6e344a34..da433b3e 100644
--- a/docs/data/zz_cli_help.toml
+++ b/docs/data/zz_cli_help.toml
@@ -136,7 +136,7 @@ To display the documentation for a specific DNS provider, run:
   $ lego dnshelp -c code
 
 Supported DNS providers:
-  acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, derak, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, websupport, wedos, yandex, yandexcloud, zoneee, zonomi
+  acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, derak, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, websupport, wedos, yandex, yandexcloud, zoneee, zonomi
 
 More information: https://go-acme.github.io/lego/dns
 """
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index 7aeeb33f..f1094d6e 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -40,6 +40,7 @@ import (
 	"github.com/go-acme/lego/v4/providers/dns/dynu"
 	"github.com/go-acme/lego/v4/providers/dns/easydns"
 	"github.com/go-acme/lego/v4/providers/dns/edgedns"
+	"github.com/go-acme/lego/v4/providers/dns/efficientip"
 	"github.com/go-acme/lego/v4/providers/dns/epik"
 	"github.com/go-acme/lego/v4/providers/dns/exec"
 	"github.com/go-acme/lego/v4/providers/dns/exoscale"
@@ -198,6 +199,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
 		return easydns.NewDNSProvider()
 	case "edgedns", "fastdns": // "fastdns" is for compatibility with v3, must be dropped in v5
 		return edgedns.NewDNSProvider()
+	case "efficientip":
+		return efficientip.NewDNSProvider()
 	case "epik":
 		return epik.NewDNSProvider()
 	case "exec":
diff --git a/providers/dns/efficientip/efficientip.go b/providers/dns/efficientip/efficientip.go
new file mode 100644
index 00000000..cf532e7c
--- /dev/null
+++ b/providers/dns/efficientip/efficientip.go
@@ -0,0 +1,152 @@
+// Package efficientip implements a DNS provider for solving the DNS-01 challenge using Efficient IP.
+package efficientip
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/go-acme/lego/v4/challenge/dns01"
+	"github.com/go-acme/lego/v4/platform/config/env"
+	"github.com/go-acme/lego/v4/providers/dns/efficientip/internal"
+)
+
+// Environment variables names.
+const (
+	envNamespace = "EFFICIENTIP_"
+
+	EnvUsername = envNamespace + "USERNAME"
+	EnvPassword = envNamespace + "PASSWORD"
+	EnvHostname = envNamespace + "HOSTNAME"
+	EnvDNSName  = envNamespace + "DNS_NAME"
+	EnvViewName = envNamespace + "VIEW_NAME"
+
+	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
+	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL"
+	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT"
+)
+
+// Config is used to configure the creation of the DNSProvider.
+type Config struct {
+	Username           string
+	Password           string
+	Hostname           string
+	DNSName            string
+	ViewName           string
+	PropagationTimeout time.Duration
+	PollingInterval    time.Duration
+	HTTPClient         *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+	return &Config{
+		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
+		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
+		HTTPClient: &http.Client{
+			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second),
+		},
+	}
+}
+
+// DNSProvider implements the challenge.Provider interface.
+type DNSProvider struct {
+	config *Config
+	client *internal.Client
+}
+
+// NewDNSProvider returns a new DNS provider
+// using environment variable EFFICIENTIP_API_KEY for adding and removing the DNS record.
+func NewDNSProvider() (*DNSProvider, error) {
+	values, err := env.Get(EnvUsername, EnvPassword, EnvHostname, EnvDNSName)
+	if err != nil {
+		return nil, fmt.Errorf("efficientip: %w", err)
+	}
+
+	config := NewDefaultConfig()
+	config.Username = values[EnvUsername]
+	config.Password = values[EnvPassword]
+	config.Hostname = values[EnvHostname]
+	config.DNSName = values[EnvDNSName]
+	config.ViewName = env.GetOrDefaultString(EnvViewName, "")
+
+	return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Efficient IP.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+	if config == nil {
+		return nil, errors.New("efficientip: the configuration of the DNS provider is nil")
+	}
+
+	if config.Username == "" {
+		return nil, errors.New("efficientip: missing username")
+	}
+	if config.Password == "" {
+		return nil, errors.New("efficientip: missing password")
+	}
+	if config.Hostname == "" {
+		return nil, errors.New("efficientip: missing hostname")
+	}
+	if config.DNSName == "" {
+		return nil, errors.New("efficientip: missing dnsname")
+	}
+
+	client := internal.NewClient(config.Hostname, config.Username, config.Password)
+
+	if config.HTTPClient != nil {
+		client.HTTPClient = config.HTTPClient
+	}
+
+	return &DNSProvider{config: config, client: client}, nil
+}
+
+func (d *DNSProvider) Present(domain, _, keyAuth string) error {
+	info := dns01.GetChallengeInfo(domain, keyAuth)
+
+	ctx := context.Background()
+
+	r := internal.ResourceRecord{
+		RRName:      dns01.UnFqdn(info.EffectiveFQDN),
+		RRType:      "TXT",
+		Value1:      info.Value,
+		DNSName:     d.config.DNSName,
+		DNSViewName: d.config.ViewName,
+	}
+
+	_, err := d.client.AddRecord(ctx, r)
+	if err != nil {
+		return fmt.Errorf("efficientip: add record: %w", err)
+	}
+
+	return nil
+}
+
+func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error {
+	info := dns01.GetChallengeInfo(domain, keyAuth)
+
+	ctx := context.Background()
+
+	params := internal.DeleteInputParameters{
+		RRName:      dns01.UnFqdn(info.EffectiveFQDN),
+		RRType:      "TXT",
+		RRValue1:    info.Value,
+		DNSName:     d.config.DNSName,
+		DNSViewName: d.config.ViewName,
+	}
+
+	_, err := d.client.DeleteRecord(ctx, params)
+	if err != nil {
+		return fmt.Errorf("efficientip: delete record: %w", err)
+	}
+
+	return nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+	return d.config.PropagationTimeout, d.config.PollingInterval
+}
diff --git a/providers/dns/efficientip/efficientip.toml b/providers/dns/efficientip/efficientip.toml
new file mode 100644
index 00000000..278701e0
--- /dev/null
+++ b/providers/dns/efficientip/efficientip.toml
@@ -0,0 +1,26 @@
+Name = "Efficient IP"
+Description = ''''''
+URL = "https://efficientip.com/"
+Code = "efficientip"
+Since = "v4.13.0"
+
+Example = '''
+EFFICIENTIP_USERNAME="user" \
+EFFICIENTIP_PASSWORD="secret" \
+EFFICIENTIP_HOSTNAME="ipam.example.org" \
+EFFICIENTIP_DNS_NAME="dns.smart" \
+lego --email you@example.com --dns efficientip --domains my.example.org run
+'''
+
+[Configuration]
+  [Configuration.Credentials]
+    EFFICIENTIP_USERNAME = "Username"
+    EFFICIENTIP_PASSWORD = "Password"
+    EFFICIENTIP_HOSTNAME = "Hostname (ex: foo.example.com)"
+    EFFICIENTIP_DNS_NAME = "DNS name (ex: dns.smart)"
+  [Configuration.Additional]
+    EFFICIENTIP_VIEW_NAME = "View name (ex: external)"
+    EFFICIENTIP_POLLING_INTERVAL = "Time between DNS propagation check"
+    EFFICIENTIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
+    EFFICIENTIP_TTL = "The TTL of the TXT record used for the DNS challenge"
+    EFFICIENTIP_HTTP_TIMEOUT = "API request timeout"
diff --git a/providers/dns/efficientip/internal/client.go b/providers/dns/efficientip/internal/client.go
new file mode 100644
index 00000000..2fea76a1
--- /dev/null
+++ b/providers/dns/efficientip/internal/client.go
@@ -0,0 +1,209 @@
+package internal
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
+	querystring "github.com/google/go-querystring/query"
+)
+
+type Client struct {
+	baseURL    *url.URL
+	HTTPClient *http.Client
+
+	username string
+	password string
+}
+
+func NewClient(hostname string, username string, password string) *Client {
+	baseURL, _ := url.Parse(fmt.Sprintf("https://%s/rest/", hostname))
+
+	return &Client{
+		HTTPClient: &http.Client{Timeout: 5 * time.Second},
+		baseURL:    baseURL,
+		username:   username,
+		password:   password,
+	}
+}
+
+func (c Client) ListRecords(ctx context.Context) ([]ResourceRecord, error) {
+	endpoint := c.baseURL.JoinPath("dns_rr_list")
+
+	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var result []ResourceRecord
+
+	err = c.do(req, &result)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (c Client) GetRecord(ctx context.Context, id string) (*ResourceRecord, error) {
+	endpoint := c.baseURL.JoinPath("dns_rr_info")
+
+	query := endpoint.Query()
+	query.Set("rr_id", id)
+	endpoint.RawQuery = query.Encode()
+
+	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var result []ResourceRecord
+
+	err = c.do(req, &result)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(result) == 0 {
+		return nil, nil
+	}
+
+	return &result[0], nil
+}
+
+func (c Client) AddRecord(ctx context.Context, record ResourceRecord) (*BaseOutput, error) {
+	endpoint := c.baseURL.JoinPath("dns_rr_add")
+
+	req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
+	if err != nil {
+		return nil, err
+	}
+
+	var result []BaseOutput
+
+	err = c.do(req, &result)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(result) == 0 {
+		return nil, nil
+	}
+
+	return &result[0], nil
+}
+
+func (c Client) DeleteRecord(ctx context.Context, params DeleteInputParameters) (*BaseOutput, error) {
+	endpoint := c.baseURL.JoinPath("dns_rr_delete")
+
+	// (rr_id || (rr_name && (dns_id || dns_name || hostaddr)))
+
+	v, err := querystring.Values(params)
+	if err != nil {
+		return nil, fmt.Errorf("query parameters: %w", err)
+	}
+	endpoint.RawQuery = v.Encode()
+
+	req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var result []BaseOutput
+
+	err = c.do(req, &result)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(result) == 0 {
+		return nil, nil
+	}
+
+	return &result[0], nil
+}
+
+func (c Client) do(req *http.Request, result any) error {
+	req.SetBasicAuth(c.username, c.password)
+	req.Header.Set("cache-control", "no-cache")
+
+	resp, err := c.HTTPClient.Do(req)
+	if err != nil {
+		return errutils.NewHTTPDoError(req, err)
+	}
+
+	defer func() { _ = resp.Body.Close() }()
+
+	switch req.Method {
+	case http.MethodPost:
+		if resp.StatusCode != http.StatusCreated {
+			return parseError(req, resp)
+		}
+	default:
+		if resp.StatusCode == http.StatusNoContent {
+			return nil
+		}
+
+		if resp.StatusCode != http.StatusOK {
+			return parseError(req, resp)
+		}
+	}
+
+	if result == nil {
+		return nil
+	}
+
+	raw, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return errutils.NewReadResponseError(req, resp.StatusCode, err)
+	}
+
+	err = json.Unmarshal(raw, result)
+	if err != nil {
+		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
+	}
+
+	return nil
+}
+
+func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
+	buf := new(bytes.Buffer)
+
+	if payload != nil {
+		err := json.NewEncoder(buf).Encode(payload)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
+		}
+	}
+
+	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
+	if err != nil {
+		return nil, fmt.Errorf("unable to create request: %w", err)
+	}
+
+	req.Header.Set("Accept", "application/json")
+
+	if payload != nil {
+		req.Header.Set("Content-Type", "application/json")
+	}
+
+	return req, nil
+}
+
+func parseError(req *http.Request, resp *http.Response) error {
+	raw, _ := io.ReadAll(resp.Body)
+
+	var response APIError
+	err := json.Unmarshal(raw, &response)
+	if err != nil {
+		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
+	}
+
+	return fmt.Errorf("[status code %d] %w", resp.StatusCode, response)
+}
diff --git a/providers/dns/efficientip/internal/fixtures/dns_rr_add.json b/providers/dns/efficientip/internal/fixtures/dns_rr_add.json
new file mode 100644
index 00000000..49932c25
--- /dev/null
+++ b/providers/dns/efficientip/internal/fixtures/dns_rr_add.json
@@ -0,0 +1,5 @@
+[
+  {
+    "ret_oid": "239"
+  }
+]
diff --git a/providers/dns/efficientip/internal/fixtures/dns_rr_delete-error.json b/providers/dns/efficientip/internal/fixtures/dns_rr_delete-error.json
new file mode 100644
index 00000000..7eb0db64
--- /dev/null
+++ b/providers/dns/efficientip/internal/fixtures/dns_rr_delete-error.json
@@ -0,0 +1,6 @@
+{
+  "errno": "20117",
+  "errmsg": "This RR does not exist",
+  "severity": "error",
+  "category": "dns_rr_delete"
+}
diff --git a/providers/dns/efficientip/internal/fixtures/dns_rr_delete.json b/providers/dns/efficientip/internal/fixtures/dns_rr_delete.json
new file mode 100644
index 00000000..d794fba8
--- /dev/null
+++ b/providers/dns/efficientip/internal/fixtures/dns_rr_delete.json
@@ -0,0 +1,5 @@
+[
+  {
+    "ret_oid": "251"
+  }
+]
diff --git a/providers/dns/efficientip/internal/fixtures/dns_rr_info.json b/providers/dns/efficientip/internal/fixtures/dns_rr_info.json
new file mode 100644
index 00000000..8a2f6d02
--- /dev/null
+++ b/providers/dns/efficientip/internal/fixtures/dns_rr_info.json
@@ -0,0 +1,64 @@
+[
+  {
+    "errno": "0",
+    "rr_all_value": "test1",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "test.lego.example.com",
+    "rr_full_name_utf": "test.lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": "test",
+    "rr_type": "TXT",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "test1",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "239",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "26",
+    "rr_value_id": "274",
+    "rr_type_id": "6",
+    "rr_glue_id": "21",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  }
+]
diff --git a/providers/dns/efficientip/internal/fixtures/dns_rr_list.json b/providers/dns/efficientip/internal/fixtures/dns_rr_list.json
new file mode 100644
index 00000000..2371eafd
--- /dev/null
+++ b/providers/dns/efficientip/internal/fixtures/dns_rr_list.json
@@ -0,0 +1,436 @@
+[
+  {
+    "errno": "0",
+    "rr_all_value": "test1",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "test.lego.example.com",
+    "rr_full_name_utf": "test.lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": "test",
+    "rr_type": "TXT",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "test1",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "239",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "26",
+    "rr_value_id": "274",
+    "rr_type_id": "6",
+    "rr_glue_id": "21",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  },
+  {
+    "errno": "0",
+    "rr_all_value": "test2",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "test.lego.example.com",
+    "rr_full_name_utf": "test.lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": "test",
+    "rr_type": "TXT",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "test2",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "241",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "26",
+    "rr_value_id": "275",
+    "rr_type_id": "6",
+    "rr_glue_id": "21",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  },
+  {
+    "errno": "0",
+    "rr_all_value": "test1",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "lego.example.com",
+    "rr_full_name_utf": "lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": ".",
+    "rr_type": "TXT",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "test1",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "245",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "21",
+    "rr_value_id": "274",
+    "rr_type_id": "6",
+    "rr_glue_id": "3",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  },
+  {
+    "errno": "0",
+    "rr_all_value": "test2",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "lego.example.com",
+    "rr_full_name_utf": "lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": ".",
+    "rr_type": "TXT",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "test2",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "247",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "21",
+    "rr_value_id": "275",
+    "rr_type_id": "6",
+    "rr_glue_id": "3",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  },
+  {
+    "errno": "0",
+    "rr_all_value": "dns.smart, root@lego.example.com, 2023062719, 1200, 600, 1209600, 3600",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "lego.example.com",
+    "rr_full_name_utf": "lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": ".",
+    "rr_type": "SOA",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "dns.smart",
+    "value2": "root@lego.example.com",
+    "value3": "2023062719",
+    "value4": "1200",
+    "value5": "600",
+    "value6": "1209600",
+    "value7": "3600",
+    "dnszone_id": "9",
+    "rr_id": "201",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "21",
+    "rr_value_id": "282",
+    "rr_type_id": "2",
+    "rr_glue_id": "3",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  },
+  {
+    "errno": "0",
+    "rr_all_value": "dns.smart",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "lego.example.com",
+    "rr_full_name_utf": "lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "",
+    "rr_value_ip4_addr": "",
+    "rr_glue": ".",
+    "rr_type": "NS",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "dns.smart",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "200",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "21",
+    "rr_value_id": "10",
+    "rr_type_id": "1",
+    "rr_glue_id": "3",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  },
+  {
+    "errno": "0",
+    "rr_all_value": "127.0.0.1",
+    "dnszone_sort_zone": "lego.example.com",
+    "dnszone_is_rpz": "0",
+    "dnszone_type": "master",
+    "rr_full_name": "loopback.lego.example.com",
+    "rr_full_name_utf": "loopback.lego.example.com",
+    "rr_name_ip_addr": "",
+    "rr_name_ip4_addr": "",
+    "rr_value_ip_addr": "7f000001",
+    "rr_value_ip4_addr": "7f000001",
+    "rr_glue": "loopback",
+    "rr_type": "A",
+    "ttl": "3600",
+    "delayed_time": "0",
+    "rr_class_name": "",
+    "value1": "127.0.0.1",
+    "value2": "",
+    "value3": "",
+    "value4": "",
+    "value5": "",
+    "value6": "",
+    "value7": "",
+    "dnszone_id": "9",
+    "rr_id": "208",
+    "dns_id": "3",
+    "dnszone_name_utf": "lego.example.com",
+    "dnszone_name": "lego.example.com",
+    "dns_name": "dns.smart",
+    "dns_type": "vdns",
+    "dns_cloud": "0",
+    "vdns_parent_id": "0",
+    "dnsview_name": "#",
+    "dnsview_class_name": "",
+    "dnsview_id": "0",
+    "dnszone_site_name": "#",
+    "dnszone_is_reverse": "0",
+    "dnszone_masters": "",
+    "vdns_parent_name": "#",
+    "dnszone_forwarders": "",
+    "dns_class_name": "",
+    "dnszone_class_name": "",
+    "dns_version": "",
+    "dns_comment": "",
+    "delayed_create_time": "0",
+    "delayed_delete_time": "0",
+    "multistatus": "",
+    "rr_auth_gsstsig": "0",
+    "rr_last_update_time": "",
+    "rr_last_update_days": "",
+    "rr_name_id": "22",
+    "rr_value_id": "237",
+    "rr_type_id": "3",
+    "rr_glue_id": "17",
+    "dnsview_class_parameters": "",
+    "dnsview_class_parameters_properties": "",
+    "dnsview_class_parameters_inheritance_source": "",
+    "rr_class_parameters": "",
+    "rr_class_parameters_properties": "",
+    "rr_class_parameters_inheritance_source": ""
+  }
+]
diff --git a/providers/dns/efficientip/internal/types.go b/providers/dns/efficientip/internal/types.go
new file mode 100644
index 00000000..017089a5
--- /dev/null
+++ b/providers/dns/efficientip/internal/types.go
@@ -0,0 +1,109 @@
+package internal
+
+import "fmt"
+
+type ResourceRecord struct {
+	ErrorCode string `json:"errno,omitempty"`
+
+	DelayedCreateTime                       string `json:"delayed_create_time,omitempty"`
+	DelayedDeleteTime                       string `json:"delayed_delete_time,omitempty"`
+	DelayedTime                             string `json:"delayed_time,omitempty"`
+	DNSClassName                            string `json:"dns_class_name,omitempty"`
+	DNSCloud                                string `json:"dns_cloud,omitempty"`
+	DNSComment                              string `json:"dns_comment,omitempty"`
+	DNSID                                   string `json:"dns_id,omitempty"`
+	DNSName                                 string `json:"dns_name,omitempty"`
+	DNSType                                 string `json:"dns_type,omitempty"`
+	DNSVersion                              string `json:"dns_version,omitempty"`
+	DNSViewClassName                        string `json:"dnsview_class_name,omitempty"`
+	DNSViewClassParameters                  string `json:"dnsview_class_parameters,omitempty"`
+	DNSViewClassParametersInheritanceSource string `json:"dnsview_class_parameters_inheritance_source,omitempty"`
+	DNSViewClassParametersProperties        string `json:"dnsview_class_parameters_properties,omitempty"`
+	DNSViewID                               string `json:"dnsview_id,omitempty"`
+	DNSViewName                             string `json:"dnsview_name,omitempty"`
+	DNSZoneClassName                        string `json:"dnszone_class_name,omitempty"`
+	DNSZoneForwarders                       string `json:"dnszone_forwarders,omitempty"`
+	DNSZoneID                               string `json:"dnszone_id,omitempty"`
+	DNSZoneIsReverse                        string `json:"dnszone_is_reverse,omitempty"`
+	DNSZoneIsRpz                            string `json:"dnszone_is_rpz,omitempty"`
+	DNSZoneMasters                          string `json:"dnszone_masters,omitempty"`
+	DNSZoneName                             string `json:"dnszone_name,omitempty"`
+	DNSZoneNameUTF                          string `json:"dnszone_name_utf,omitempty"`
+	DNSZoneSiteName                         string `json:"dnszone_site_name,omitempty"`
+	DNSZoneSortZone                         string `json:"dnszone_sort_zone,omitempty"`
+	DNSZoneType                             string `json:"dnszone_type,omitempty"`
+	MultiStatus                             string `json:"multistatus,omitempty"`
+	RRAllValue                              string `json:"rr_all_value,omitempty"`
+	RRAuthGsstsig                           string `json:"rr_auth_gsstsig,omitempty"`
+	RRClassName                             string `json:"rr_class_name,omitempty"`
+	RRClassParameters                       string `json:"rr_class_parameters,omitempty"`
+	RRClassParametersInheritanceSource      string `json:"rr_class_parameters_inheritance_source,omitempty"`
+	RRClassParametersProperties             string `json:"rr_class_parameters_properties,omitempty"`
+	RRFullName                              string `json:"rr_full_name,omitempty"`
+	RRFullNameUTF                           string `json:"rr_full_name_utf,omitempty"`
+	RRGlue                                  string `json:"rr_glue,omitempty"`
+	RRGlueID                                string `json:"rr_glue_id,omitempty"`
+	RRID                                    string `json:"rr_id,omitempty"`
+	RRLastUpdateDays                        string `json:"rr_last_update_days,omitempty"`
+	RRLastUpdateTime                        string `json:"rr_last_update_time,omitempty"`
+	RRName                                  string `json:"rr_name,omitempty"`
+	RRNameID                                string `json:"rr_name_id,omitempty"`
+	RRNameIP4Addr                           string `json:"rr_name_ip4_addr,omitempty"`
+	RRNameIPAddr                            string `json:"rr_name_ip_addr,omitempty"`
+	RRType                                  string `json:"rr_type,omitempty"`
+	RRTypeID                                string `json:"rr_type_id,omitempty"`
+	RRValueID                               string `json:"rr_value_id,omitempty"`
+	RRValueIP4Addr                          string `json:"rr_value_ip4_addr,omitempty"`
+	RRValueIPAddr                           string `json:"rr_value_ip_addr,omitempty"`
+	TTL                                     string `json:"ttl,omitempty"`
+	Value1                                  string `json:"value1,omitempty"`
+	Value2                                  string `json:"value2,omitempty"`
+	Value3                                  string `json:"value3,omitempty"`
+	Value4                                  string `json:"value4,omitempty"`
+	Value5                                  string `json:"value5,omitempty"`
+	Value6                                  string `json:"value6,omitempty"`
+	Value7                                  string `json:"value7,omitempty"`
+	VDNSParentID                            string `json:"vdns_parent_id,omitempty"`
+	VDNSParentName                          string `json:"vdns_parent_name,omitempty"`
+}
+
+type DeleteInputParameters struct {
+	RRID        string `url:"rr_id,omitempty"`
+	DNSName     string `url:"dns_name,omitempty"`
+	DNSViewName string `url:"dnsview_name,omitempty"`
+	RRName      string `url:"rr_name,omitempty"`
+	RRType      string `url:"rr_type,omitempty"`
+	RRValue1    string `url:"rr_value1,omitempty"`
+}
+
+type BaseOutput struct {
+	RetOID string `json:"ret_oid,omitempty"`
+}
+
+type APIError struct {
+	ErrorCode   string `json:"errno,omitempty"`
+	ErrMsg      string `json:"errmsg,omitempty"`
+	Severity    string `json:"severity,omitempty"`
+	Category    string `json:"category,omitempty"`
+	Parameters  string `json:"parameters,omitempty"`
+	ParamFormat string `json:"param_format,omitempty"`
+	ParamValue  string `json:"param_value,omitempty"`
+}
+
+func (a APIError) Error() string {
+	msg := fmt.Sprintf("%s: %s %s %s", a.Category, a.Severity, a.ErrorCode, a.ErrMsg)
+
+	if a.Parameters != "" {
+		msg += fmt.Sprintf(" parameters: %s", a.Parameters)
+	}
+
+	if a.ParamFormat != "" {
+		msg += fmt.Sprintf(" param_format: %s", a.ParamFormat)
+	}
+
+	if a.ParamValue != "" {
+		msg += fmt.Sprintf(" param_value: %s", a.ParamValue)
+	}
+
+	return msg
+}


Test patch:
diff --git a/providers/dns/efficientip/efficientip_test.go b/providers/dns/efficientip/efficientip_test.go
new file mode 100644
index 00000000..3ee2da77
--- /dev/null
+++ b/providers/dns/efficientip/efficientip_test.go
@@ -0,0 +1,201 @@
+package efficientip
+
+import (
+	"testing"
+	"time"
+
+	"github.com/go-acme/lego/v4/platform/tester"
+	"github.com/stretchr/testify/require"
+)
+
+const envDomain = envNamespace + "DOMAIN"
+
+var envTest = tester.NewEnvTest(
+	EnvUsername,
+	EnvPassword,
+	EnvHostname,
+	EnvDNSName,
+	EnvViewName,
+).WithDomain(envDomain)
+
+func TestNewDNSProvider(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		envVars  map[string]string
+		expected string
+	}{
+		{
+			desc: "success",
+			envVars: map[string]string{
+				EnvUsername: "user",
+				EnvPassword: "secret",
+				EnvHostname: "example.com",
+				EnvDNSName:  "dns.smart",
+			},
+		},
+		{
+			desc: "missing username",
+			envVars: map[string]string{
+				EnvUsername: "",
+				EnvPassword: "secret",
+				EnvHostname: "example.com",
+				EnvDNSName:  "dns.smart",
+			},
+			expected: "efficientip: some credentials information are missing: EFFICIENTIP_USERNAME",
+		},
+		{
+			desc: "missing password",
+			envVars: map[string]string{
+				EnvUsername: "user",
+				EnvPassword: "",
+				EnvHostname: "example.com",
+				EnvDNSName:  "dns.smart",
+			},
+			expected: "efficientip: some credentials information are missing: EFFICIENTIP_PASSWORD",
+		},
+		{
+			desc: "missing hostname",
+			envVars: map[string]string{
+				EnvUsername: "user",
+				EnvPassword: "secret",
+				EnvHostname: "",
+				EnvDNSName:  "dns.smart",
+			},
+			expected: "efficientip: some credentials information are missing: EFFICIENTIP_HOSTNAME",
+		},
+		{
+			desc: "missing DNS name",
+			envVars: map[string]string{
+				EnvUsername: "user",
+				EnvPassword: "secret",
+				EnvHostname: "example.com",
+				EnvDNSName:  "",
+			},
+			expected: "efficientip: some credentials information are missing: EFFICIENTIP_DNS_NAME",
+		},
+		{
+			desc:     "missing credentials",
+			envVars:  map[string]string{},
+			expected: "efficientip: some credentials information are missing: EFFICIENTIP_USERNAME,EFFICIENTIP_PASSWORD,EFFICIENTIP_HOSTNAME,EFFICIENTIP_DNS_NAME",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			defer envTest.RestoreEnv()
+			envTest.ClearEnv()
+
+			envTest.Apply(test.envVars)
+
+			p, err := NewDNSProvider()
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		username string
+		password string
+		hostname string
+		dnsName  string
+		expected string
+	}{
+		{
+			desc:     "success",
+			username: "user",
+			password: "secret",
+			hostname: "example.com",
+			dnsName:  "dns.smart",
+		},
+		{
+			desc:     "missing username",
+			password: "secret",
+			hostname: "example.com",
+			dnsName:  "dns.smart",
+			expected: "efficientip: missing username",
+		},
+		{
+			desc:     "missing password",
+			username: "user",
+			hostname: "example.com",
+			dnsName:  "dns.smart",
+			expected: "efficientip: missing password",
+		},
+		{
+			desc:     "missing hostname",
+			username: "user",
+			password: "secret",
+			dnsName:  "dns.smart",
+			expected: "efficientip: missing hostname",
+		},
+		{
+			desc:     "missing dnsName",
+			username: "user",
+			password: "secret",
+			hostname: "example.com",
+			expected: "efficientip: missing dnsname",
+		},
+		{
+			desc:     "missing all",
+			expected: "efficientip: missing username",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			config := NewDefaultConfig()
+
+			config.Username = test.username
+			config.Password = test.password
+			config.Hostname = test.hostname
+			config.DNSName = test.dnsName
+
+			p, err := NewDNSProviderConfig(config)
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestLivePresent(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.Present(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
+
+func TestLiveCleanUp(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	time.Sleep(1 * time.Second)
+
+	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
diff --git a/providers/dns/efficientip/internal/client_test.go b/providers/dns/efficientip/internal/client_test.go
new file mode 100644
index 00000000..a766c908
--- /dev/null
+++ b/providers/dns/efficientip/internal/client_test.go
@@ -0,0 +1,427 @@
+package internal
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func setupTest(t *testing.T, method, pattern string, status int, file string) *Client {
+	t.Helper()
+
+	mux := http.NewServeMux()
+	server := httptest.NewServer(mux)
+	t.Cleanup(server.Close)
+
+	mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) {
+		if req.Method != method {
+			http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest)
+			return
+		}
+
+		username, password, ok := req.BasicAuth()
+		if !ok {
+			http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+			return
+		}
+
+		if username != "user" {
+			http.Error(rw, fmt.Sprintf("username: want %s got %s", username, "user"), http.StatusUnauthorized)
+			return
+		}
+
+		if password != "secret" {
+			http.Error(rw, fmt.Sprintf("password: want %s got %s", password, "secret"), http.StatusUnauthorized)
+			return
+		}
+
+		open, err := os.Open(filepath.Join("fixtures", file))
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		defer func() { _ = open.Close() }()
+
+		rw.WriteHeader(status)
+		_, err = io.Copy(rw, open)
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+	})
+
+	srvURL, _ := url.Parse(server.URL)
+
+	client := NewClient(srvURL.Host, "user", "secret")
+	client.HTTPClient = server.Client()
+	client.baseURL, _ = url.Parse(server.URL)
+
+	return client
+}
+
+func TestListRecords(t *testing.T) {
+	client := setupTest(t, http.MethodGet, "/dns_rr_list", http.StatusOK, "dns_rr_list.json")
+
+	ctx := context.Background()
+
+	records, err := client.ListRecords(ctx)
+	require.NoError(t, err)
+
+	expected := []ResourceRecord{
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "test1",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "test.lego.example.com",
+			RRFullNameUTF:     "test.lego.example.com",
+			RRGlue:            "test",
+			RRGlueID:          "21",
+			RRID:              "239",
+			RRNameID:          "26",
+			RRType:            "TXT",
+			RRTypeID:          "6",
+			RRValueID:         "274",
+			TTL:               "3600",
+			Value1:            "test1",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "test2",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "test.lego.example.com",
+			RRFullNameUTF:     "test.lego.example.com",
+			RRGlue:            "test",
+			RRGlueID:          "21",
+			RRID:              "241",
+			RRNameID:          "26",
+			RRType:            "TXT",
+			RRTypeID:          "6",
+			RRValueID:         "275",
+			TTL:               "3600",
+			Value1:            "test2",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "test1",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "lego.example.com",
+			RRFullNameUTF:     "lego.example.com",
+			RRGlue:            ".",
+			RRGlueID:          "3",
+			RRID:              "245",
+			RRNameID:          "21",
+			RRType:            "TXT",
+			RRTypeID:          "6",
+			RRValueID:         "274",
+			TTL:               "3600",
+			Value1:            "test1",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "test2",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "lego.example.com",
+			RRFullNameUTF:     "lego.example.com",
+			RRGlue:            ".",
+			RRGlueID:          "3",
+			RRID:              "247",
+			RRNameID:          "21",
+			RRType:            "TXT",
+			RRTypeID:          "6",
+			RRValueID:         "275",
+			TTL:               "3600",
+			Value1:            "test2",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "dns.smart, root@lego.example.com, 2023062719, 1200, 600, 1209600, 3600",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "lego.example.com",
+			RRFullNameUTF:     "lego.example.com",
+			RRGlue:            ".",
+			RRGlueID:          "3",
+			RRID:              "201",
+			RRNameID:          "21",
+			RRType:            "SOA",
+			RRTypeID:          "2",
+			RRValueID:         "282",
+			TTL:               "3600",
+			Value1:            "dns.smart",
+			Value2:            "root@lego.example.com",
+			Value3:            "2023062719",
+			Value4:            "1200",
+			Value5:            "600",
+			Value6:            "1209600",
+			Value7:            "3600",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "dns.smart",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "lego.example.com",
+			RRFullNameUTF:     "lego.example.com",
+			RRGlue:            ".",
+			RRGlueID:          "3",
+			RRID:              "200",
+			RRNameID:          "21",
+			RRType:            "NS",
+			RRTypeID:          "1",
+			RRValueID:         "10",
+			TTL:               "3600",
+			Value1:            "dns.smart",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+		{
+			ErrorCode:         "0",
+			DelayedCreateTime: "0",
+			DelayedDeleteTime: "0",
+			DelayedTime:       "0",
+			DNSCloud:          "0",
+			DNSID:             "3",
+			DNSName:           "dns.smart",
+			DNSType:           "vdns",
+			DNSViewID:         "0",
+			DNSViewName:       "#",
+			DNSZoneID:         "9",
+			DNSZoneIsReverse:  "0",
+			DNSZoneIsRpz:      "0",
+			DNSZoneName:       "lego.example.com",
+			DNSZoneNameUTF:    "lego.example.com",
+			DNSZoneSiteName:   "#",
+			DNSZoneSortZone:   "lego.example.com",
+			DNSZoneType:       "master",
+			RRAllValue:        "127.0.0.1",
+			RRAuthGsstsig:     "0",
+			RRFullName:        "loopback.lego.example.com",
+			RRFullNameUTF:     "loopback.lego.example.com",
+			RRGlue:            "loopback",
+			RRGlueID:          "17",
+			RRID:              "208",
+			RRNameID:          "22",
+			RRType:            "A",
+			RRTypeID:          "3",
+			RRValueID:         "237",
+			RRValueIP4Addr:    "7f000001",
+			RRValueIPAddr:     "7f000001",
+			TTL:               "3600",
+			Value1:            "127.0.0.1",
+			VDNSParentID:      "0",
+			VDNSParentName:    "#",
+		},
+	}
+
+	assert.Equal(t, expected, records)
+}
+
+func TestGetRecord(t *testing.T) {
+	client := setupTest(t, http.MethodGet, "/dns_rr_info", http.StatusOK, "dns_rr_info.json")
+
+	ctx := context.Background()
+
+	record, err := client.GetRecord(ctx, "239")
+	require.NoError(t, err)
+
+	expected := &ResourceRecord{
+		ErrorCode:         "0",
+		DelayedCreateTime: "0",
+		DelayedDeleteTime: "0",
+		DelayedTime:       "0",
+		DNSCloud:          "0",
+		DNSID:             "3",
+		DNSName:           "dns.smart",
+		DNSType:           "vdns",
+		DNSViewID:         "0",
+		DNSViewName:       "#",
+		DNSZoneID:         "9",
+		DNSZoneIsReverse:  "0",
+		DNSZoneIsRpz:      "0",
+		DNSZoneName:       "lego.example.com",
+		DNSZoneNameUTF:    "lego.example.com",
+		DNSZoneSiteName:   "#",
+		DNSZoneSortZone:   "lego.example.com",
+		DNSZoneType:       "master",
+		RRAllValue:        "test1",
+		RRAuthGsstsig:     "0",
+		RRFullName:        "test.lego.example.com",
+		RRFullNameUTF:     "test.lego.example.com",
+		RRGlue:            "test",
+		RRGlueID:          "21",
+		RRID:              "239",
+		RRNameID:          "26",
+		RRType:            "TXT",
+		RRTypeID:          "6",
+		RRValueID:         "274",
+		TTL:               "3600",
+		Value1:            "test1",
+		VDNSParentID:      "0",
+		VDNSParentName:    "#",
+	}
+
+	assert.Equal(t, expected, record)
+}
+
+func TestAddRecord(t *testing.T) {
+	client := setupTest(t, http.MethodPost, "/dns_rr_add", http.StatusCreated, "dns_rr_add.json")
+
+	ctx := context.Background()
+
+	r := ResourceRecord{
+		RRName:      "test.example.com",
+		RRType:      "TXT",
+		Value1:      "test",
+		DNSName:     "dns.smart",
+		DNSViewName: "external",
+	}
+
+	resp, err := client.AddRecord(ctx, r)
+	require.NoError(t, err)
+
+	expected := &BaseOutput{RetOID: "239"}
+
+	assert.Equal(t, expected, resp)
+}
+
+func TestDeleteRecord(t *testing.T) {
+	client := setupTest(t, http.MethodDelete, "/dns_rr_delete", http.StatusOK, "dns_rr_delete.json")
+
+	ctx := context.Background()
+
+	resp, err := client.DeleteRecord(ctx, DeleteInputParameters{RRID: "251"})
+	require.NoError(t, err)
+
+	expected := &BaseOutput{RetOID: "251"}
+
+	assert.Equal(t, expected, resp)
+}
+
+func TestDeleteRecord_error(t *testing.T) {
+	client := setupTest(t, http.MethodDelete, "/dns_rr_delete", http.StatusBadRequest, "dns_rr_delete-error.json")
+
+	ctx := context.Background()
+
+	_, err := client.DeleteRecord(ctx, DeleteInputParameters{RRID: "251"})
+	require.ErrorAs(t, err, &APIError{})
+}