-
-
Save dankrause/5585907 to your computer and use it in GitHub Desktop.
import requests | |
class HoverException(Exception): | |
pass | |
class HoverAPI(object): | |
def __init__(self, username, password): | |
params = {"username": username, "password": password} | |
r = requests.post("https://www.hover.com/api/login", params=params) | |
if not r.ok or "hoverauth" not in r.cookies: | |
raise HoverException(r) | |
self.cookies = {"hoverauth": r.cookies["hoverauth"]} | |
def call(self, method, resource, data=None): | |
url = "https://www.hover.com/api/{0}".format(resource) | |
r = requests.request(method, url, data=data, cookies=self.cookies) | |
if not r.ok: | |
raise HoverException(r) | |
if r.content: | |
body = r.json() | |
if "succeeded" not in body or body["succeeded"] is not True: | |
raise HoverException(body) | |
return body | |
# connect to the API using your account | |
client = HoverAPI("myusername", "mypassword") | |
# get details of a domains without DNS records | |
client.call("get", "domains") | |
# get all domains and DNS records | |
client.call("get", "dns") | |
# notice the "id" field of domains in response to the above calls - that's needed | |
# to address the domains individually, like so: | |
# get details of a specific domain without DNS records | |
client.call("get", "domains/dom123456") | |
# get DNS records of a specific domain: | |
client.call("get", "domains/dom123456/dns") | |
# create a new A record: | |
record = {"name": "mysubdomain", "type": "A", "content": "127.0.0.1"} | |
client.call("post", "domains/dom123456/dns", record) | |
# create a new SRV record | |
# note that content is "{priority} {weight} {port} {target}" | |
record = {"name": "mysubdomain", "type": "SRV", "content": "10 10 123 __service"} | |
client.call("post", "domains/dom123456/dns", record) | |
# create a new MX record | |
# note that content is "{priority} {host}" | |
record = {"name": "mysubdomain", "type": "MX", "content": "10 mail"} | |
client.call("post", "domains/dom123456/dns", record) | |
# notice the "id" field of DNS records in the above calls - that's | |
# needed to address the DNS records individually, like so: | |
# update an existing DNS record | |
client.call("put", "dns/dns1234567", {"content": "127.0.0.1"}) | |
# delete a DNS record: | |
client.call("delete", "dns/dns1234567") |
#!/usr/bin/python | |
""" | |
bulkhover.py 1.1 | |
This is a command-line script to import and export DNS records for a single | |
domain into or out of a hover account. | |
Usage: | |
bulkhover.py [options] (import|export) <domain> <dnsfile> | |
bulkhover.py (-h | --help) | |
bulkhover.py --version | |
Options: | |
-h --help Show this screen | |
--version Show version | |
-c --conf=<conf> Path to conf | |
-u --username=<user> Your hover username | |
-p --password=<pass> Your hover password | |
-f --flush Delete all existing records before importing | |
Examples: | |
The DNS file should have one record per line, in the format: | |
{name} {type} {content} | |
For example: | |
www A 127.0.0.1 | |
@ MX 10 example.com | |
Since the script output is in the same format as its input, you can use shell | |
pipelines to do complex operations. | |
Copy all DNS records from one domain to another: | |
bulkhover.py -c my.conf export example.com - | ./bulkhover.py -c my.conf -f import other.com - | |
Copy only MX records from one domain to another: | |
./bulkhover.py -c my.conf export foo.com - | awk '$2 == "MX" {print $0}' | ./bulkhover.py -c my.conf import bar.com - | |
To avoid passing your username and password in the command-line, you can use | |
a conf file that contains them instead: | |
[hover] | |
username=YOUR_USERNAME | |
password=YOUR_PASSWORD | |
""" | |
import ConfigParser | |
import docopt | |
import requests | |
import sys | |
class HoverException(Exception): | |
pass | |
class HoverAPI(object): | |
def __init__(self, username, password): | |
params = {"username": username, "password": password} | |
r = requests.post("https://www.hover.com/api/login", params=params) | |
if not r.ok or "hoverauth" not in r.cookies: | |
raise HoverException(r) | |
self.cookies = {"hoverauth": r.cookies["hoverauth"]} | |
def call(self, method, resource, data=None): | |
url = "https://www.hover.com/api/{0}".format(resource) | |
r = requests.request(method, url, data=data, cookies=self.cookies) | |
if not r.ok: | |
raise HoverException(r) | |
if r.content: | |
body = r.json() | |
if "succeeded" not in body or body["succeeded"] is not True: | |
raise HoverException(body) | |
return body | |
def import_dns(username, password, domain, filename, flush=False): | |
try: | |
client = HoverAPI(username, password) | |
except HoverException as e: | |
raise HoverException("Authentication failed") | |
if flush: | |
records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"] | |
for record in records: | |
client.call("delete", "dns/{0}".format(record["id"])) | |
print "Deleted {name} {type} {content}".format(**record) | |
domain_id = client.call("get", "domains/{0}".format(domain))["domain"]["id"] | |
if filename == "-": filename = "/dev/stdin" | |
with open(filename, "r") as f: | |
for line in f: | |
parts = line.strip().split(" ", 2) | |
record = {"name": parts[0], "type": parts[1], "content": parts[2]} | |
client.call("post", "domains/{0}/dns".format(domain), record) | |
print "Created {name} {type} {content}".format(**record) | |
def export_dns(username, password, domain, filename): | |
try: | |
client = HoverAPI(username, password) | |
except HoverException as e: | |
raise HoverException("Authentication failed") | |
records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"] | |
if filename == "-": filename = "/dev/stdout" | |
with open(filename, "w") as f: | |
for record in records: | |
f.write("{name} {type} {content}\n".format(**record)) | |
def main(args): | |
def get_conf(filename): | |
config = ConfigParser.ConfigParser() | |
config.read(filename) | |
items = dict(config.items("hover")) | |
return items["username"], items["password"] | |
if args["--conf"] is None: | |
if not all((args["--username"], args["--password"])): | |
print("You must specifiy either a conf file, or a username and password") | |
return 1 | |
else: | |
username, password = args["--username"], args["--password"] | |
else: | |
username, password = get_conf(args["--conf"]) | |
try: | |
if args["import"]: | |
import_dns(username, password, args["<domain>"], args["<dnsfile>"], args["--flush"]) | |
elif args["export"]: | |
export_dns(username, password, args["<domain>"], args["<dnsfile>"]) | |
except HoverException as e: | |
print "Unable to update DNS: {0}".format(e) | |
return 1 | |
if __name__ == "__main__": | |
version = __doc__.strip().split("\n")[0] | |
args = docopt.docopt(__doc__, version=version) | |
status = main(args) | |
sys.exit(status) |
#!/usr/bin/env python | |
""" | |
dynhover.py 1.2 | |
This tool will update an A record for given (sub)domain in your hover.com | |
with your IP, or an IP that you specify | |
Usage: | |
dynhover.py (-c <conf> | -u <user> -p <password>) <domain> | |
dynhover.py (-h | --help) | |
dynhover.py --version | |
Options: | |
-h --help Show this screen | |
--version Show version | |
-c --conf=<conf> Path to conf | |
-u --username=<user> Your hover username | |
-p --password=<pass> Your hover password | |
-i --ip=<ip> An IP to set (auto-detected by default) | |
""" | |
import ConfigParser | |
import docopt | |
import requests | |
import sys | |
class HoverException(Exception): | |
pass | |
class HoverAPI(object): | |
def __init__(self, username, password): | |
params = {"username": username, "password": password} | |
r = requests.post("https://www.hover.com/api/login", params=params) | |
if not r.ok or "hoverauth" not in r.cookies: | |
raise HoverException(r) | |
self.cookies = {"hoverauth": r.cookies["hoverauth"]} | |
def call(self, method, resource, data=None): | |
url = "https://www.hover.com/api/{0}".format(resource) | |
r = requests.request(method, url, data=data, cookies=self.cookies) | |
if not r.ok: | |
raise HoverException(r) | |
if r.content: | |
body = r.json() | |
if "succeeded" not in body or body["succeeded"] is not True: | |
raise HoverException(body) | |
return body | |
def get_public_ip(): | |
return requests.get("http://ifconfig.me/ip").content | |
def update_dns(username, password, fqdn, ip): | |
try: | |
client = HoverAPI(username, password) | |
except HoverException as e: | |
raise HoverException("Authentication failed") | |
dns = client.call("get", "dns") | |
dns_id = None | |
for domain in dns["domains"]: | |
if fqdn == domain["domain_name"]: | |
fqdn = "@.{domain_name}".format(**domain) | |
for entry in domain["entries"]: | |
if entry["type"] != "A": continue | |
if "{0}.{1}".format(entry["name"], domain["domain_name"]) == fqdn: | |
dns_id = entry["id"] | |
break | |
if dns_id is None: | |
raise HoverException("No DNS record found for {0}".format(fqdn)) | |
response = client.call("put", "dns/{0}".format(dns_id), {"content": my_ip}) | |
if "succeeded" not in response or response["succeeded"] is not True: | |
raise HoverException(response) | |
def main(args): | |
if args["--username"]: | |
username, password = args["--username"], args["--password"] | |
else: | |
config = ConfigParser.ConfigParser() | |
config.read(args["--conf"]) | |
items = dict(config.items("hover")) | |
username, password = items["username"], items["password"] | |
domain = args["<domain>"] | |
ip = args["--ip"] or get_public_ip() | |
try: | |
update_dns(username, password, domain, ip) | |
except HoverException as e: | |
print "Unable to update DNS: {0}".format(e) | |
return 1 | |
return 0 | |
if __name__ == "__main__": | |
version = __doc__.strip().split("\n")[0] | |
args = docopt.docopt(__doc__, version=version) | |
status = main(args) | |
sys.exit(status) |
#!/bin/bash | |
[[ $# -lt 3 ]] && echo "Usage: $0 USERNAME PASSWORD DNS_ID" | |
USERNAME=${1} | |
PASSWORD=${2} | |
DNS_ID=${3} | |
# find your DNS ID here: https://www.hover.com/api/domains/yourdomain.com/dns/ | |
# (replace "yourdomain.com" with your actual domain, and look for the record | |
# you want to change. The ID looks like: dns1234567) | |
IP=$(curl "http://ifconfig.me/ip" -s) | |
curl "https://www.hover.com/api/dns/${DNS_ID}" \ | |
-X PUT \ | |
-d "content=${IP}" \ | |
-s \ | |
-b <(curl "https://www.hover.com/api/login" \ | |
-X POST \ | |
-G \ | |
-d "username=${USERNAME}" \ | |
-d "password=${PASSWORD}" \ | |
-s \ | |
-o /dev/null \ | |
-c -) | |
echo | |
Yes, I'm seeing the same behavior. My scripts started failing at around 5:00pm Tuesday night (August 4th). On one of my servers I am getting the same message, and another one which I believe is running a different version of Python, is giving me the error:
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='www.hover.com', port=443): Max retries exceeded with url: /api/login?username=xxxxxxxx&password=yyyyyyyy
This might be how to fix it but I'm not quite certain how to handle the redirects when signing in. Hope it helps
class HoverAPI(object):
def __init__(self, username, password):
params = {"username": username, "password": password}
r = requests.post("https://www.hover.com/signin", params=params)
if not r.ok or not len(r.history)>0 or "hoverauth" not in r.history[0].cookies:
raise HoverException(r)
self.cookies = {"hoverauth": r.history[0].cookies["hoverauth"]}
def call(self, method, resource, data=None):
url = "https://www.hover.com/api/{0}".format(resource)
r = requests.request(method, url, data=data, cookies=self.cookies)
if not r.ok:
raise HoverException(r)
if r.content:
body = r.json()
if "succeeded" not in body or body["succeeded"] is not True:
raise HoverException(body)
return body
Getting logged in now, but now when I try to use client.call("get", "dns"), I get this error:
Traceback (most recent call last):
File "/usr/local/bin/update-hover-dns.py", line 65, in
result = client.call("get", "dns")
File "/usr/local/bin/update-hover-dns.py", line 35, in call
body = r.json()
TypeError: 'dict' object is not callable
Found the solution to my problem here:
http://love-python.blogspot.ca/2013/03/dict-object-is-not-callable-on-json.html
In short, changed body = r.json() to body = r.json solved it.
@addisflava, assuming you're just running the script via CRON just do multiple calls one for @ and one for * since they both have their own dnsID
I'm also getting this issue and can't find a fix...
{"succeeded":false,"error_code":"login","error":"You must login first"}
Same here
So using this it seems to authenticate (If I use the wrong password I can reliably get an authentication error) but I get this result:
[22:59 david@plex ~] > python dynhover.py -u d#####t -p N######a k######x.com
Traceback (most recent call last):
File "dynhover.py", line 100, in <module>
status = main(args)
File "dynhover.py", line 89, in main
update_dns(username, password, domain, ip)
File "dynhover.py", line 70, in update_dns
response = client.call("put", "dns/{0}".format(dns_id), {"content": my_ip})
NameError: global name 'my_ip' is not defined
any thoughts?
I modified the bash script dynhover.sh to fit my needs and also fixed the "You must login first" error. It seemed Curl wasn't using cookies to save the login session. So I changed it to first login, and then run the subsequent dns updates. It might not be as elegant as the simple script above (I'm not a bash coder by any means), but I also made it do a bit more.
I'm using it on a router (EdgeRouter X), so I have easy access to what my current IP is via ethernet interfaces instead of relying on external websites. In this case I parsed the output of the app ip to get the address. Then the address is stored in a file next to the script so that the next time it runs it doesn't have to do any external calls or update hover if it doesn't have to by simply comparing the current ip to one used the last time hover was updated. If it needs updating, it runs the curl commands and writes the new address back to the file. If you want to use it, you'll probably have to change the location of apps used (or the apps themselves, or even go back to using external sites for the ip variable) and storage locations, but if you're on EdgeOS, it should work as is if you stick it in /config/scripts. It also no longer looks for variables passed to it and instead hardcodes them, so if you want that back you can change those relevant lines back to the ones above.
#!/bin/bash
# find your DNS ID here: https://www.hover.com/api/domains/yourdomain.com/dns/
# (replace "yourdomain.com" with your actual domain, and look for the record
# you want to change. The ID looks like: dns1234567)
USERNAME=usernamehere
PASSWORD=passwordhere
DNS_ID=dnsrecord1 # put dns record here for A record @
DNS_ID2=dnsrecord2 # put dns record here for A record *
firstrun=0
ipfile="/config/scripts/lastip"
# get current ip
IP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)
# check for file to store last ip and if it doesn't exist, create it and write current ip to it
if [ ! -e "$ipfile" ] ; then
echo "lastip file doesn't exist. creating now."
touch "$ipfile"
if [ ! -w "$ipfile" ] ; then
echo "cannot write to $ipfile"
exit 1
else
# can write to new file, so insert variable
echo "lastip=$IP" > $ipfile
echo "wrote current ip into new file $ipfile"
firstrun=1
fi
fi
# get ip last time script ran
source $ipfile
# compare ip in file to current
if [ $IP != $lastip ] || [ $firstrun = 1 ]
then
# not the same or first time running script. login, get cookie, then update.
echo "logging in to hover"
curl "https://www.hover.com/api/login" \
-X POST \
-G \
-d "username=${USERNAME}" \
-d "password=${PASSWORD}" \
-s \
-o /dev/null \
--cookie "cookies.txt" \
--cookie-jar "cookies.txt"
echo -e "\nupdating first dns entry"
curl "https://www.hover.com/api/dns/${DNS_ID}" \
-X PUT \
-d "content=${IP}" \
-s \
--cookie "cookies.txt" \
--cookie-jar "cookies.txt"
echo -e "\nupdating second dns entry"
curl "https://www.hover.com/api/dns/${DNS_ID2}" \
-X PUT \
-d "content=${IP}" \
-s \
--cookie "cookies.txt" \
--cookie-jar "cookies.txt" \
# save this new ip to file
echo "lastip=$IP" > $ipfile
echo
else
echo "ip hasn't changed"
fi
I got the "You must login first error" again, this is my current simple version
#!/bin/bash
[[ $# -lt 3 ]] && echo "Usage: $0 USERNAME PASSWORD DNS_ID"
USERNAME=${1}
PASSWORD=${2}
DNS_ID=${3}
# find your DNS ID here: https://www.hover.com/api/domains/yourdomain.com/dns/
# (replace "yourdomain.com" with your actual domain, and look for the record
# you want to change. The ID looks like: dns1234567)
IP=$(curl "https://api.ipify.org" -s)
curl -X "POST" "https://www.hover.com/api/login" \
--data-urlencode "username=${USERNAME}" \
--data-urlencode "password=${PASSWORD}" \
--cookie "cookies.txt" \
--cookie-jar "cookies.txt"
curl "https://www.hover.com/api/dns/${DNS_ID}" \
-X PUT \
-d "content=${IP}" \
-s \
--cookie "cookies.txt" \
--cookie-jar "cookies.txt"
echo
Any chance you guys have the 2FA activated? I had mine active. Disabling it, let me login.
Anyone have an example of calling this from Powershell?
it looks like they changed where they post the username/password json, line 10:
r = requests.post("https://www.hover.com/signin/auth.json", params=params)
I cannot get any PUT to work. I always get back: {"succeeded":false,"error":"Can not update attributes: dn"}
Has anyone been able to fix this?
As the API is entirely un-documented and un-supported, it appears they've made some changes recently :)
The API URL for a DNS update is now
https://www.hover.com/api/control_panel/dns
and the minimum JSON required to perform an update is
{"domain":{"id":"domain-YOURDOMAIN.COM","dns_records":[{"id":"dns12345678"}]},"fields":{"content":"127.0.0.1"}}
For anyone interested @mwoffenden , I've pulled this together into a powershell script which will update a single record.
The script demonstrates the bare essentials, nothing clever - but it does what I need it to for now :)
#Update which Hover record?
$dnsID = "dns12345678"
$dnsdomain = "YOURDOMAIN.com"
$username = "Username"
$password = "Password"
#Get current public IP (pretty cool little trick, don't adjust this line)
$myIP = Resolve-DnsName -Name myip.opendns.com -Server resolver1.opendns.com
#Connect to HoverAPI
$Headers = @{ "accept"="application/json";
"content-type"="application/json"}
$params = @{ "username"=$username;
"password"=$password}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$loginResult = Invoke-WebRequest -Uri "https://www.hover.com/api/login" -Method POST -Body ($params|ConvertTo-Json) -Headers $Headers -SessionVariable WebSession
#Check the login was successful
if($loginResult.Headers.'Set-Cookie' -notmatch "hoverauth" -or $loginResult.StatusDescription -ne "OK")
{
Write-Host "There has been a problem"
}
else
{
#update the record
$jsonRequest = '{"domain":{"id":"domain-' + $dnsdomain + '","dns_records":[{"id":"'+$dnsID+'"}]},"fields":{"content":"' + $myIP.IPAddress + '"}}'
$updateResult = Invoke-WebRequest -Uri "https://www.hover.com/api/control_panel/dns" -Method put -Body $jsonRequest -WebSession $WebSession
}
Cheers!
Nat
Does anyone have a working solution to create/delete a txt record for _acme-challenge using curl at the moment?
Thank you!
The API URL for a DNS update is now
https://www.hover.com/api/control_panel/dns
and the minimum JSON required to perform an update is
{"domain":{"id":"domain-YOURDOMAIN.COM","dns_records":[{"id":"dns12345678"}]},"fields":{"content":"127.0.0.1"}}
@l0cutis Thank you so much for your code. It still works today!
It confused me until I realized that domain-
part was needed alongside the actual domain name. If I give it a plain domain name, it will result in an empty error that looks like this: {"succeeded":false,"errors":[]}
Also, I'm kind of curious to know how you figured out the API. Did you capture packets? Or did you look at the source code?
@lorenzo95
To create a new record, you can use the following scheme.
URL: https://www.hover.com/api/control_panel/dns
Method: POST
JSON:
{
dns_record: {
"name": "_acme-challenge.YOUR_DOMAIN",
"type": "TXT",
"content": "CHALLENGE_CONTENT",
"ttl": "900"
},
id: "domain-YOUR_DOMAIN"
}
To destroy a record, you can use the following scheme.
URL: https://www.hover.com/api/control_panel/dns
Method: DELETE
JSON:
{
"domains":[
{
"id":"domain-YOUR_DOMAIN",
"name":"YOUR_DOMAIN",
"dns_records":TARGET_RECORD_ID
}
]
}
You can get TARGET_RECORD_ID from GET /api/dns
The above schemes are not specific to text records, so you can use it for other record types like A.
By the way, I figured this out through chrome dev tool's network tab (right click on the specific request and select copy)
Unfortunately though, hover enforced 2fa to all clients a while ago. So to use any of the API, you either need to setup a automatic email/sms receiving setup or disable it through the GUI temporarily (Hover will turn it back on after 1 hour).
As for the certificate renewal, one solution that I found online was to redirect it using an NS record, but hover currently does not seem to offer NS record on the DNS GUI, and it seems that you can't set it through the API either. (It returns a "success":true response, but it takes no effect.)
Another possible solution is to use the CNAME record as mentioned here: https://community.letsencrypt.org/t/cant-make-acme-challenge-ns-record-on-hover/50719/3 though I couldn't try as it was not outlined in detail.
The best solution for automation for now seems to be to use another DNS server entirely, but it's not ideal as it would cost extra each month.
There's a sister bug here: AnalogJ/lexicon#1602
Yes, the current suggestion is to use a different DNS provider.
My logins to hover.com are not 2FA enforced. I enabled 2FA a while ago, and as soon as I saw it broke my dynamic DNS updates, I turned it off. Just tried logging into the Hover website now and there was no prompt for 2FA, nor did it ask me to re-enable it or set it back up again. I am no longer using this code, but rather some calls to the Hover API with curl, and everything has continued to work fine ever since I turned 2FA off.
@barcoboy is this still working? Can you show the login curl please? I have no 2FA and keep getting locked out.
@wayneconnolly, here are the two curl calls I use to add an entry to my Hover DNS:
#Login
curl "https://www.hover.com/api/login" -H "Content-type: application/json" -X POST -d "{"password": "$PASSWORD", "username": "$USERNAME"}" -s -S -c $COOKIEJAR -o /dev/null
#Add to DNS
curl -k "https://www.hover.com/api/domains/mydomain.com/dns" -X POST -d "name=${NAME}" -d "type=TXT" -d "content=${DATA}" -s -S -b $COOKIEJAR
@wayneconnolly, here are the two curl calls I use to add an entry to my Hover DNS:
#Login curl "https://www.hover.com/api/login" -H "Content-type: application/json" -X POST -d "{"password": "$PASSWORD", "username": "$USERNAME"}" -s -S -c $COOKIEJAR -o /dev/null
#Add to DNS curl -k "https://www.hover.com/api/domains/mydomain.com/dns" -X POST -d "name=${NAME}" -d "type=TXT" -d "content=${DATA}" -s -S -b $COOKIEJAR
Thank you very much. I'll try this later today. I do note that the login is different so that's a positive thing. https://www.hover.com/signin/auth.json
. I emailed hover yesterday asking how to do it also. No idea if they will reply in a helpful manner.
UPDATE: I just tried with username password as you described and it gets a 404. A direct curl to that endpoint shows a 404 also. Could you please confirm on your end?
$ curl -k "https://www.hover.com/api/login"
<!DOCTYPE html>
<html>
<head>
<script src="https://browser.sentry-cdn.com/6.2.3/bundle.min.js" crossorigin="anonymous"></script>
<script src="https://browser.sentry-cdn.com/6.2.3/bundle.tracing.min.js" crossorigin="anonymous"></script>
<script>
if (window.Sentry) {
var release = "";
var username = "";
var admin = "";
Sentry.init({
dsn: "https://[email protected]/164872",
release,
ignoreErrors: [
"gCrWeb",
"hotjar",
"bat.bing.com",
// The rest of this list is taken from
// https://gist.github.com/impressiver/5092952
// which is linked from:
// https://docs.sentry.io/clients/javascript/tips/
"top.GLOBALS",
"originalCreateNotification",
"canvas.contentDocument",
"MyApp_RemoveAllHighlights",
"http://tt.epicplay.com",
"Can't find variable: ZiteReader",
"jigsaw is not defined",
"ComboSearch is not defined",
"http://loading.retry.widdit.com/",
"atomicFindClose",
"fb_xd_fragment",
"bmi_SafeAddOnload",
"EBCallBackMessageReceived",
"conduitPage",
],
whitelistUrls: [/www\.hover\.com/i],
integrations: [new Sentry.Integrations.BrowserTracing()],
});
if (username) {
Sentry.setUser({ username });
}
if (admin) {
Sentry.setContext("admin", {
username: admin,
});
}
}
</script>
<title>Domain Names | Buy Domains & Email At Hover.com</title>
<meta content='text/html; charset=UTF-8' http-equiv='content-type'>
<meta content='IE=Edge' http-equiv='X-UA-Compatible'>
<meta content='3CbaVvw-I7MlrmmmHz0bfbko7oMCW1mn2u65uWsWWB8' name='google-site-verification'>
<meta content='b6zle0gvjb63epbzegrrmbp4wwltnn' name='facebook-domain-verification'>
<meta content='width=device-width, initial-scale=1.0' name='viewport'>
<meta content='telephone=no' name='format-detection'>
<meta content='Find the perfect domain name for your idea at Hover. All domains come with industry-leading customer support and free WHOIS privacy. Name your passion today!' name='description'>
<meta content='Find the perfect domain name for your idea at Hover. All domains come with industry-leading customer support and free WHOIS privacy. Name your passion today!' property='og:description'>
<meta content='Domain Names | Buy Domains & Email At Hover.com' property='og:title'>
<meta content='/packs/src/application/images/home/og_hover-ff5e561a072494f142806a1ee8541fca.png' property='og:image'>
<link href='https://www.hover.com/api/login' rel='canonical'>
<link href='/site.webmanifest' rel='manifest'>
<link color='#229e87' href='/safari-pinned-tab.svg' rel='mask-icon'>
<link href='/apple-touch-icon-57x57.png' rel='apple-touch-icon' sizes='57x57'>
<link href='/apple-touch-icon-114x114.png' rel='apple-touch-icon' sizes='114x114'>
<link href='/apple-touch-icon-72x72.png' rel='apple-touch-icon' sizes='72x72'>
<link href='/apple-touch-icon-144x144.png' rel='apple-touch-icon' sizes='144x144'>
<link href='/apple-touch-icon-60x60.png' rel='apple-touch-icon' sizes='60x60'>
<link href='/apple-touch-icon-120x120.png' rel='apple-touch-icon' sizes='120x120'>
<link href='/apple-touch-icon-76x76.png' rel='apple-touch-icon' sizes='76x76'>
<link href='/apple-touch-icon-152x152.png' rel='apple-touch-icon' sizes='152x152'>
<link href='/apple-touch-icon-180x180.png' rel='apple-touch-icon' sizes='180x180'>
<link href='/favicon-196x196.png' rel='icon' sizes='196x196' type='image/png'>
<link href='/favicon-160x160.png' rel='icon' sizes='160x160' type='image/png'>
<link href='/favicon-96x96.png' rel='icon' sizes='96x96' type='image/png'>
<link href='/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png'>
<link href='/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png'>
<meta content='#229e87' name='msapplication-TileColor'>
<meta content='/mstile-144x144.png' name='msapplication-TileImage'>
<meta content='#229e87' name='theme-color'>
<link rel="stylesheet" media="all" href="/packs/fonts-fba7b87f80dc4ec5b49cfc53e3d92ff7.css" />
<link rel="stylesheet" media="all" href="/packs/application-3825aff153368bada4a3ba66c31869af.css" />
<link rel="stylesheet" media="all" href="/packs/hover_refresh-c7bcda836c7a2770625a7ecff139ed21.css" />
<link rel="stylesheet" media="all" href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,600" />
<script src="/packs/manifest-35aff3e5d85edee906e3.js"></script>
</head>
<body class='has_new_styles'>
<script>dataLayer = [{}];</script>
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-TDSBDF"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TDSBDF');</script>
<header class='main__header'>
<div class='main__logo'>
<a href='/' title='Return to home page'>
<img alt="Hover Logo" src="/packs/src/application/images/common/hv-logo-2020-e535dce1f67e9a648643e23cac750002.svg" />
</a>
</div>
<nav class='main__nav'>
<div class='nav__primary'>
<a class='nav__link' href='/domains' title='Domains'>Domains</a>
<a class='nav__link' href='/email' title='Email'>Email</a>
<a class='nav__link' href='/about' title='About Us'>About Us</a>
<a class='nav__link' href='https://hover.blog' title='Blog'>Blog</a>
</div>
<div class='nav__secondary'>
<a class='nav__link' href='https://help.hover.com'>Help</a>
<a class='nav__link has-more' data-trigger='signin' href='/signin'>
Sign In
<i class='fas fa-chevron-down'></i>
</a>
<div class='signin__menu'>
<a class='signin__item' href='/signin'>
<div class='signin__heading'>Control Panel</div>
<p>View and manage domains, email, and features for your account.</p>
<i class='far fa-arrow-right'></i>
</a>
<a class='signin__item' href='https://mail.hover.com/'>
<div class='signin__heading'>Webmail</div>
<p>Access your email inbox from your web browser.</p>
<i class='far fa-arrow-right'></i>
</a>
<a class='signin__item' href='/signup'>Need an account? Sign up</a>
</div>
</div>
<div class='nav__cart' style='display: none'>
<a class='cart__button' href='/cart'>
<i class='fas fa-shopping-cart'></i>
<span class='cart__count'></span>
</a>
</div>
</nav>
<a class='mobile__menu-trigger' data-trigger='menu'>
<i class='far fa-bars'></i>
</a>
</header>
<div aria-hidden='true' class='mobile__menu'>
<a class='mobile__menu-trigger' data-trigger='menu'>
<i class='fal fa-times'></i>
</a>
<nav class='mobile__nav'>
<a class='nav__link' href='/domains'>Domains</a>
<a class='nav__link' href='/email'>Email</a>
<a class='nav__link' href='/about'>About Us</a>
<a class='nav__link' href='https://hover.blog'>Blog</a>
<div class='nav__separator'></div>
<a class='nav__link' href='https://help.hover.com'>Help</a>
<a class='nav__link' href='https://mail.hover.com/'>Webmail</a>
<a class='nav__link' href='/signin'>Sign In</a>
</nav>
</div>
<div id='your_account_menu'>
<ul>
<li class='title'>
Your Account
</li>
</ul>
</div>
<main>
<div class='grid_container' id='content'>
<div class='home error not_found'>
<section class='hero' style='padding-top: 40px;'>
<img src='/packs/src/application/images/404-456216b46583c9e46ecec75ca1c626cc.svg' width='580'>
<h1>is a great number...but not a page at Hover.com.</h1>
</section>
</div>
</div>
</main>
<footer class='main__footer'>
<div class='footer__top'>
<div class='footer__brand'>
<div class='main__logo'>
<a href='/'>
<img src='https://hover-assets.s3.ca-central-1.amazonaws.com/images/logo.svg'>
</a>
</div>
<div class='footer__social'>
<a aria-label='Twitter' href='https://twitter.com/hover'>
<i class='fab fa-twitter'></i>
</a>
<a aria-label='Facebook' href='https://www.facebook.com/hover'>
<i class='fab fa-facebook-f'></i>
</a>
<a aria-label='LinkedIn' href='https://www.linkedin.com/showcase/hover-domains-and-email/'>
<i class='fab fa-linkedin-in'></i>
</a>
<a aria-label='TikTok' href='https://www.tiktok.com/@hoverdomainnames'>
<i class='fab fa-tiktok'></i>
</a>
</div>
</div>
<div class='footer__column footer__column--products'>
<div class='footer__heading'>Products</div>
<a class='footer__link' href='/domains'>Domains</a>
<a class='footer__link indented' href='/transfer-in'>Transfer</a>
<a class='footer__link indented' href='/renew'>Renew</a>
<a class='footer__link indented' href='/domain-pricing'>Pricing</a>
<a class='footer__link' href='/email'>Email</a>
</div>
<div class='footer__column footer__column--company'>
<div class='footer__heading'>Company</div>
<a class='footer__link' href='/about'>About Us</a>
<a class='footer__link' href='https://hover.blog'>Blog</a>
<a class='footer__link' href='https://www.tucows.com/careers/'>Jobs</a>
<a class='footer__link' href='/affiliates'>Affiliates</a>
</div>
<div class='footer__column footer__column--support'>
<div class='footer__heading'>Support</div>
<a class='footer__link' href='https://help.hover.com'>Help Center</a>
<a class='footer__link' href='https://hoverstatus.com/'>Service Status</a>
</div>
<div class='footer__column'>
<div class='footer__heading'>Account</div>
<a class='footer__link' href='/signin'>Sign In</a>
<a class='footer__link' href='https://mail.hover.com'>Webmail</a>
</div>
</div>
<div class='footer__bottom'>
<div class='footer__legal'>
<a href='/tos'>Terms of Service</a>
<a href='/privacy'>Privacy Policy</a>
</div>
<div class='footer__copyright'>
Copyright © 2023 Hover
</div>
</div>
</footer>
<script src="/packs/jquery-eb76d5d3fc0228c47ac1.js"></script>
<script src="/packs/application-c3f1f2b2a53c1db41d28.js"></script>
<script src="/packs/hover_refresh-fd258324985771c547ed.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-4171338-2', 'auto');
ga('require', 'linkid', 'linkid.js');
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
</script>
</body>
</html>
Sorry for the delay in getting back to you... slipped through the cracks!
a 404 error is normal to that URL when doing a GET request... you have use the POST method. Make sure you are doing that by using the "-X POST" command line parameter with curl.
Sorry for the delay in getting back to you... slipped through the cracks!
a 404 error is normal to that URL when doing a GET request... you have use the POST method. Make sure you are doing that by using the "-X POST" command line parameter with curl.
Thank you - curl "https://www.hover.com/api/login" -H "Content-type: application/json" -X POST -d "{"password": "$PASSWORD", "username": "$USERNAME"}" -s -S -c $COOKIEJAR -o /dev/null
I was using -X POST
.
I contacted hover.com and it's definately been disabled even with 2FA
Lei (Hover Help Center)
Jul 6, 2023, 12:05 EDT
Hello!
Thanks for your reply - API is no longer usable to log in to Hover.
If using an API is critical to your setup, I may suggest our sister company, [OpenSRS.com](http://opensrs.com/).
Best,
Just FYI - there's an example that does use 2FA. I've copied code from it. dns-lexicon seems to have support for 2FA now as well. https://github.com/pjslauta/hover-dyn-dns
Anyone else noticing this is broken? I think there may have been security changes at Hover regarding an incident. I'm receiving the following error on attempting a login:
requests.exceptions.ConnectionError: ('Connection aborted.', error(54, 'Connection reset by peer'))