PA-API OAuth2 Python Example
The following is an example of accessing the Telldus PA-API using Python and OAuth 2.0.
This example requires the following 3rd party Python packages: Flask, requests
You can install them using pip:
pip install -U flask requests
The Example Project
Running the Project
You can run the project's main application using Python from the root of the project folder:
python app.py
Then open your browser at http://127.0.0.1:5000.
### Project Directory
You can organise your project directory as follows:
telldus_oauth2_flask_app/
│
├── app.py
├── config.py
├── requirements.txt
└── templates/
└── index.html
App & Configurations
Configuration
We define some commonly used constants here.
config.py
# config.py import os # Obtain the client ID and client secret from https://pa-api.telldus.com/keys/showToken CLIENT_ID = 'Your client ID (Public key)' CLIENT_SECRET = 'Your client secret (Private key)' # Default Telldus Live! account credentials USERNAME = 'your@email.com' PASSWORD = 'your_password' # OAuth2 and API endpoints TOKEN_URL = 'https://pa-api.telldus.com/oauth2/accessToken' API_BASE_URL = 'https://pa-api.telldus.com/oauth2' # Scope requested during token exchange SCOPE = 'live-app' # Flask secret key for session management SECRET_KEY = os.urandom(24) # User-Agent sent with all API requests USER_AGENT = 'MyTelldusApp/1.0'
requirements.txt
Flask requests
The Flask Application
The main application.
app.py
# app.py
from flask import Flask, session, redirect, url_for, request, render_template, flash
import requests
import config
import json
app = Flask(__name__)
app.config.from_object('config')
# ---------------------------------------------------------------------------
# OAuth2 helpers
# ---------------------------------------------------------------------------
def obtain_tokens(username, password):
"""Obtain OAuth2 access and refresh tokens using the password grant."""
payload = {
'grant_type': 'password',
'username': username,
'password': password,
'scope': config.SCOPE,
'client_id': config.CLIENT_ID,
'client_secret': config.CLIENT_SECRET
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': config.USER_AGENT
}
response = requests.post(config.TOKEN_URL, data=payload, headers=headers)
response.raise_for_status()
data = response.json()
access_token = data.get('access_token')
refresh_token = data.get('refresh_token')
if not access_token:
raise ValueError(data.get('error_description', 'Failed to obtain access token.'))
return access_token, refresh_token
def refresh_tokens(refresh_token):
"""Obtain a new access token using a refresh token."""
payload = {
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
'client_id': config.CLIENT_ID,
'client_secret': config.CLIENT_SECRET
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': config.USER_AGENT
}
response = requests.post(config.TOKEN_URL, data=payload, headers=headers)
response.raise_for_status()
data = response.json()
return data.get('access_token'), data.get('refresh_token')
def api_get(path, access_token, params=None):
"""Perform an authenticated GET request to the Telldus OAuth2 API."""
headers = {
'Authorization': f'Bearer {access_token}',
'User-Agent': config.USER_AGENT
}
url = f'{config.API_BASE_URL}/{path}'
response = requests.get(url, headers=headers, params=params or {})
response.raise_for_status()
return response.json()
def print_debug_message(message):
print(f'[DEBUG] {message}')
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@app.route('/')
def index():
access_token = session.get('access_token')
print_debug_message('Accessing home page.')
if not access_token:
print_debug_message('No access token found. User not authenticated.')
return render_template('index.html', authenticated=False)
# Example API call: list devices
params = {
'supportedMethods': 65535,
'format': 'json'
}
print_debug_message(f'Making API call to {config.API_BASE_URL}/devices/list')
try:
result = api_get('devices/list', access_token, params)
devices = result.get('device', [])
print_debug_message(f'Devices fetched: {json.dumps(devices, indent=2)}')
except Exception as e:
flash('An error occurred while fetching devices.', 'danger')
print_debug_message(f'Exception during API call: {str(e)}')
devices = []
return render_template('index.html',
authenticated=True,
devices=devices,
access_token=access_token)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username or not password:
flash('Username and password are required.', 'warning')
return redirect(url_for('login'))
print_debug_message(f'Attempting OAuth2 login for user: {username}')
try:
access_token, refresh_token = obtain_tokens(username, password)
print_debug_message('OAuth2 tokens obtained successfully.')
except Exception as e:
flash(f'Login failed: {e}', 'danger')
print_debug_message(f'Login failed: {e}')
return redirect(url_for('login'))
session['access_token'] = access_token
session['refresh_token'] = refresh_token
flash('You have successfully logged in.', 'success')
return redirect(url_for('index'))
return render_template('index.html', authenticated=False, show_login_form=True)
@app.route('/logout')
def logout():
print_debug_message('Logging out user.')
session.clear()
flash('You have been logged out.', 'success')
return redirect(url_for('index'))
@app.route('/devices')
def devices():
access_token = session.get('access_token')
print_debug_message('Accessing devices route.')
if not access_token:
flash('You need to log in first.', 'warning')
return redirect(url_for('index'))
params = {'supportedMethods': 65535, 'format': 'json'}
try:
result = api_get('devices/list', access_token, params)
devices = result.get('device', [])
except Exception as e:
flash('Failed to fetch devices.', 'danger')
print_debug_message(f'Exception: {e}')
devices = []
return render_template('index.html',
authenticated=True,
devices=devices,
show_devices=True)
@app.route('/clients')
def clients():
access_token = session.get('access_token')
print_debug_message('Accessing clients route.')
if not access_token:
flash('You need to log in first.', 'warning')
return redirect(url_for('index'))
params = {'format': 'json'}
try:
result = api_get('clients/list', access_token, params)
clients = result.get('client', [])
except Exception as e:
flash('Failed to fetch clients.', 'danger')
print_debug_message(f'Exception: {e}')
clients = []
return render_template('index.html',
authenticated=True,
clients=clients,
show_clients=True)
if __name__ == '__main__':
app.secret_key = config.SECRET_KEY
app.run(debug=True)
The HTML Template
The home page also serves as the login form and results view.
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Telldus OAuth2 Flask App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<h1 class="mb-4">Telldus Live! OAuth2 Integration</h1>
{% if not authenticated %}
{% if show_login_form %}
<p>Enter your Telldus Live! credentials to connect.</p>
<form method="POST" action="{{ url_for('login') }}">
<div class="mb-3">
<label for="username" class="form-label">Username (email)</label>
<input type="email" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Connect with Telldus</button>
</form>
{% else %}
<p>You are not connected to Telldus Live!.</p>
<a href="{{ url_for('login') }}" class="btn btn-primary">Connect with Telldus</a>
{% endif %}
{% else %}
<p>You are connected to Telldus Live!</p>
<a href="{{ url_for('logout') }}" class="btn btn-danger">Logout</a>
<hr>
{% if show_devices or devices is defined %}
<h2>Devices</h2>
{% if devices %}
<ul class="list-group">
{% for device in devices %}
<li class="list-group-item">
<strong>{{ device.name }}</strong> (ID: {{ device.id }})
</li>
{% endfor %}
</ul>
{% else %}
<p>No devices found.</p>
{% endif %}
{% endif %}
{% if show_clients and clients is defined %}
<h2>Clients</h2>
{% if clients %}
<ul class="list-group">
{% for client in clients %}
<li class="list-group-item">
<strong>{{ client.name }}</strong> (ID: {{ client.id }})
</li>
{% endfor %}
</ul>
{% else %}
<p>No clients found.</p>
{% endif %}
{% endif %}
{% if not show_devices and not show_clients %}
<h2>Quick Links</h2>
<a href="{{ url_for('devices') }}" class="btn btn-info me-2">View Devices</a>
<a href="{{ url_for('clients') }}" class="btn btn-info">View Clients</a>
{% endif %}
{% endif %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Command-Line Alternative
If you only need to obtain tokens without a web interface, you can use a standalone script. The repository includes oauth2.py for this purpose.
Create a config.ini file:
[oauth2] client_id = Your client ID client_secret = Your client secret username = your@email.com password = your_password [server] user_agent = MyTelldusApp/1.0
Run the script:
python oauth2.py
On success, the access token and refresh token are printed to the console.
Testing an API Call from the Command Line
Once you have an access token, you can test an API call directly with curl:
curl -X GET "https://pa-api.telldus.com/oauth2/devices/list?supportedMethods=65535&format=json" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "User-Agent: MyTelldusApp/1.0"
Replace YOUR_ACCESS_TOKEN with the token obtained from the login flow or the command-line script.
