Building OpenSRS Clients Using HTTP POST
A Simple Explanation That's Long Overdue
Every now and again, I get requests for a simple explanation on the basics of writing an OpenSRS client app that's capable of provisioning and managing domain names. In the process of answering these requests, I found that there wasn't any clear and dirt-simple documentation on rolling your own client, nor was the necessary information all in one place.
I've decided to rectify the problem by writing an article that covers coding your open OpenSRS client from the ground up. My plan is to first post it here on the Tucows Blog and eventually have it added to the official Tucows API documentation.
XML over HTTPS POST: The Simplest Way to Communicate with the OpenSRS Server
Messages to and from the OpenSRS servers are formatted as XML documents, and the simplest way to send and receive them is to use HTTPS POST. It's the method that requires the least setting up, it uses libraries that are very likely to be in the default installation of your favorite programming language and it minimizes the amount of code you have to write. For these reasons, I prefer using the HTTPS POST method when building OpenSRS clients.
I'll spend most of this article covering two major aspects of communicating with the OpenSRS servers:
- Authenticating your client
- Building and sending a message
Authenticating Your Client
In order to communicate with the OpenSRS servers, you client needs to be able to authenticate itself by meeting three requirements:
- It must provide the server with your OpenSRS username
- It must provide the server with a message digest based on the message content and your OpenSRS private key
- It must be at an IP address that is on the “approved” list for your OpenSRS username.

I'll cover these requirements in the next few sections.
Your OpenSRS Username
This is the easiest requirement to fulfill. If you have an OpenSRS account, you have an OpenSRS username.
(If you don't have an OpenSRS account, you can sign up for one here!)
Generating Your Private Key
As I mentioned earlier, one of the requirements for communicating with the server is a message digest generated using the message you want to send to the server and your OpenSRS private key.
Remember that there are two systems: the Live system and the “Horizon” testing system. Each is its own separate system, and your private key on one system will not work on the other.
If you've never generated your OpenSRS private key before, or if you've forgotten or lost your private it, you'll need to generate a new one.
To generate a new private key, navigate your browser to the appropriate Reseller Web Interface (RWI) page. Here's a link for the RWI for the Live system, and here's a link for the RWI for Horizon. Log in and scroll to the Profile Management section near the bottom of the page:

Near the bottom of the Profile Management section is a link marked Generate New Private Key. Click on it. You'll be greeted with the following dialog box:

Click OK. This will take you to the following page:

Right below the text that reads Cut and paste the private key listed below into your OpenSRS.conf file: is your new private key. Copy it and paste it somewhere for now — we'll use it shortly.
Two things to note about the private keys:
- A newly-generated private doesn't become active until about two minutes after it's generated. Wait the two minutes before using it!
- Private keys are 112 characters long.
Validating Your IP Address
As an added security measure, OpenSRS will only accept requests from IP addresses on the “approved” list for your OpenSRS account.
The link for adding an IP address to the “approved” list is just above the Generate New Private Key link, in the Profile Management near the bottom of the RWI main page:

Click on that link to take you to the OpenSRS Script/API Access page, where you can add your computer's IP address to the approved list.

A couple of things to note about the IP address list:
- Remember that the IP address list applies only to your account and for the specific environment (Live or Horizon).
- If you need to specify a range of IP addresses, use the Net Block drop-down menu to specify a range of 2, 4, 8, 16, 32, 64 or 128 addresses.
- You can add up to 5 IP addresses or ranges of IP addresses to the “approved” list.
- Once added to the “approved” list, it takes about an hour for the addition to take effect.
Constructing the Message
Now that we've got the authentication details out of the way, let's take a look at how a message to the OpenSRS servers is built when sending it via HTTPS POST.
It's pretty simple:
- The actual XML of the message goes into the body section of the message
- Additional information — such as authentication credentials — go into the message's headers.
The table below shows which headers are required and what goes in them, as well as what goes in the body:
Structure of Tucows API Messages Sent Using HTTPS POST | ||
|---|---|---|
Headers | Content-Type | text/xml |
X-Username | Your Tucows API username goes here | |
X-Signature | The message digest (see below) goes here | |
Content-Length | The message length goes here | |
Body | The message goes here | |
Let's take a look at the trickiest part: the message digest.
Building the Message Digest
The message digest is built using the message that you want to send to the OpenSRS servers, your OpenSRS private key and an MD5 hash function that outputs its results in hexadecimal.
Here are the steps to generating the message digest:
- Concatenate your private key to the end of the message that you want to send to the OpenSRS servers.
- Generate an MD5 hexadecimal hash of that string.
- Take the resulting hash and concatenate your private key to the end of it.
- Generate an MD5 hexadecimal hash of that string.
The result of these steps is the message digest, which should be put into the X-Signature header.
Here's a graphic that illustrates generating a message digest:

A Simple API Command
The only thing left to do is choose the command that we want to send to the server. The structure of every possible API call is described in the OpenSRS API Specification [ online version | PDF version ], which includes request and response examples in both Perl and XML.
The example I use all the time is the Lookup Domain command because it's very simple. Here's the XML from page 198 of the OpenSRS API Specification for this command. which returns the status of a domain, which can be either “available” or “taken”.
Let's take a look at the XML for a Lookup Domain command that checks the availability of example.com. I've highlighted the parts that are of the most interest for this particular example.
<?xml version="1.0" encoding="UTF-8" standalone='yes'?>
<!DOCTYPE OPS_envelope SYSTEM 'ops.dtd'>
<OPS_envelope>
<header>
<version>0.9</version>
</header>
<body>
<data_block>
<dt_assoc>
<item key=”protocol”>XCP</item>
<item key=”action”>LOOKUP</item>
<item key=”object”>DOMAIN</item>
<item key=”attributes”>
<dt_assoc>
<item key=”domain”>example.com</item>
</dt_assoc>
</item>
</dt_assoc>
</data_block>
</body>
</OPS_envelope>
The first area of highlighted text specifies that we're sending a message using the XCP protocol, which is the protocol for provisioning and managing domain names (the other one, which is for all other Tucows services except Blogware, is called TPP). It also specifies that the command that we want to execute is the Lookup Domain command.
The second area of highlighted text specifies the domain name whose status we'd like to look up: example.com. (RFC 2606 states that example.com, example.org and example.net are reserved and cannot be registered; they are “taken” by definition.)
Once the message above is received and acted upon by the OpenSRS servers, a response similar to the one below is returned. Once again, I've highlighted the parts that are of the most interest:
<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
<!DOCTYPE OPS_envelope SYSTEM “ops.dtd”>
<OPS_envelope>
<header>
<version>0.9</version>
</header>
<body>
<data_block>
<dt_assoc>
<item key=”protocol”>XCP</item>
<item key=”object”>DOMAIN</item>
<item key=”response_text”>response_text_goes_here</item>
<item key=”action”>REPLY</item>
<item key=”attributes”>
<dt_assoc>
<item key=”status”>domain_status_goes_here</item>
<item key=”match”></item>
</dt_assoc>
</item>
<item key=”response_code”>response_code_goes_here</item>
<item key=”is_success”>either_0_or_1</item>
</dt_assoc>
</data_block>
</body>
</OPS_envelope>
The two most important parts of the response are:
- The
<item key="is_success">element, which specifies whether or not the command was successfully executed. This element appears in the response for every OpenSRS API command and should be the first thing that a client application checks. The value returned is1 if the command was successfully executed,0otherwise. - The
<item key="response_code">element, which is a numeric code that describes the response. This element also appears in the response for every OpenSRS API command and should be the second thing that a client application checks.
Im the case of the Lookup Domain command, the response codes that you'll see most of the time are:
210: Domain available.211: Domain taken.
You might have noticed the <item key="response_text"> and <item key="status"> elements in the the XML for the response. The information returned in these elements the same as the information in the <item key="response_code"> element; they're just spelled out in English. For example, if a domain is taken…
- the
<item key="response_code">element contains210 - the
<item key="response_text">element contains “Domain taken” - and the
<item key="status">contains “taken”.
I generally prefer to use the contents of the <item key="response_code"> element, as this code is less likely to be changed in future versions of the API.
Putting it All Together in Ruby
I've just presented you with enough information to write a quick little client application that checks a domain name for availability.
I'll use the Ruby programming language. A number of programmers I know are very interested in that particular programming language, partly because of the Ruby on Rails web application framework and partially because of the resurgence of interest in dynamic functional programming languages. It's also a language that isn't covered in the current documentation.
The application will be a simple command-line tool. To check a domain name for availability, the user should simply have to entr this at the command line:
ruby lookup-domain.rb domain_name
Let's get coding!
First, let's make sure that the necessary libraries, net/https, digest/md5 and rexml/document are loaded:
require 'net/https'
require 'digest/md5'
require 'rexml/document'
include REXML
Now let's define the constant OPENSRS_SERVER. To connect to the Live server, use this code:
# Definition for Live server
OPENSRS_SERVER = 'rr-n1-tor.opensrs.net'
If you'd rather connect to the “Horizon” test server, you'll want to use this code instead:
# Definitions for "Horizon" test server
OPENSRS_SERVER = 'horizon.opensrs.net'
I recommend that you connect to the Live server for this particular application. You'll get real results, and there's no risk of you losing any money (some API commands, such as the one for registering a domain name, will debit your OpenSRS account).
Let's define a few constants…
OPENSRS_PORT = 55443
OPENSRS_USERNAME = 'your_username'
OPENSRS_PRIVATE_KEY = 'your_private_key'
DOMAIN_AVAILABLE = 210
DOMAIN_TAKEN = 211
We'll want to store the first command line argument, which is the domain name whose status we want to check:
domain = ARGV[0]
And now, the message:
requestXml = <<-END
<?xml version=”1.0″ encoding=”UTF-8″ standalone='yes'?>
<!DOCTYPE OPS_envelope SYSTEM 'ops.dtd'>
<OPS_envelope>
<header>
<version>0.9</version>
</header>
<body>
<data_block>
<dt_assoc>
<item key=”protocol”>XCP</item>
<item key=”action”>LOOKUP</item>
<item key=”object”>DOMAIN</item>
<item key=”attributes”>
<dt_assoc>
<item key=”domain”>#{domain}</item>
</dt_assoc>
</item>
</dt_assoc>
</data_block>
</body>
</OPS_envelope>
END
And once we've defined the message, we can define a message digest:
message_digest = Digest::MD5.hexdigest(
Digest::MD5.hexdigest(requestXml + OPENSRS_PRIVATE_KEY) + OPENSRS_PRIVATE_KEY
)
While all that preamble out of the way, we can send the message:
http = Net::HTTP.new OPENSRS_SERVER, OPENSRS_PORT
begin
http.use_ssl = true
req = Net::HTTP::Post.new('/')
req['Content-Type'] = 'text/xml'
req['X-Username'] = OPENSRS_USERNAME
req['X-Signature'] = message_digest
req['Content-Length'] = requestXml.size.to_s
response = http.request(req, requestXml)
ensure
http.finish if http.started?
end
And now, it's time to parse the message and display the result:
response_doc = Document.new(response.body)
is_success = response_doc.root.elements[
"body/data_block/dt_assoc/item[@key='is_success']“
].text.to_i
response_code = response_doc.root.elements[
"body/data_block/dt_assoc/item[@key='response_code']“
].text.to_i
if is_success == 1
case response_code
when DOMAIN_AVAILABLE
puts “#{domain} is available.”
when DOMAIN_TAKEN
puts “Somebody already registered #{domain}.”
else
puts “This is one of those rare weird cases —
response code #{response_code}.”
end
else
puts “Something weird happened at the server end —
response code #{response_code}. Try again.”
end

good
Comment by Anonymous — May 14, 2007 @ 9:55 pm
Thank you so much. For some reason I've always had such a hard time with the official docs. I spent so much time - over the past couple years on and off trying to understand it and always failed to get anywhere. This just sets it straight. I'm using Perl instead of Ruby, but I was able to easily port your example to Perl and it worked!
Comment by Anonymous — May 25, 2007 @ 9:59 pm
Can you do the same but for the TPP protocol?
Thanks!
Comment by Bernard Bolduc — November 13, 2007 @ 11:32 am