ubank
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 theauth_key
and long lifetoken
, 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
- Set
x-api-version
to fix #4 (thanks @jakepronger!)
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.