# 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 ` 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 (; Android ; ) ``` 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 `
`: ```html
``` 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: | |------------------------------------------------>| | 302 Redirect → /web/login/2fa | |<------------------------------------------------| | | | GET /web/login/2fa | ← refreshes cookies |------------------------------------------------>| | Set-Cookie: XSRF-TOKEN= | |<------------------------------------------------| | | | POST /web/login/2fa | ← JSON: {code: , channel: "authenticator"} | X-XSRF-TOKEN: | |------------------------------------------------>| | 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= | |<------------------------------------------------| | | | 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 | |------------------------------------------------>| | {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)