Receiving 403 when posting to /xml-api/listacct


Jun 9, 2011

I am trying to post to /xml-api/listacct? but it keep receiving an http 403 and I am never presented with a 401. Also, what are the differences between 2082/2083 and 2086/2087 I see among the pairs themselves the lower numbered one is non-ssl, but between the two groups I don't know the difference. Here is my code in Python:

I am trying to post some data to a form using urllib2, but instead of
being presented with a 401 and then sending my credentials I am
constantly being present with an HTTP 403.

For some reason doesn't seem like the auth header is even being sent
and the user-agent header is sent after connection-close http

>>> print platform.python_version()

Here is what happens according to python (also corroborated on the
server side as well, just incase). I'm also not sure the header
order is exactly a problem, but I'm 95% sure.

Sample run:

$ ./cptest

send: 'POST /xml-api/listaccts? HTTP/1.1\r\nAccept-Encoding:
identity\r\nContent-Length: 13\r\nHost:
application/x-www-form-urlencoded\r\nConnection: close\r\nUser-Agent:
Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv,
Gecko/20101203 Firefox/3.6.13\r\n\r\n'
send: 'domain=anjopa'
reply: 'HTTP/1.1 403 Forbidden\r\n'
header: Connection: close
header: Server: cpsrvd/
header: Content-type: text/xml
Traceback (most recent call last):
File "./cptest", line 61, in <module>
File "./cptest", line 55, in queryCpanel
response =, paramaters)
File "/usr/lib/python2.6/", line 397, in open
response = meth(req, response)
File "/usr/lib/python2.6/", line 510, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/lib/python2.6/", line 435, in error
return self._call_chain(*args)
File "/usr/lib/python2.6/", line 369, in _call_chain
result = func(*args)
File "/usr/lib/python2.6/", line 518, in http_error_default
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 403: Forbidden

import sys
from xml.dom.minidom import parse, parseString
import urllib
import urllib2
import base64
from cookielib import CookieJar

# Turn on HTTP debugging
11.5. Setting the User-Agent
import httplib
httplib.HTTPConnection.debuglevel = 1
#this doesn't seem to work

url = 'http://servername/:2086/xml-api/listaccts?'

# username = username'
# password = 'pass'
paramaters = urllib.urlencode(dict(domain='anjopa'))

def query():

# Instantiate and Initialize AuthInfo Handler for use w/ the build_opener

authinfo = urllib2.HTTPBasicAuthHandler()
authinfo.add_password(realm="Web Host Manager",

# Instantiate Cookie jar Handler for use w/ build_opener

cj = CookieJar()

# Create an opener object from list of handlers above

opener = urllib2.build_opener(authinfo,urllib2.HTTPCookieProcessor(cj),

# Add headers here:`
opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Windows; U;
Windows NT 6.1; en-US; rv, Gecko/20101203 Firefox/3.6.13')]

#probably un-necessary, but supposedly this makes the url open use
the above handlers by default in our program

# The data is actually sent at this line
# The presence of the paramaters argument signals urlopen to do a
POST instead of a GET
response =, paramaters)

if __name__ == '__main__':

Anyone know where I can with this one?


Dennis O.


Well-Known Member
Apr 29, 2005
Houston, TX
cPanel Access Level
Root Administrator
Not sure what the problem with what you are doing is, but here's how I handle requests in python:

import urllib2, base64
user = 'root'
password = 'somepass'
req = urllib2.Request('https://localhost:2087/xml-api/listaccts', {}, {'Authorization':'Basic ' + base64.b64encode( user + ':' + password ) } )
res =  urllib2.urlopen(req)
Of course you may want to add a few things into this for production use, such as putting the urlopen() call into a try-catch, etc.


Jun 9, 2011
Hi Matt/Anyone else,

(before you read into my text, I wanted to ask ask a quick follow up, how can limit my output to just a domain or a user, ip, etc? I thought I could just past it as parameters in my post but that doesn't seem to be working, like domain=sample should match all domains that have sample in them, no?)

Thank you! This works perfectly. I don't understand what the difference between the open_builder and request objects are. It almost seems as if I can use a request object in a build_opener function, but I am not sure how.

I think the crux of the problem with my last piece of code is I think it's not sending it because it wasn't first presented with a 401, so I don't think order of the headers really mattered. Some applications send anonymous application and then when presented with a 401 they will send the authorization header, but the cpanel when you don't send the authorization header it send a 403 (Forbidden). The odd thing is your piece of code works perfectly, because you added the headers manually.

If I add this to my opener it works as well:
opener.addheaders = [('Authorization: Basic', base64.b64encode( username + ':' + password )) ]

Thank you for your awesome help!


Jul 2, 2011
I have already found a way to authenticate with Cpanel remote access key.
Both examples (with login\pass and with Cpanel hash) I have posted on my blog

Thank you!