Repository: go-acme/lego (language: go)

Issue:
Support for provider: epik.com
### Welcome

- [X] Yes, I've searched similar issues on GitHub and didn't find any.
- [X] Yes, the DNS provider exposes a public API.
- [X] Yes, I know that the lego maintainers don't have an account in all DNS providers in the world.
- [ ] Yes, I'm able to create a pull request and be able to maintain the implementation.
- [X] Yes, I'm able to test an implementation if someone creates a pull request to add the support of this DNS provider.

### How do you use lego?

Binary

### Link to the DNS provider

https://www.epik.com

### Link to the API documentation

https://www.epik.com/support/knowledgebase/epik-domain-management-api/

https://docs.userapi.epik.com/v2/

### Additional Notes

thank you!


Discussion / hints:
Hello @marius-locomotion,

as the API documentation is a bit weak about the response of the API endpoint, I will need you to help me to get more information.

First, you will need a [Epik API signature](https://registrar.epik.com/account/api-settings/).

I will need the JSON response for the following calls:
- https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/getDnsRecord
- https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/createHostRecord
- https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/removeHostRecord

You should anonymize the JSON response to protect your data and then copy-paste the content here.

Thank you.

```
curl -X GET "https://usersapiv2.epik.com/v2/domains/MYDOMAIN.ORG/records?SIGNATURE=MYSIGNATURE" -H "accept: application/json"
```


```json
{
  "data": {
    "name": "MYDOMAIN.ORG",
    "code": 1000,
    "records": [
      {
        "id": "abc123",
        "name": "www",
        "type": "CAA",
        "data": "1 issue letsencrypt.org",
        "aux": 0,
        "ttl": 300
      },
      {
        "id": "abc123",
        "name": "www",
        "type": "A",
        "data": "192.64.147.249",
        "aux": 0,
        "ttl": 300
      },
      {
        "id": "abc123",
        "name": "*",
        "type": "A",
        "data": "192.64.147.249",
        "aux": 0,
        "ttl": 300
      },
      {
        "id": "abc123",
        "name": "",
        "type": "CAA",
        "data": "0 issue trust-provider.com",
        "aux": 0,
        "ttl": 300
      },
      {
        "id": "abc123",
        "name": "",
        "type": "CAA",
        "data": "1 issue letsencrypt.org",
        "aux": 0,
        "ttl": 300
      },
      {
        "id": "abc123",
        "name": "",
        "type": "A",
        "data": "192.64.147.249",
        "aux": 0,
        "ttl": 300
      }
    ]
  }
}
```






```
curl -X POST "https://usersapiv2.epik.com/v2/domains/MYDOMAIN.ORG/records?SIGNATURE=MYSIGNATURE" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"create_host_records_payload\": { \"HOST\": \"www2\", \"TYPE\": \"A\", \"DATA\": \"192.64.147.249\", \"AUX\": 0, \"TTL\": 300 }}"
```

response body:
```json
{
  "code": 1000,
  "message": "Command completed successfully.",
  "description": null
}
```

response headers
```
 cache-control: no-store, no-cache, must-revalidate 
 content-type: application/json 
 expires: Thu, 19 Nov 1981 08:52:00 GMT 
 pragma: no-cache
```





```
curl -X DELETE "https://usersapiv2.epik.com/v2/domains/MYDOMAIN.ORG/records?SIGNATURE=MYSIGNATURE&ID=RECORDID" -H "accept: application/json"
```

response body
```json
{
  "code": 1000,
  "message": "Command completed successfully.",
  "description": null
}
```

response headers
```
 cache-control: no-store, no-cache, must-revalidate 
 content-type: application/json 
 expires: Thu, 19 Nov 1981 08:52:00 GMT 
 pragma: no-cache 
```

Please see above the output from the 3 API calls. I hope this helps!
Let me know if there's anything else I can help with.
And thanks a lot for your help with it!
the responses of the 2 calls (createHostRecord, removeHostRecord) are really different than the documentation.

from the doc (createHostRecord):
```
{
  "data": {
    "data": [
      {
        "name": "string",
        "code": "string",
        "message": "string"
      }
    ]
  }
}
```

from the doc (removeHostRecord):
```
{
  "data": {
    "data": [
      {
        "name": "string",
        "code": "string",
        "message": "string"
      }
    ]
  }
}
```

Can you confirm that you don't drop the `data.data` section for these calls?
Yes, 100%
There is no upper level "data".
That is the exact output of the curl commands I posted, where I replaced only the sensitive values.

Fix patch:
diff --git a/README.md b/README.md
index d41c2ac364..3f60e88744 100644
--- a/README.md
+++ b/README.md
@@ -53,24 +53,24 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
 | [ConoHa](https://go-acme.github.io/lego/dns/conoha/)                            | [Constellix](https://go-acme.github.io/lego/dns/constellix/)                    | [deSEC.io](https://go-acme.github.io/lego/dns/desec/)                           | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) |
 | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/)               | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/)                | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/)                        | [DNSPod](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/)                          | [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/)                      |
-| [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/)           | [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/)                              | [Joker](https://go-acme.github.io/lego/dns/joker/)                              | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/)               | [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/)                        | [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/)                            | [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/)                                  | [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/)                      | [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/)                      | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          |
-| [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            |
-| [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              | [Yandex](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/)                            |                                                                                 |                                                                                 |                                                                                 |
+| [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/)                      | [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/)           |
+| [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/)                              | [Joker](https://go-acme.github.io/lego/dns/joker/)                              | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/)               |
+| [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/)                        | [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/)                            | [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/)                                  | [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/)                      | [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/)                      |
+| [TransIP](https://go-acme.github.io/lego/dns/transip/)                          | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        |
+| [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              | [Yandex](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 c917c9342e..8b3e95e256 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -44,6 +44,7 @@ func allDNSCodes() string {
 		"dynu",
 		"easydns",
 		"edgedns",
+		"epik",
 		"exec",
 		"exoscale",
 		"freemyip",
@@ -771,6 +772,26 @@ func displayDNSHelp(name string) error {
 		ew.writeln()
 		ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgedns`)
 
+	case "epik":
+		// generated from: providers/dns/epik/epik.toml
+		ew.writeln(`Configuration for Epik.`)
+		ew.writeln(`Code:	'epik'`)
+		ew.writeln(`Since:	'v4.5.0'`)
+		ew.writeln()
+
+		ew.writeln(`Credentials:`)
+		ew.writeln(`	- "EPIK_SIGNATURE":	Epik API signature (https://registrar.epik.com/account/api-settings/)`)
+		ew.writeln()
+
+		ew.writeln(`Additional Configuration:`)
+		ew.writeln(`	- "EPIK_HTTP_TIMEOUT":	API request timeout`)
+		ew.writeln(`	- "EPIK_POLLING_INTERVAL":	Time between DNS propagation check`)
+		ew.writeln(`	- "EPIK_PROPAGATION_TIMEOUT":	Maximum waiting time for DNS propagation`)
+		ew.writeln(`	- "EPIK_TTL":	The TTL of the TXT record used for the DNS challenge`)
+
+		ew.writeln()
+		ew.writeln(`More information: https://go-acme.github.io/lego/dns/epik`)
+
 	case "exec":
 		// generated from: providers/dns/exec/exec.toml
 		ew.writeln(`Configuration for External program.`)
diff --git a/docs/content/dns/zz_gen_epik.md b/docs/content/dns/zz_gen_epik.md
new file mode 100644
index 0000000000..8acfed37be
--- /dev/null
+++ b/docs/content/dns/zz_gen_epik.md
@@ -0,0 +1,62 @@
+---
+title: "Epik"
+date: 2019-03-03T16:39:46+01:00
+draft: false
+slug: epik
+---
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/epik/epik.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+
+Since: v4.5.0
+
+Configuration for [Epik](https://www.epik.com/).
+
+
+<!--more-->
+
+- Code: `epik`
+
+Here is an example bash command using the Epik provider:
+
+```bash
+EPIK_SIGNATURE=xxxxxxxxxxxxxxxxxxxxxxxxxx \
+lego --email myemail@example.com --dns epik --domains my.example.org run
+```
+
+
+
+
+## Credentials
+
+| Environment Variable Name | Description |
+|-----------------------|-------------|
+| `EPIK_SIGNATURE` | Epik API signature (https://registrar.epik.com/account/api-settings/) |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here](/lego/dns/#configuration-and-credentials).
+
+
+## Additional Configuration
+
+| Environment Variable Name | Description |
+|--------------------------------|-------------|
+| `EPIK_HTTP_TIMEOUT` | API request timeout |
+| `EPIK_POLLING_INTERVAL` | Time between DNS propagation check |
+| `EPIK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
+| `EPIK_TTL` | The TTL of the TXT record used for the DNS challenge |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here](/lego/dns/#configuration-and-credentials).
+
+
+
+
+## More information
+
+- [API documentation](https://docs.userapi.epik.com/v2/#/)
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/epik/epik.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index f25c2840cb..ae25512ac6 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -35,6 +35,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/epik"
 	"github.com/go-acme/lego/v4/providers/dns/exec"
 	"github.com/go-acme/lego/v4/providers/dns/exoscale"
 	"github.com/go-acme/lego/v4/providers/dns/freemyip"
@@ -166,6 +167,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 "epik":
+		return epik.NewDNSProvider()
 	case "exec":
 		return exec.NewDNSProvider()
 	case "exoscale":
diff --git a/providers/dns/epik/epik.go b/providers/dns/epik/epik.go
new file mode 100644
index 0000000000..1f906e4c95
--- /dev/null
+++ b/providers/dns/epik/epik.go
@@ -0,0 +1,147 @@
+// Package epik implements a DNS provider for solving the DNS-01 challenge using Epik.
+package epik
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"strings"
+	"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/epik/internal"
+)
+
+// Environment variables names.
+const (
+	envNamespace = "EPIK_"
+
+	EnvSignature = envNamespace + "SIGNATURE"
+
+	EnvTTL                = envNamespace + "TTL"
+	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 {
+	Signature          string
+	PropagationTimeout time.Duration
+	PollingInterval    time.Duration
+	TTL                int
+	HTTPClient         *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+	return &Config{
+		TTL:                env.GetOrDefaultInt(EnvTTL, 3600),
+		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
+		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
+		HTTPClient: &http.Client{
+			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+		},
+	}
+}
+
+// DNSProvider implements the challenge.Provider interface.
+type DNSProvider struct {
+	config *Config
+	client *internal.Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Epik.
+// Credentials must be passed in the environment variable: EPIK_SIGNATURE.
+func NewDNSProvider() (*DNSProvider, error) {
+	values, err := env.Get(EnvSignature)
+	if err != nil {
+		return nil, fmt.Errorf("epik: %w", err)
+	}
+
+	config := NewDefaultConfig()
+	config.Signature = values[EnvSignature]
+
+	return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Epik.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+	if config == nil {
+		return nil, errors.New("epik: the configuration of the DNS provider is nil")
+	}
+
+	if config.Signature == "" {
+		return nil, errors.New("epik: missing credentials")
+	}
+
+	client := internal.NewClient(config.Signature)
+
+	if config.HTTPClient != nil {
+		client.HTTPClient = config.HTTPClient
+	}
+
+	return &DNSProvider{config: config, client: client}, 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
+}
+
+// Present creates a TXT record using the specified parameters.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+	fqdn, value := dns01.GetRecord(domain, keyAuth)
+
+	// find authZone
+	authZone, err := dns01.FindZoneByFqdn(fqdn)
+	if err != nil {
+		return fmt.Errorf("epik: %w", err)
+	}
+
+	record := internal.RecordRequest{
+		Host: dns01.UnFqdn(strings.TrimSuffix(fqdn, authZone)),
+		Type: "TXT",
+		Data: value,
+		TTL:  d.config.TTL,
+	}
+
+	_, err = d.client.CreateHostRecord(dns01.UnFqdn(authZone), record)
+	if err != nil {
+		return fmt.Errorf("epik: %w", err)
+	}
+
+	return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+	fqdn, value := dns01.GetRecord(domain, keyAuth)
+
+	// find authZone
+	authZone, err := dns01.FindZoneByFqdn(fqdn)
+	if err != nil {
+		return fmt.Errorf("epik: %w", err)
+	}
+
+	dom := dns01.UnFqdn(authZone)
+	host := dns01.UnFqdn(strings.TrimSuffix(fqdn, authZone))
+
+	records, err := d.client.GetDNSRecords(dom)
+	if err != nil {
+		return fmt.Errorf("epik: %w", err)
+	}
+
+	for _, record := range records {
+		if strings.EqualFold(record.Type, "TXT") && record.Data == value && record.Name == host {
+			_, err = d.client.RemoveHostRecord(dom, record.ID)
+			if err != nil {
+				return fmt.Errorf("epik: %w", err)
+			}
+		}
+	}
+
+	return nil
+}
diff --git a/providers/dns/epik/epik.toml b/providers/dns/epik/epik.toml
new file mode 100644
index 0000000000..1839b5c013
--- /dev/null
+++ b/providers/dns/epik/epik.toml
@@ -0,0 +1,22 @@
+Name = "Epik"
+Description = ''''''
+URL = "https://www.epik.com/"
+Code = "epik"
+Since = "v4.5.0"
+
+Example = '''
+EPIK_SIGNATURE=xxxxxxxxxxxxxxxxxxxxxxxxxx \
+lego --email myemail@example.com --dns epik --domains my.example.org run
+'''
+
+[Configuration]
+  [Configuration.Credentials]
+    EPIK_SIGNATURE = "Epik API signature (https://registrar.epik.com/account/api-settings/)"
+  [Configuration.Additional]
+    EPIK_POLLING_INTERVAL = "Time between DNS propagation check"
+    EPIK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
+    EPIK_TTL = "The TTL of the TXT record used for the DNS challenge"
+    EPIK_HTTP_TIMEOUT = "API request timeout"
+
+[Links]
+  API = "https://docs.userapi.epik.com/v2/#/"
diff --git a/providers/dns/epik/internal/client.go b/providers/dns/epik/internal/client.go
new file mode 100644
index 0000000000..0ff4735e26
--- /dev/null
+++ b/providers/dns/epik/internal/client.go
@@ -0,0 +1,163 @@
+package internal
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"path"
+	"time"
+)
+
+const defaultBaseURL = "https://usersapiv2.epik.com/v2"
+
+type Client struct {
+	HTTPClient *http.Client
+	baseURL    *url.URL
+	signature  string
+}
+
+func NewClient(signature string) *Client {
+	baseURL, _ := url.Parse(defaultBaseURL)
+
+	return &Client{
+		HTTPClient: &http.Client{Timeout: 5 * time.Second},
+		baseURL:    baseURL,
+		signature:  signature,
+	}
+}
+
+// GetDNSRecords gets DNS records for a domain.
+// https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/getDnsRecord
+func (c Client) GetDNSRecords(domain string) ([]Record, error) {
+	resp, err := c.do(http.MethodGet, domain, url.Values{}, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	defer func() { _ = resp.Body.Close() }()
+
+	all, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read request body (%d): %w", resp.StatusCode, err)
+	}
+
+	err = checkError(resp.StatusCode, all)
+	if err != nil {
+		return nil, err
+	}
+
+	var data GetDNSRecordResponse
+	err = json.Unmarshal(all, &data)
+	if err != nil {
+		return nil, fmt.Errorf("failed to unmarshal request body (%d): %s", resp.StatusCode, string(all))
+	}
+
+	return data.Data.Records, nil
+}
+
+// CreateHostRecord creates a record for a domain.
+// https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/createHostRecord
+func (c Client) CreateHostRecord(domain string, record RecordRequest) (*Data, error) {
+	payload := CreateHostRecords{Payload: record}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.do(http.MethodPost, domain, url.Values{}, bytes.NewReader(body))
+	if err != nil {
+		return nil, err
+	}
+
+	defer func() { _ = resp.Body.Close() }()
+
+	all, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read request body (%d): %w", resp.StatusCode, err)
+	}
+
+	err = checkError(resp.StatusCode, all)
+	if err != nil {
+		return nil, err
+	}
+
+	var data Data
+	err = json.Unmarshal(all, &data)
+	if err != nil {
+		return nil, fmt.Errorf("%d: %s", resp.StatusCode, string(all))
+	}
+
+	return &data, nil
+}
+
+// RemoveHostRecord removes a record for a domain.
+// https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/removeHostRecord
+func (c Client) RemoveHostRecord(domain string, recordID string) (*Data, error) {
+	params := url.Values{}
+	params.Set("ID", recordID)
+
+	resp, err := c.do(http.MethodDelete, domain, params, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	defer func() { _ = resp.Body.Close() }()
+
+	all, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read request body (%d): %w", resp.StatusCode, err)
+	}
+
+	err = checkError(resp.StatusCode, all)
+	if err != nil {
+		return nil, err
+	}
+
+	var data Data
+	err = json.Unmarshal(all, &data)
+	if err != nil {
+		return nil, fmt.Errorf("%d: %s", resp.StatusCode, string(all))
+	}
+
+	return &data, nil
+}
+
+func (c *Client) do(method, domain string, params url.Values, body io.Reader) (*http.Response, error) {
+	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "domains", domain, "records"))
+	if err != nil {
+		return nil, fmt.Errorf("create endpoint: %w", err)
+	}
+
+	params.Set("SIGNATURE", c.signature)
+	endpoint.RawQuery = params.Encode()
+
+	req, err := http.NewRequest(method, endpoint.String(), body)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("Accept", "application/json")
+	if body != nil {
+		req.Header.Set("Content-Type", "application/json")
+	}
+
+	return c.HTTPClient.Do(req)
+}
+
+func checkError(statusCode int, all []byte) error {
+	if statusCode == http.StatusOK {
+		return nil
+	}
+
+	var apiErr APIError
+	err := json.Unmarshal(all, &apiErr)
+	if err != nil {
+		return fmt.Errorf("%d: %s", statusCode, string(all))
+	}
+
+	return &apiErr
+}
diff --git a/providers/dns/epik/internal/fixtures/createHostRecord.json b/providers/dns/epik/internal/fixtures/createHostRecord.json
new file mode 100644
index 0000000000..ee4603e084
--- /dev/null
+++ b/providers/dns/epik/internal/fixtures/createHostRecord.json
@@ -0,0 +1,5 @@
+{
+  "code": 1000,
+  "message": "Command completed successfully.",
+  "description": null
+}
diff --git a/providers/dns/epik/internal/fixtures/error.json b/providers/dns/epik/internal/fixtures/error.json
new file mode 100644
index 0000000000..4481104644
--- /dev/null
+++ b/providers/dns/epik/internal/fixtures/error.json
@@ -0,0 +1,9 @@
+{
+  "errors": [
+    {
+      "code": 1,
+      "message": "Unauthorized",
+      "description": "Unauthorized: Signature was not provided or was invalid"
+    }
+  ]
+}
diff --git a/providers/dns/epik/internal/fixtures/getDnsRecord.json b/providers/dns/epik/internal/fixtures/getDnsRecord.json
new file mode 100644
index 0000000000..40b290c469
--- /dev/null
+++ b/providers/dns/epik/internal/fixtures/getDnsRecord.json
@@ -0,0 +1,56 @@
+{
+  "data": {
+    "name": "MYDOMAIN.ORG",
+    "code": 1000,
+    "records": [
+      {
+        "id": "abc123",
+        "name": "www",
+        "type": "CAA",
+        "data": "1 issue letsencrypt.org",
+        "aux": 0,
+        "ttl": 300
+      },
+      {
+        "id": "abc123",
+        "name": "www",
+        "type": "A",
+        "data": "192.64.147.249",
+        "aux": 0,
+        "ttl": 300
+      },
+      {
+        "id": "abc123",
+        "name": "*",
+        "type": "A",
+        "data": "192.64.147.249",
+        "aux": 0,
+        "ttl": 300
+      },
+      {
+        "id": "abc123",
+        "name": "",
+        "type": "CAA",
+        "data": "0 issue trust-provider.com",
+        "aux": 0,
+        "ttl": 300
+      },
+      {
+        "id": "abc123",
+        "name": "",
+        "type": "CAA",
+        "data": "1 issue letsencrypt.org",
+        "aux": 0,
+        "ttl": 300
+      },
+      {
+        "id": "abc123",
+        "name": "",
+        "type": "A",
+        "data": "192.64.147.249",
+        "aux": 0,
+        "ttl": 300
+      }
+    ]
+  }
+}
diff --git a/providers/dns/epik/internal/fixtures/removeHostRecord.json b/providers/dns/epik/internal/fixtures/removeHostRecord.json
new file mode 100644
index 0000000000..ee4603e084
--- /dev/null
+++ b/providers/dns/epik/internal/fixtures/removeHostRecord.json
@@ -0,0 +1,5 @@
+{
+  "code": 1000,
+  "message": "Command completed successfully.",
+  "description": null
+}
diff --git a/providers/dns/epik/internal/types.go b/providers/dns/epik/internal/types.go
new file mode 100644
index 0000000000..d136e4fb59
--- /dev/null
+++ b/providers/dns/epik/internal/types.go
@@ -0,0 +1,58 @@
+package internal
+
+import (
+	"fmt"
+	"strings"
+)
+
+type RecordRequest struct {
+	Host string `json:"HOST,omitempty"`
+	Type string `json:"TYPE,omitempty"`
+	Data string `json:"DATA,omitempty"`
+	Aux  int    `json:"AUX,omitempty"`
+	TTL  int    `json:"TTL,omitempty"`
+}
+
+type SetHostRecords struct {
+	Payload []RecordRequest `json:"set_host_records_payload"`
+}
+
+type CreateHostRecords struct {
+	Payload RecordRequest `json:"create_host_records_payload"`
+}
+
+type Data struct {
+	Code        int    `json:"code,omitempty"`
+	Message     string `json:"message,omitempty"`
+	Description string `json:"description,omitempty"`
+}
+
+type APIError struct {
+	Errors []Data `json:"errors"`
+}
+
+func (a APIError) Error() string {
+	var parts []string
+	for _, data := range a.Errors {
+		parts = append(parts, fmt.Sprintf("code: %d, message: %s, description: %s", data.Code, data.Message, data.Description))
+	}
+
+	return strings.Join(parts, ", ")
+}
+
+type Record struct {
+	ID   string `json:"id"`
+	Name string `json:"name"`
+	Type string `json:"type"`
+	Data string `json:"data"`
+	AUX  int    `json:"aux"`
+	TTL  int    `json:"ttl"`
+}
+
+type GetDNSRecordResponse struct {
+	Data struct {
+		Name    string   `json:"name"`
+		Code    int      `json:"code"`
+		Records []Record `json:"records"`
+	} `json:"data"`
+}


Test patch:
diff --git a/providers/dns/epik/epik_test.go b/providers/dns/epik/epik_test.go
new file mode 100644
index 0000000000..c0cd3d43b6
--- /dev/null
+++ b/providers/dns/epik/epik_test.go
@@ -0,0 +1,113 @@
+package epik
+
+import (
+	"testing"
+
+	"github.com/go-acme/lego/v4/platform/tester"
+	"github.com/stretchr/testify/require"
+)
+
+const envDomain = envNamespace + "DOMAIN"
+
+var envTest = tester.NewEnvTest(EnvSignature).WithDomain(envDomain)
+
+func TestNewDNSProvider(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		envVars  map[string]string
+		expected string
+	}{
+		{
+			desc: "success",
+			envVars: map[string]string{
+				EnvSignature: "secret",
+			},
+		},
+		{
+			desc:     "missing credentials",
+			envVars:  map[string]string{},
+			expected: "epik: some credentials information are missing: EPIK_SIGNATURE",
+		},
+	}
+
+	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)
+				require.NotNil(t, p.client)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+	testCases := []struct {
+		desc      string
+		signature string
+		expected  string
+	}{
+		{
+			desc:      "success",
+			signature: "A",
+		},
+		{
+			desc:     "missing credentials",
+			expected: "epik: missing credentials",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			config := NewDefaultConfig()
+			config.Signature = test.signature
+
+			p, err := NewDNSProviderConfig(config)
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+				require.NotNil(t, p.client)
+			} 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)
+
+	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
diff --git a/providers/dns/epik/internal/client_test.go b/providers/dns/epik/internal/client_test.go
new file mode 100644
index 0000000000..3425faea7b
--- /dev/null
+++ b/providers/dns/epik/internal/client_test.go
@@ -0,0 +1,196 @@
+package internal
+
+import (
+	"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) (*http.ServeMux, *Client) {
+	t.Helper()
+	mux := http.NewServeMux()
+	server := httptest.NewServer(mux)
+
+	client := NewClient("secret")
+	client.HTTPClient = server.Client()
+	client.baseURL, _ = url.Parse(server.URL)
+
+	return mux, client
+}
+
+func TestClient_GetDNSRecords(t *testing.T) {
+	mux, client := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodGet, http.StatusOK, "getDnsRecord.json"))
+
+	records, err := client.GetDNSRecords("example.com")
+	require.NoError(t, err)
+
+	expected := []Record{
+		{
+			ID:   "abc123",
+			Name: "www",
+			Type: "CAA",
+			Data: "1 issue letsencrypt.org",
+			AUX:  0,
+			TTL:  300,
+		},
+		{
+			ID:   "abc123",
+			Name: "www",
+			Type: "A",
+			Data: "192.64.147.249",
+			AUX:  0,
+			TTL:  300,
+		},
+		{
+			ID:   "abc123",
+			Name: "*",
+			Type: "A",
+			Data: "192.64.147.249",
+			AUX:  0,
+			TTL:  300,
+		},
+		{
+			ID:   "abc123",
+			Type: "CAA",
+			Data: "0 issue trust-provider.com",
+			AUX:  0,
+			TTL:  300,
+		},
+		{
+			ID:   "abc123",
+			Type: "CAA",
+			Data: "1 issue letsencrypt.org",
+			AUX:  0,
+			TTL:  300,
+		},
+		{
+			ID:   "abc123",
+			Type: "A",
+			Data: "192.64.147.249",
+			AUX:  0,
+			TTL:  300,
+		},
+	}
+
+	assert.Equal(t, expected, records)
+}
+
+func TestClient_GetDNSRecords_error(t *testing.T) {
+	mux, client := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json"))
+
+	_, err := client.GetDNSRecords("example.com")
+	assert.Error(t, err)
+}
+
+func TestClient_CreateHostRecord(t *testing.T) {
+	mux, client := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodPost, http.StatusOK, "createHostRecord.json"))
+
+	record := RecordRequest{
+		Host: "www2",
+		Type: "A",
+		Data: "192.64.147.249",
+		Aux:  0,
+		TTL:  300,
+	}
+
+	data, err := client.CreateHostRecord("example.com", record)
+	require.NoError(t, err)
+
+	expected := &Data{
+		Code:    1000,
+		Message: "Command completed successfully.",
+	}
+
+	assert.Equal(t, expected, data)
+}
+
+func TestClient_CreateHostRecord_error(t *testing.T) {
+	mux, client := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodPost, http.StatusUnauthorized, "error.json"))
+
+	record := RecordRequest{
+		Host: "www2",
+		Type: "A",
+		Data: "192.64.147.249",
+		Aux:  0,
+		TTL:  300,
+	}
+
+	_, err := client.CreateHostRecord("example.com", record)
+	assert.Error(t, err)
+}
+
+func TestClient_RemoveHostRecord(t *testing.T) {
+	mux, client := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodDelete, http.StatusOK, "removeHostRecord.json"))
+
+	data, err := client.RemoveHostRecord("example.com", "abc123")
+	require.NoError(t, err)
+
+	expected := &Data{
+		Code:    1000,
+		Message: "Command completed successfully.",
+	}
+
+	assert.Equal(t, expected, data)
+}
+
+func TestClient_RemoveHostRecord_error(t *testing.T) {
+	mux, client := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json"))
+
+	_, err := client.RemoveHostRecord("example.com", "abc123")
+	assert.Error(t, err)
+}
+
+func testHandler(method string, statusCode int, filename string) http.HandlerFunc {
+	return func(rw http.ResponseWriter, req *http.Request) {
+		if req.Method != method {
+			http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed)
+			return
+		}
+
+		auth := req.URL.Query().Get("SIGNATURE")
+		if auth != "secret" {
+			http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized)
+			return
+		}
+
+		rw.WriteHeader(statusCode)
+
+		if statusCode == http.StatusNoContent {
+			return
+		}
+
+		file, err := os.Open(filepath.Join("fixtures", filename))
+		if err != nil {
+			http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError)
+			return
+		}
+
+		defer func() { _ = file.Close() }()
+
+		_, err = io.Copy(rw, file)
+		if err != nil {
+			http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError)
+			return
+		}
+	}
+}