All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 5s
197 lines
8.1 KiB
Markdown
197 lines
8.1 KiB
Markdown
# BML Internet Banking API Documentation
|
|
|
|
Reverse-engineered from traffic captures of the BML Mobile Banking Android app (`mv.com.bml.mib`).
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Bank of Maldives (BML) uses a hybrid authentication model: a web session flow (cookie-based, Inertia.js frontend) handles login and profile selection, which then feeds into a PKCE OAuth 2.0 exchange to obtain a Bearer token for the REST API.
|
|
|
|
The login process is stateful and must be executed in order:
|
|
1. Web login (credentials + TOTP)
|
|
2. Profile activation
|
|
3. PKCE OAuth token exchange
|
|
4. Authenticated REST API calls using the Bearer token
|
|
|
|
---
|
|
|
|
## Base URLs
|
|
|
|
| Purpose | Base URL |
|
|
|---|---|
|
|
| Web login / OAuth | `https://www.bankofmaldives.com.mv/internetbanking` |
|
|
| REST API (authenticated) | `https://www.bankofmaldives.com.mv/internetbanking/api/mobile` |
|
|
| Foreign limits API | `https://app.bankofmaldives.com.mv/api/v2` |
|
|
|
|
---
|
|
|
|
## Authentication Model
|
|
|
|
| Value | How obtained | How used |
|
|
|---|---|---|
|
|
| `XSRF-TOKEN` cookie | Set by server on `GET /web/login` | Sent as `X-XSRF-TOKEN` header on all web POST requests |
|
|
| `blaze_session` cookie | Set by server during web flow | Managed automatically by cookie jar |
|
|
| `blaze_identity` cookie | Set by server after profile activation | Managed automatically; identifies the active profile |
|
|
| `access_token` | Returned by `POST /oauth/token` after PKCE exchange | Sent as `Authorization: Bearer <token>` on all REST API calls |
|
|
| `refresh_token` | Returned alongside `access_token` | Used to obtain a new `access_token` without re-login |
|
|
|
|
The web flow uses a standard browser cookie jar. The REST API only needs the Bearer token — no cookies required after the OAuth exchange.
|
|
|
|
---
|
|
|
|
## OAuth 2.0 PKCE Parameters
|
|
|
|
| Parameter | Value |
|
|
|---|---|
|
|
| `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` |
|
|
| `redirect_uri` | `https://app.bankofmaldives.com.mv/oauth/mobile-callback` |
|
|
| `response_type` | `code` |
|
|
| `code_challenge_method` | `S256` |
|
|
|
|
The `code_verifier` is a cryptographically random 72-byte value, base64url-encoded (no padding). The `code_challenge` is the SHA-256 hash of the verifier, also base64url-encoded.
|
|
|
|
The `Device-ID` is a random 8-byte hex string generated once per login session.
|
|
|
|
---
|
|
|
|
## User-Agent Strategy
|
|
|
|
Two different User-Agent strings are used depending on the phase:
|
|
|
|
| Phase | User-Agent to use |
|
|
|---|---|
|
|
| Web login steps (GET/POST `/web/*`, `/oauth/authorize`) | Browser UA |
|
|
| OAuth token endpoint (`POST /oauth/token`) | Browser UA |
|
|
| All authenticated REST API calls (`/api/mobile/*`, `/api/v2/*`) | App UA |
|
|
|
|
**Browser UA** (used during the entire web session and OAuth flow):
|
|
```
|
|
Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0
|
|
```
|
|
|
|
**App UA** (used for all REST API calls after the token is obtained):
|
|
```
|
|
bml-mobile-banking/348 (<Build.MANUFACTURER>; Android <Build.VERSION.RELEASE>; <Build.MODEL>)
|
|
```
|
|
|
|
Example app UA:
|
|
```
|
|
bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)
|
|
```
|
|
|
|
---
|
|
|
|
## Inertia.js Response Format
|
|
|
|
The BML web frontend is built with Inertia.js. All web page responses embed their data as HTML-escaped JSON in the `data-page` attribute of the root `<div>`:
|
|
|
|
```html
|
|
<div id="app" data-page="{"component":"Login","props":{...}}">
|
|
```
|
|
|
|
To extract: find `data-page="..."`, unescape HTML entities (`"` → `"`, `&` → `&`, `'` → `'`, `<` → `<`, `>` → `>`), then parse as JSON. The useful data is inside the `props` key.
|
|
|
|
---
|
|
|
|
## Login Flow
|
|
|
|
```
|
|
Client Server
|
|
| |
|
|
| GET /web/login | ← seeds XSRF-TOKEN + blaze_session cookies
|
|
|------------------------------------------------>|
|
|
| Set-Cookie: XSRF-TOKEN=...; blaze_session=... |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| POST /web/login | ← JSON: {username, password, code:""}
|
|
| X-XSRF-TOKEN: <xsrf> |
|
|
|------------------------------------------------>|
|
|
| 302 Redirect → /web/login/2fa |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| GET /web/login/2fa | ← refreshes cookies
|
|
|------------------------------------------------>|
|
|
| Set-Cookie: XSRF-TOKEN=<new> |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| POST /web/login/2fa | ← JSON: {code: <TOTP>, channel: "authenticator"}
|
|
| X-XSRF-TOKEN: <xsrf2> |
|
|
|------------------------------------------------>|
|
|
| 302 Redirect → /web/profile |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| GET /web/profile | ← profile picker (multi) or auto-redirect (single)
|
|
|------------------------------------------------>|
|
|
| 200 (profile list) OR 302 (auto-activated) |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| (multi-profile) GET /web/profile/{profileId} | ← activate selected profile
|
|
|------------------------------------------------>|
|
|
| 302 → /web/redirect (personal) |
|
|
| 302 → /web/profile/2fa/business (business) |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| GET /oauth/authorize?...&code_challenge=... | ← PKCE authorize
|
|
|------------------------------------------------>|
|
|
| 302 → mobile-callback?code=<auth_code> |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| POST /oauth/token | ← exchange auth code for tokens
|
|
| {code, code_verifier, grant_type, client_id} |
|
|
|------------------------------------------------>|
|
|
| {access_token, refresh_token, expires_in} |
|
|
|<------------------------------------------------|
|
|
| |
|
|
| GET /api/mobile/dashboard | ← authenticated REST API
|
|
| Authorization: Bearer <access_token> |
|
|
|------------------------------------------------>|
|
|
| {success: true, payload: {dashboard: [...]}} |
|
|
|<------------------------------------------------|
|
|
```
|
|
|
|
---
|
|
|
|
## Session Expiry
|
|
|
|
The access token expires after `expires_in` seconds (typically 3600). On a `401` or `419` response from any REST endpoint:
|
|
|
|
1. Attempt to refresh using the stored `refresh_token` → [Token Refresh](03-oauth-token.md#token-refresh)
|
|
2. If refresh fails, re-run the full login flow
|
|
|
|
---
|
|
|
|
## Transfer Types
|
|
|
|
| Code | Description |
|
|
|---|---|
|
|
| `IAT` | Internal Account Transfer — BML account to BML account |
|
|
| `QTR` | Quick Transfer — transfer via PayMV alias |
|
|
| `DOT` | Domestic Outside Transfer — BML to another bank (e.g. MIB) |
|
|
|
|
---
|
|
|
|
## Documents
|
|
|
|
| # | File | Description |
|
|
|---|---|---|
|
|
| 1 | [Login](01-login.md) | Web login: credentials, TOTP, profile selection |
|
|
| 2 | [Business Profile OTP](02-business-otp.md) | SMS/email OTP for business profile activation |
|
|
| 3 | [OAuth Token](03-oauth-token.md) | PKCE token exchange and token refresh |
|
|
| 4 | [Dashboard](04-dashboard.md) | Fetch all accounts (CASA, card, loan) |
|
|
| 5 | [User Info](05-userinfo.md) | User profile details and session health check |
|
|
| 6 | [Account History](06-account-history.md) | Paginated transaction history for CASA accounts |
|
|
| 7 | [Card Statement](07-card-statement.md) | Card transaction history (prepaid, credit, debit) |
|
|
| 8 | [Transfer](08-transfer.md) | Initiate and confirm fund transfers |
|
|
| 9 | [Contacts](09-contacts.md) | Saved beneficiaries — list, save, delete |
|
|
| 10 | [Account Validation](10-validate.md) | Validate BML accounts, aliases, and MIB accounts |
|
|
| 11 | [Foreign Limits](11-foreign-limits.md) | USD foreign transaction limits by card and channel |
|
|
|
|
---
|
|
|
|
|
|
|
|
---
|
|
|
|
> **Next →** [Login](01-login.md)
|