ubank icon indicating copy to clipboard operation
ubank copied to clipboard

Access ubank programatically.

ubank

Access ubank's API.

Contents

  • Contents
  • Getting started
  • Accessing ubank's API
  • API endpoints
  • Testing
  • Release
  • Changelog

Getting started

Install ubank with pip (Python 3.8+ is required):

$ pip install ubank

Before accessing the API, you'll first need to enrol a new device with ubank. Running ubank as a module helps with this task:

$ python -m ubank --help
usage: ubank.py [-h] [-o FILE] [-v] username

Enrols new device with ubank. You will be asked for your ubank password and secret code interactively.

positional arguments:
  username              ubank username

optional arguments:
  -h, --help            show this help message and exit
  -o FILE, --output FILE
                        write JSON device credentials to file (default: write to stdout)
  -v, --verbose         displays httpx INFO logs

We'll enrol a new device and save the credentials to device.json. Keep this file safe! You'll be prompted for your ubank password and security code during this step.

$  python -m ubank [email protected] --output device.json
Enter ubank password:
Enter security code sent to 04xxxxx789: 123456
$ cat device.json
{
  "hardware_id": "35bd47b0-eced-4fb4-88e1-24657c2500ec",
  "device_id": "cc1d3291-8e7d-45fc-845b-326b65bffcb1",
  "device_meta": "{\"appVersion\": \"15.11.1\", \"binaryVersion\": \"15.11.1\", \"deviceName\": \"iPhone19-1\", \"environment\": \"production\", \"instance\": \"live\", \"native\": true, \"platform\": \"ios\"}",
  "hashed_pin": "N0ZsiU81f+qiOZvs424E06AasHBlHsSlH9Fj1J0Sz5c=",
  "secret": "c3e59465-2449-4692-8d0a-6dc9bb8b2ae2",
  "auth_key": "pLzKjKs0FW104tqaj5qD3wYmZf0Q+udRCsRgST1gRGwh9iaxVf5qZdn+LtidvqSx20Y=",
  "email": "[email protected]",
  "mobile_number": "+61423456789",
  "user_id": "51457aec-9fb4-45c4-9ed0-4d17b70665ec",
  "username": "48b16c6f-19a5-46e7-855e-5d6922882276",
  "token": "dw2FYNdTRLgIS8YxZlQ0RnihkpgxRB/+a/o3vmQWWiRtrF11H4ZjA8ywZfaoUYK/Gkc="
}

Accessing ubank's API

You won't use your username and password to access ubank's API. Instead, you'll use the enrolled device's credentials (stored in device.json).

Instantiate a ubank.Device from device.json:

import json
import ubank

with open("device.json") as file:
    device = ubank.Device(**json.load(file))

Next, we'll instantiate ubank.Client with the device created above. Use this class as a context manager. This ensures ubank sessions and HTTP connections are properly cleaned when leaving the with block.

ubank.Client's base_url is set to https://api.ubank.com.au/, so only the API path is required when making requests.

[!IMPORTANT] You must store the instance's .device attribute after instantiation. Otherwise the stored device credentials will be expired and you'll need to re-enrol.

Instantiating ubank.Client refreshes the auth_key and long life token, held in the .device attribute.

with ubank.Client(device) as client:
    with open("device.json", "w") as file:
        file.write(client.device.dumps())
    print(client.get("/app/v1/accounts/summary").json())

{'linkedBanks': [{'bankId': 1, 'shortBankName': 'ubank', 'accounts': [{'label': 'Spend', 'type': 'TRANSACTION', 'balance': {'currency': 'AUD', 'current': 100, 'available': 100}, 'status': 'Active', 'id': '695db516-b0e2-4807-baca-77314a6257ce', 'nickname': 'Spend', 'number': '12345678', 'bsb': '670864', 'lastBalanceRefresh': '2024-01-02T00:00:00.000Z', 'openDate': '2024-01-01T00:00:00.000Z', 'isJointAccount': False}, {'label': 'Save', 'type': 'SAVINGS', 'balance': {'currency': 'AUD', 'current': 1200.44, 'available': 1200.44}, 'status': 'Active', 'id': '5bad6edf-247e-4221-9bfc-e7608f5984cb', 'nickname': 'Save', 'number': '23456789', 'bsb': '670864', 'lastBalanceRefresh': '2024-01-02T00:00:00.000Z', 'openDate': '2024-01-01T00:00:00.000Z', 'isJointAccount': False}]}]}

API endpoints

Here are some API endpoints to try:

with ubank.Client(device) as client:
    with open("device.json", "w") as file:
        file.write(client.device.dumps())
    print(client.get("/app/v1/accounts").json())
    print(client.get("/app/v1/accounts/summary").json())
    print(client.get("/app/v1/achievements").json())
    print(client.get("/app/v1/campaigns").json())
    print(client.get("/app/v1/cards").json())
    print(client.get("/app/v1/contacts").json())
    print(client.get("/app/v1/customer-details").json())
    print(client.get("/app/v1/insights").json())
    print(client.get("/app/v1/insights/interest").json())
    print(client.get("/app/v1/products").json())
    print(client.get("/app/v1/promotions").json())
    print(client.get("/app/v1/savings-goals").json())
    print(client.get("/app/v1/tfn").json())

Testing

Pull credentials from AWS:

aws-vault exec brodie@oasis -- python
Opening the SSO authorization page in your default browser (use Ctrl-C to abort)
https://device.sso.ap-southeast-2.amazonaws.com/?user_code=YMMV-CUMT
>>> import json
>>>
>>> import boto3
>>>
>>> import ubank
>>>
>>>
>>> def get_device(parameter_name="/portfolio/ubank-device"):
...     """Returns ubank enrolled device from AWS Parameter Store."""
...     return ubank.Device(
...         **json.loads(
...             # Retrieve JSON string from decrypted parameter value.
...             boto3.client("ssm", region_name="us-east-1").get_parameter(
...                 Name=parameter_name, WithDecryption=True
...             )["Parameter"]["Value"]
...         )
...     )
...
>>>
>>> def save_device(device, parameter_name="/portfolio/ubank-device"):
...     """Saves ubank device credentials to AWS Parameter Store."""
...     return boto3.client("ssm", region_name="us-east-1").put_parameter(
...         Name=parameter_name,
...         Value=device.dumps(),
...         Type="SecureString",
...         Overwrite=True,
...     )
...
>>>
>>> # Get ubank account balances and trusted cookie.
>>> device = get_device()
>>> with ubank.Client(device) as client:
...     # Update stored device credentials.
...     save_device(client.device)
...     for account in client.get("/app/v1/accounts/summary").json()["linkedBanks"][0][
...         "accounts"
...     ]:
...         print(account)
...
{'Version': 77, 'Tier': 'Standard', 'ResponseMetadata': {'RequestId': '1dd6cfff-dead-beef-asdf-123ead7e3ba0', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'Server', 'date': 'Thu, 1 Nov 1970 01:23:45 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '32', 'connection': 'keep-alive', 'x-amzn-requestid': '1dd6cfff-dead-beef-asdf-123ead7e3ba0'}, 'RetryAttempts': 0}}
{'label': 'Spend account', 'type': 'TRANSACTION', 'balance': {'currency': 'AUD', 'current': 1000.00, 'available': 1000.00}, 'status': 'Active', 'id': '9a293f00-c000-45b2-b21e-28cf09453f73', 'nickname': 'USpend', 'number': '00000000', 'bsb': '000000', 'lastBalanceRefresh': '1970-01-01T01:23:45.678Z', 'openDate': '1970-01-01T01:23:45.678Z', 'isJointAccount': False, 'depositProductData': {'interestTiers': [{'interestRate': 0, 'minimumRange': 0}]}}
{'label': 'Save account', 'type': 'SAVINGS', 'balance': {'currency': 'AUD', 'current': 1000000.00, 'available': 1000000.00}, 'status': 'Active', 'id': '88bcd861-21ad-48d9-8c3d-d789c5845252', 'nickname': 'USave', 'number': '00000000', 'bsb': '000000', 'lastBalanceRefresh': '1970-01-01T01:23:45.678Z', 'openDate': '1970-01-01T01:23:45.678Z', 'isJointAccount': False, 'depositProductData': {'interestTiers': [{'interestRate': 5.5, 'minimumRange': 0, 'maximumRange': 100000}, {'interestRate': 5, 'minimumRange': 100000.01, 'maximumRange': 250000}, {'interestRate': 0, 'minimumRange': 250000.01}], 'interestPaymentFrequency': {'interestPaymentCountPerPeriod': 1, 'interestPeriod': '1 Month', 'interestPaymentSchedule': 'End'}}}
>>>

Release

Bump project version. e.g.,

$ poetry version patch
Bumping version from 0.1.1 to 0.1.2

Publish to PyPI:

$ read -s PASSWORD
$ poetry publish --build -u __token__ -p "$PASSWORD"

Changelog

1.1.0

1.0.0

  • Drop Playwright requirement.
  • Re-implement with simpler and lightweight httpx libary.
  • Easier access to full ubank API.

0.1.2

  • Automate ubank access using Playwright headless browser.