Python SDK discontinued, alternatives for application management?


The Python SDK is discontinued in December 2019 (ref github repo), and is no longer working for me, I get AttributeError: module 'ttn' has no attribute 'HandlerClient'.

I used this to create new devices etc., what are the alternatives? The github page says see for more information, but this is for v3.

I found the following:

However, I have some questions.
The input, to the endpoint ( POST /applications/{app_id}/devices), is the following:

  "altitude": 0,
  "app_id": "some-app-id",
  "attributes": {
    "key": "",
    "value": ""
  "description": "Some description of the device",
  "dev_id": "some-dev-id",
  "latitude": 52.375,
  "longitude": 4.887,
  "lorawan_device": {
    "activation_constraints": "local",
    "app_eui": "0102030405060708",
    "app_id": "some-app-id",
    "app_key": "01020304050607080102030405060708",
    "app_s_key": "01020304050607080102030405060708",
    "dev_addr": "01020304",
    "dev_eui": "0102030405060708",
    "dev_id": "some-dev-id",
    "disable_f_cnt_check": false,
    "f_cnt_down": 0,
    "f_cnt_up": 0,
    "last_seen": 0,
    "nwk_s_key": "01020304050607080102030405060708",
    "uses32_bit_f_cnt": true

It says “all fields must be supplied”. What do I when I use OTAA, do I leave the app_s_key, dev_addr, nwk_s_key etc fields emtpy?

"app_s_key": "",
"nwk_s_key": "",
"dev_addr": "",

My script would look something like this:

import requests
access_key = "secret-xxxxxxxxx.."
key= "Key {}".format(access_key)
endpoint = "<app_id>/devices/"
params = dict linked above,headers={"Authorization": key}, params = params)

However, im getting the following error msg: Invalid Device: DevID not valid: can not be empty

Any help?


The full thing? If not, what did you post exactly?

A quick search seems to show that a limited payload should indeed work. I think “all fields must be supplied” is false, and also attributes being a single object rather than an array/list seems an error in the documentation, which is confirmed by the source code of the discontinued Python SDK. The same source code suggests that most data is optional.


I tried with the same limited payload from the post you linked, but I get Invalid Device: DevID not valid: can not be empty. Below is the full code. App keys etc. are random dummy numbers with the correct length, and the app_key matches the app key found in the application.

import requests
import json

endpoint = ''
accessKey = 'ttn-account-v2.xxxxxxxxxxxxxxxxxxxxxxxxxx'
key = f'Key {accessKey}'

params = {"lorawan_device": {
           "dev_id": "474849", 
           "dev_eui": "70424ff23ae42345", 
           "app_key": "ad96e95f37bba1441ea836bbf8b44f09", 
           "app_eui": "70b3d57ed0027366",
           "app_id": "test4879", 
           "activation_constraints": "local", 
           "uses32_bit_f_cnt": True}, 
         "app_id": "test4849", 
         "dev_id": "474879"}
params_json = json.dumps(params)
response =,headers={'Authorization': key}, params = params_json)

data = response.json()

I assume you meant app_eui, not app_key? (AppKey can be anything, and preferably is different for each device. AppEUI must be one of the AppEUIs known to the application test4849.)

Fun fact, and maybe the solution: the limited payload I linked to earlier also uses POST, but even then also uses the device ID in the URL. I’d only expect that for PUT (when updating a device), but not for POST (when creating a device). Also, the documentation you linked to, does not use the device ID in the URL. But it might not hurt to try…?

(When that does not help, then that might actually be the cause of the error in that topic I linked to…)

Yes, typo sorry.

I tried using

with the same code previously linked, but I’m getting the same error message. I’m using the GetDevice get endpoint with success, by the way.

I assume (and very much expect) things are the same when the DevEUI device ID is not all-numeric…?

(The regular expression used in the validation code allows for that, but it’s reporting empty, not invalid… Maybe, just maybe, plain numbers are lost somewhere earlier in the chain of handling API calls. Nah, I really don’t expect that…)

Also, did you try using curl?

With response =,headers={'Authorization': key}, params = params_json) the input params has to be called data, with that changed, everything works as expected. Such a silly mistake…, no wonder it called for missing data…

1 Like