Aller au contenu Aller au menu principal Aller au menu secondaire Aller au pied de page

Using the .fr API with Python (2/4)

Home > Observatory and resources > Expert papers > Using the .fr API with Python (2/4)
10/31/2022

We’ve presented the general workings of the .fr API. Let’s see now how to use it in practice in different programming languages. Let’s start with Python.

Warning, to simplify this article we will omit part of the code, particularly the handling of errors. That would not of course be acceptable with a real program. A more complete version of the program can be found at https://gitlab.rd.nic.fr/afnic/code-samples/-/tree/main/API/Python.

The technical choices

Python has, in its standard library, means of making HTTP requests. However, many Python programmers prefer the requests library, which is simpler to use. And this is what we shall use here.

Authentication

def get_token():

    payload = {'client_id': 'registrars-api-client',

               'username': login, 'password': password,

               'grant_type': 'password'}

    r = requests.post("https://login-sandbox.nic.fr/auth/realms/fr/protocol/openid-connect/token",

                      data=payload)

    body = json.loads(r.text)

    return body["access_token"]

This code defines a function that will make a HTTP request using the POST method to obtain the token. For this it will pass a series of key-value pairs1 indicating in particular the client’s password. The result is analysed with the functions of the json module from the standard library. Once the token has been obtained (it is one of the members of the JSON object sent), we can make a dictionary of the fields necessary for the headers of the future HTTP requests:

def get_headers():

    token = get_token()

    return {'Accept': 'application/json',

            'Content-Type': 'application/json',

            'Extensions': 'FRNIC_V2' ,

            'Authorization': "Bearer %s" % token}

Errors that may occur

In the event of an error, you’ll receive a HTTP response status code indicating the problem, and a response coded in JSON giving further details. Thus you will be able to deal with the errors with something like:

if response.status_code != 200:

    body = json.loads(response.text)

    raise Exception("HTTP status code for delete is %s (reason \"%s\")" % \              

      (response.status_code, body))2

List of our domains

headers = afnic.get_headers()

response = requests.get("https://api-sandbox.nic.fr/v1/domains",

                        headers=headers)

body = json.loads(response.text)

for domain in body["content"]:

    print(domain["name"])

We make a GET request to the domain management URI by adding the necessary headers. There are no arguments to this request. The result is in JSON, which will be analysed, transforming it into a Python object. We have only to iterate this object, printing the list of the domain names we manage.

This code has a limitation: the list of domains may be long, and it only recovers the first N names3. To recover them all, we need a loop to successively call up several pages of domains. A complete example is shown in the Gitlab repository.

Availability enquiry

This service is often referred to as DAS, Domain Availability Service.

list = sys.argv[1:]

list = {'names': list}

headers = afnic.get_headers()

response = requests.post("https://api-sandbox.nic.fr/v1/domains/check",

                         headers=headers, data=json.dumps(list))

body = json.loads(response.text)["response"]

for name in body:

    print(name)

The list of domain names we’re interested in is obtained from the command line (le sys.argv). We make a Python dictionary, which json.dumps will encode later into JSON syntax. The request this time uses the HTTP POST method. The response is a JSON object, which we analyse, before iterating and posting the information on each name (which we do not seek to analyse further):

% ./das.py foobar.fr nic.fr stuff.com

{'name': 'foobar.fr', 'available': True}

{'name': 'stuff.com', 'available': False, 'reason': 'ZONE_UNKNOWN'}

{'name': 'nic.fr', 'available': False, 'reason': 'IN_USE'}

One name is available for registration, another is already in use, the last one name is available for registration being in another TLD.

Creation of a domain

args = {'name': sys.argv[1],

        "authorizationInformation": "Very1234secure",

        "registrantClientId": contact,

        "contacts": [

            {

                "clientId": contact,

                "role": "ADMINISTRATIVE"

            },

            {

                "clientId": contact,

                "role": "TECHNICAL"

            }

        ]}

headers = afnic.get_headers()

response = requests.post("https://api-sandbox.nic.fr/v1/domains",

                         headers=headers, data=json.dumps(args))

body = json.loads(response.text)

print(body)

Here, the domain name to be created is indicated on the command line (sys.argv[1]). The creation of a name requires passing a more complex JSON object where we indicate, in addition to this name, the “authinfo” that will serve to authenticate the transfers, and above all the holder of the name and the contacts. Here, we have chosen to use the same identifier for the holder, the technical contact and the administrative contact. This identifier is the one obtained when creating a contact (not shown here), such as “CTC65093”.

Deletion of a domain

domain = sys.argv[1]

headers = afnic.get_headers()

response = requests.delete("https://api-sandbox.nic.fr/v1/domains/%s" % domain,

                         headers=headers)

body = json.loads(response.text)

print(body)

The only new features here are the use of the HTTP DELETE method which, as its name suggests, is used to destroy the object concerned, and the fact that the identifier of this object, the domain name, is in the URI.

Adding a name server to all the domains

The main advantage of the API is obviously the possibility of carrying out operations on all or part of your domains. Here, we’re going to add a new name server to all the names in the portfolio4. So we’re going to recover the list of domains of this portfolio and then make a loop:

response = requests.get("https://api-sandbox.nic.fr/v1/domains",

                        headers=headers)

if response.status_code != 200:

    raise Exception("HTTP status code for list is %s" % response.status_code)

body = json.loads(response.text)

for domain in body["content"]:

    args = {'name': domain['name'], 'nameServersToAdd': [ns]}

    response = requests.patch("https://api-sandbox.nic.fr/v1/domains/",

                         headers=headers, data=json.dumps(args))

Note the use of the PATCH method to modify an object (here, a domain). Note also that you must pass as an argument an array of the servers to be added, an array which is here reduced to a single element5.

Authorisation codes

When you request the creation of a domain that’s on the list of terms subject to prior examination, you must provide an authorisation code allocated by Afnic precisely after this prior examination. Here is how to request such a code:

payload = {'domainName': domain, 'registrantClientId': client,

                'justification': "Because I’m worth it"}

response = requests.post("https://api-sandbox.nic.fr/v1/registrar/authorization-code-requests",

                         headers=headers, data=json.dumps(payload))

body = json.loads(response.text)

if response.status_code != 201:

    raise Exception("HTTP status code for auth. code request is %s (reason \"%s\")" % (response.status_code, body))

print("Authorization code %s requested" % body["repositoryObjectId"])

You obtain an identifier which will allow you to follow the progress of your request:

response = requests.get("https://api-sandbox.nic.fr/v1/registrar/authorization-code-requests/%s" % requestId,

                         headers=headers)

body = json.loads(response.text)

print("Request %s for domain %s is in state %s" % (body["repositoryObjectId"], body["domainName"], body["status"]))

if "code" in body:

    print("Authorization code is %s (valid until %s)" % (body["code"], body["codeExpirationDate"]))

If the request is accepted by the registry, the status will be ACCEPTED (with the authorisation code in the “code” field) and, if it is refused by the registry, the status will be REFUSED.


1 – We don’t use JSON for the request.

2 – So this example will display a detailed message for the user. This is not a problem for a command line program, but if you are relaying requests from users of a website, good security practice for a production application advises us not to display errors to end users, as these may reveal intimate details about the system used.

3 – N is currently 20.

4 – The API obviously has the disadvantages inherent in its advantages: it allows an automatic operation to be carried out on the entire portfolio. But if the program has a bug, the entire portfolio will be affected. So re-read your code well before executing it (unless you’re using the sandbox).

5 – The PATCH method also allows other changes to the domain, such as to its status or adding DNSSEC keys.