> ## Documentation Index
> Fetch the complete documentation index at: https://developers.mioesim.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python integration guide for the MIOeSIM API

> Authenticate with MIOeSIM, compute HMAC-MD5 request signatures, and manage token refresh from Python using the requests library and built-in hashlib module.

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:

```bash theme={null}
pip install requests
```

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.

```python bpm_lib.py theme={null}
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.

<Warning>
  Never include `sign_key` in client-side code, public repositories, or logs. Treat it like a password.
</Warning>

## Logging in

Import `bpm_lib` and call `log_in` with your credentials:

```python main.py theme={null}
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.

```python token-refresh.py theme={null}
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.

<Note>
  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.
</Note>

## 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:

```python get-skus.py theme={null}
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.

<Tip>
  Replace `'YOUR_PHONE_NUMBER'` and `'YOUR_PASSWORD'` with your actual MIOeSIM account credentials before running the code.
</Tip>
