Files
thijooree/docs/bmlapi/README.md
Shihaam Abdul Rahman 256f216da4
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 4s
update docs
2026-05-23 23:46:00 +05:00

199 lines
8.2 KiB
Markdown

# BML Internet Banking API Documentation
Reverse-engineered from traffic captures of the BML Mobile Banking Android app (`mv.com.bml.mib`).
[Play Store](https://play.google.com/store/apps/details?id=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 ({manufacturer}; Android {version}; {model})
```
---
## 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="{&quot;component&quot;:&quot;Login&quot;,&quot;props&quot;:{...}}">
```
To extract: find `data-page="..."`, unescape HTML entities (`&quot;``"`, `&amp;``&`, `&#39;``'`, `&lt;``<`, `&gt;``>`), 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 |
---
&nbsp;
---
> **Next →** [Login](01-login.md)