This guide walks you through installing the required library, building the signing helper, logging in to obtain a session token, keeping that token fresh, and making authenticated API calls — all in Python.
Installation
Install the requests library if you do not already have it:
All other modules used in this guide (hashlib, urllib.parse, json, threading) are part of the Python standard library.
The signing library (bpm_lib.py)
Create a file called bpm_lib.py with the following content. The rest of your code will import from this file.
import hashlib
import requests
import urllib.parse
import json
sign_key = '1234567890qwertyuiopasdfghjklzxc'
login_url = 'https://bpm.mioesim.com/api_order/login'
def creat_sign(value):
"""
Accepts a list of "key=value" strings (with "sign" already excluded),
sorts them alphabetically, concatenates them, appends the secret key,
URL-encodes the result, and returns the uppercase MD5 hex digest.
"""
value.sort()
MD5 = "".join(value) + sign_key
encoded_str = urllib.parse.quote(MD5, safe='')
sign_value = hashlib.md5(encoded_str.encode('utf-8')).hexdigest().upper()
return sign_value
def log_in(phonenumber, password):
"""
Calls POST /api_order/login and returns the session token on success,
or an empty string if authentication fails.
"""
token = ''
message = [
'password=%s' % password,
'phonenumber=%s' % phonenumber,
]
sign = creat_sign(message)
response = requests.get(
url=login_url,
params={
'password': password,
'phonenumber': phonenumber,
'sign': sign,
}
)
if response.status_code == 200:
value = json.loads(response.text)
if value['code'] == 1:
token = value['data']['token']
else:
print('log_in code error: %d' % value['code'])
else:
print('log_in server error: %d' % response.status_code)
return token
Here is what each part does:
sign_key — your secret key, used as a suffix before hashing. Keep this value private.
creat_sign(value) — takes a list of "key=value" strings, sorts them, concatenates them, appends the secret key, percent-encodes the full string with urllib.parse.quote, and returns the uppercase MD5 hex digest.
log_in(phonenumber, password) — builds the signed parameter list, calls the login endpoint, parses the JSON response, and returns the token on success.
Never include sign_key in client-side code, public repositories, or logs. Treat it like a password.
Logging in
Import bpm_lib and call log_in with your credentials:
import bpm_lib
phonenumber = 'YOUR_PHONE_NUMBER'
password = 'YOUR_PASSWORD'
token = bpm_lib.log_in(phonenumber, password)
if token:
print(f"Login successful, token: {token}")
else:
print("Login failed. Check your credentials.")
A successful login returns a non-empty token string. Pass this token to every subsequent API call.
Token refresh
Tokens are valid for 2 hours from the time of issue. The official demo schedules a refresh after 324,000 seconds — roughly 90 minutes — using threading.Timer, giving a 30-minute buffer before expiry.
import threading
import bpm_lib
token = ''
def refresh_token():
"""Re-authenticate and reschedule the next refresh."""
global token
token = bpm_lib.log_in('YOUR_PHONE_NUMBER', 'YOUR_PASSWORD')
if token:
print(f"Token refreshed: {token}")
schedule_refresh()
else:
print("Token refresh failed. Retrying in 60 seconds.")
threading.Timer(60, refresh_token).start()
def schedule_refresh():
# Refresh after 90 minutes (5400 seconds) to stay within the 2-hour window
threading.Timer(5400, refresh_token).start()
# Initial login
token = bpm_lib.log_in('YOUR_PHONE_NUMBER', 'YOUR_PASSWORD')
if token:
schedule_refresh()
Store token in a location accessible to all request functions, and always read the current value rather than caching it in a local variable across long-running operations.
The 324,000-second interval in the official demo script is intentional — it is approximately 90 minutes, not 2 hours. Using a value slightly shorter than the actual expiry prevents your token from becoming invalid mid-request.
Making authenticated API calls
After login, include both token and sign on every request. Build the parameter list the same way log_in does — format each parameter as "key=value", exclude sign, pass the list to creat_sign, then append sign to the request.
The example below calls GET /api_esim/getSkus to retrieve available eSIM packages:
import requests
import json
import bpm_lib
def get_skus(token):
params_list = [
'token=%s' % token,
]
sign = bpm_lib.creat_sign(params_list)
response = requests.get(
url='https://bpm.mioesim.com/api_esim/getSkus',
params={
'token': token,
'sign': sign,
}
)
if response.status_code == 200:
return json.loads(response.text)
else:
print('get_skus server error: %d' % response.status_code)
return None
skus = get_skus(token)
print(json.dumps(skus, indent=2))
Add any additional parameters to both params_list (for signing) and the params dict (for the request), keeping the two in sync.
Replace 'YOUR_PHONE_NUMBER' and 'YOUR_PASSWORD' with your actual MIOeSIM account credentials before running the code.