diff --git a/docs/bmlapi/01-login.md b/docs/bmlapi/01-login.md new file mode 100644 index 0000000..0c1fd26 --- /dev/null +++ b/docs/bmlapi/01-login.md @@ -0,0 +1,256 @@ +# Login + +Authenticate a user through the BML web session flow. This involves four sequential HTTP steps before OAuth can begin: seeding cookies, posting credentials, verifying TOTP, and selecting a profile. + +All requests in this flow use the **web User-Agent** and a shared cookie jar that persists cookies (`XSRF-TOKEN`, `blaze_session`, `blaze_identity`) across steps. + +--- + +## Step 1 — Seed Session Cookies + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/web/login +``` + +This request initialises the session. The server sets the `XSRF-TOKEN` and `blaze_session` cookies which must be carried through the entire login flow. + +### Request + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/login' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' +``` + +### Response + +`200 OK` — HTML page. The cookies set in the response headers are the only output needed: + +``` +Set-Cookie: XSRF-TOKEN=; Path=/; SameSite=Lax +Set-Cookie: blaze_session=; Path=/; HttpOnly; SameSite=Lax +``` + +Extract `XSRF-TOKEN` from the cookie store for use in Step 2. + +--- + +## Step 2 — Submit Credentials + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/web/login +``` + +### Request + +**Content-Type:** `application/json` + +```json +{ + "username": "A123456", + "password": "your_password", + "code": "" +} +``` + +| Field | Notes | +|---|---| +| `username` | BML online banking username (typically the national ID card number) | +| `password` | Online banking password | +| `code` | Always empty string `""` at this step | + +**Headers:** + +| Header | Value | +|---|---| +| `X-XSRF-TOKEN` | Value of the `XSRF-TOKEN` cookie from Step 1 | +| `Content-Type` | `application/json` | +| `User-Agent` | `Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0` | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/login' \ + --header 'Content-Type: application/json' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --header 'X-XSRF-TOKEN: ' \ + --cookie 'XSRF-TOKEN=; blaze_session=' \ + --data '{"username":"A123456","password":"your_password","code":""}' +``` + +### Response + +**Success:** `302` redirect to `/web/login/2fa`. The `blaze_session` cookie is updated. + +**Failure:** Any non-`302` response (typically `200` with the login page re-rendered) means the credentials were rejected. + +--- + +## Step 3 — Fetch 2FA Page + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/web/login/2fa +``` + +This request refreshes the `XSRF-TOKEN` cookie for the TOTP submission in Step 4. The response body is an HTML page that can be discarded. + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/login/2fa' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --cookie 'XSRF-TOKEN=; blaze_session=' +``` + +Extract the fresh `XSRF-TOKEN` from the updated cookie store before Step 4. + +--- + +## Step 4 — Submit TOTP + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/web/login/2fa +``` + +### Request + +**Content-Type:** `application/json` + +```json +{ + "code": "123456", + "channel": "authenticator" +} +``` + +| Field | Value | Notes | +|---|---|---| +| `code` | `123456` | 6-digit TOTP from the user's authenticator app | +| `channel` | `authenticator` | Always `authenticator` for TOTP | + +**Headers:** + +| Header | Value | +|---|---| +| `X-XSRF-TOKEN` | Fresh value from Step 3 | +| `Content-Type` | `application/json` | +| `User-Agent` | Web UA | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/login/2fa' \ + --header 'Content-Type: application/json' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --header 'X-XSRF-TOKEN: ' \ + --cookie 'XSRF-TOKEN=; blaze_session=' \ + --data '{"code":"123456","channel":"authenticator"}' +``` + +### Response + +**Success:** `302` redirect to `/web/profile`. + +**Failure:** Any non-`302` (typically a `200` with the 2FA page) means the TOTP was invalid. Generate a fresh code and retry. + +--- + +## TOTP Details + +BML uses standard RFC 6238 TOTP: + +| Parameter | Value | +|---|---| +| Algorithm | HMAC-SHA1 | +| Period | 30 seconds | +| Digits | 6 | +| Encoding | Base32 secret | + +--- + +## Step 5 — Profile Selection + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/web/profile +``` + +### Response — Multi-profile account + +`200 OK` with an HTML page containing an Inertia.js `data-page` payload. After [extracting the Inertia JSON](README.md#inertiajs-response-format): + +```json +{ + "props": { + "profiles": [ + { + "profile_id": "12345", + "name": "Mohamed Ali", + "type": "Profile", + "profile": { + "profile_type": "default" + } + }, + { + "profile_id": "67890", + "name": "My Company Ltd", + "type": "Business", + "profile": { + "profile_type": "business" + } + } + ] + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `profile_id` | `string` | ID used in Step 6 to activate this profile | +| `name` | `string` | Display name of the profile | +| `type` | `string` | `"Profile"` for personal, `"Business"` for business | +| `profile.profile_type` | `string` | `"default"` (personal) or `"business"` | + +### Response — Single-profile account + +`302` redirect (to `/web/redirect` or similar). The server has auto-activated the sole profile and set the `blaze_identity` cookie. Skip Step 6 and proceed directly to [OAuth Token Exchange](03-oauth-token.md). + +--- + +## Step 6 — Activate Profile + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/web/profile/{profile_id} +``` + +Replace `{profile_id}` with the value from the profile list. + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/profile/12345' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --cookie 'XSRF-TOKEN=; blaze_session=' +``` + +### Response — Personal profile (`profile_type: "default"`) + +`302` redirect (to `/web/redirect` or any path other than `/web/profile/2fa/business`), or `409 Conflict` if the profile was already active. + +Both outcomes mean success: the `blaze_identity` cookie is now set. Proceed to [OAuth Token Exchange](03-oauth-token.md). + +### Response — Business profile (`profile_type: "business"`) + +`302` redirect to `/web/profile/2fa/business`. The business profile requires an additional SMS/email OTP verification step. + +Proceed to [Business Profile OTP](02-business-otp.md). + +--- + +## Next Steps + +- **Personal profile activated** → proceed to **[OAuth Token Exchange](03-oauth-token.md)** +- **Business profile** → proceed to **[Business Profile OTP](02-business-otp.md)** + +--- + +  + +--- + +[← README](README.md)     **Next →** [Business Profile OTP](02-business-otp.md) diff --git a/docs/bmlapi/02-business-otp.md b/docs/bmlapi/02-business-otp.md new file mode 100644 index 0000000..7036cd8 --- /dev/null +++ b/docs/bmlapi/02-business-otp.md @@ -0,0 +1,164 @@ +# Business Profile OTP + +When activating a business profile (where `profile_type` is `"business"`), the server requires SMS or email OTP verification before issuing the `blaze_identity` cookie. This is a two-step process: request an OTP to a chosen channel, then submit the received code. + +--- + +## Prerequisites + +- Completed [Login Steps 1–5](01-login.md) (web session cookies are active) +- Received a `302` redirect to `/web/profile/2fa/business` after `GET /web/profile/{profile_id}` + +--- + +## Step 1 — Fetch Available OTP Channels + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/web/profile/2fa/business +``` + +Fetches the HTML page for the business 2FA screen. This also refreshes the `XSRF-TOKEN` cookie needed for the POST requests below. + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/profile/2fa/business' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --cookie 'XSRF-TOKEN=; blaze_session=; blaze_identity=' +``` + +### Response + +`200 OK` — HTML with an Inertia.js `data-page` payload. After [extracting the Inertia JSON](README.md#inertiajs-response-format): + +```json +{ + "props": { + "channels": [ + { + "channel": "sms", + "description": "SMS", + "masked": "+960 9XX XXXX" + }, + { + "channel": "email", + "description": "Email", + "masked": "m****@example.com" + } + ] + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `channel` | `string` | Channel identifier — use in Step 2 and Step 3 | +| `description` | `string` | Human-readable channel name | +| `masked` | `string` | Partially masked destination (for display to user) | + +Present the `masked` values to the user so they can choose where to receive their OTP. + +--- + +## Step 2 — Request OTP + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/web/profile/2fa/business +``` + +Send an empty `code` to trigger OTP dispatch to the chosen channel. + +### Request + +**Content-Type:** `application/json` + +```json +{ + "code": "", + "channel": "sms" +} +``` + +| Field | Value | Notes | +|---|---|---| +| `code` | `""` | Empty — signals OTP dispatch, not submission | +| `channel` | `"sms"` or `"email"` | Channel from Step 1 | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/profile/2fa/business' \ + --header 'Content-Type: application/json' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --header 'X-XSRF-TOKEN: ' \ + --cookie 'XSRF-TOKEN=; blaze_session=' \ + --data '{"code":"","channel":"sms"}' +``` + +### Response + +**Success:** `302` redirect — OTP has been sent. + +**Failure:** Any non-`302` — request failed; check session cookies and retry. + +--- + +## Step 3 — Submit OTP + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/web/profile/2fa/business +``` + +Before submitting, refresh the XSRF token by making a fresh `GET /web/profile/2fa/business` (repeat Step 1). This ensures the token is valid for the confirmation POST. + +### Request + +**Content-Type:** `application/json` + +```json +{ + "code": "123456", + "channel": "sms" +} +``` + +| Field | Value | Notes | +|---|---|---| +| `code` | `"123456"` | OTP received via SMS/email | +| `channel` | `"sms"` or `"email"` | Same channel used in Step 2 | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/web/profile/2fa/business' \ + --header 'Content-Type: application/json' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --header 'X-XSRF-TOKEN: ' \ + --cookie 'XSRF-TOKEN=; blaze_session=' \ + --data '{"code":"123456","channel":"sms"}' +``` + +### Response — Success + +`302` redirect to `/web/redirect` or `409 Conflict`. Both mean the OTP was accepted and the `blaze_identity` cookie is now set for the business profile. + +Proceed to [OAuth Token Exchange](03-oauth-token.md). + +### Response — Invalid OTP + +`302` redirect to any path other than `/web/redirect`. The OTP was wrong. Retry Step 3 with a new code (re-requesting is not usually needed unless the OTP expired). + +### Response — Other failure + +Non-`302` HTTP status — session has likely expired. Return to the full [login flow](01-login.md). + +--- + +## Next Steps + +After the `blaze_identity` cookie is set → proceed to **[OAuth Token Exchange](03-oauth-token.md)** + +--- + +  + +--- + +[← Login](01-login.md)     **Next →** [OAuth Token](03-oauth-token.md) diff --git a/docs/bmlapi/03-oauth-token.md b/docs/bmlapi/03-oauth-token.md new file mode 100644 index 0000000..4f2a3d2 --- /dev/null +++ b/docs/bmlapi/03-oauth-token.md @@ -0,0 +1,193 @@ +# OAuth Token Exchange and Refresh + +After the web login flow has set the `blaze_identity` cookie (profile activated), the client exchanges a PKCE authorization code for an `access_token` and `refresh_token`. These tokens are used for all subsequent REST API calls. + +--- + +## Prerequisites + +- Completed [Login](01-login.md) (and [Business OTP](02-business-otp.md) if applicable) +- `blaze_identity` cookie set in the session +- PKCE `code_verifier` and `code_challenge` generated at the start of the login session +- `Device-ID` generated at the start of the login session + +--- + +## PKCE Parameter Generation + +Generate these once at the start of each login session: + +| Parameter | Generation | +|---|---| +| `code_verifier` | 72 cryptographically random bytes, base64url-encoded (no padding) | +| `code_challenge` | SHA-256 hash of `code_verifier` (as ASCII bytes), base64url-encoded (no padding) | +| `Device-ID` | 8 cryptographically random bytes, hex-encoded (16 hex chars) | +| `state` | 16 random bytes, base64url-encoded | +| `nonce` | 12 random bytes, base64url-encoded | + +--- + +## Step 1 — Authorize (Get Auth Code) + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/oauth/authorize +``` + +### Query Parameters + +| Parameter | Value | +|---|---| +| `redirect_uri` | `https://app.bankofmaldives.com.mv/oauth/mobile-callback` | +| `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` | +| `response_type` | `code` | +| `state` | Random base64url string (16 bytes) | +| `nonce` | Random base64url string (12 bytes) | +| `code_challenge` | SHA-256 of `code_verifier`, base64url-encoded | +| `code_challenge_method` | `S256` | +| `Device-ID` | Random 16-char hex string | +| `User-Agent` | App user agent string | +| `x-app-version` | `2.1.44.348` | + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/oauth/authorize?redirect_uri=https%3A%2F%2Fapp.bankofmaldives.com.mv%2Foauth%2Fmobile-callback&client_id=98C83590-513F-4716-B02B-EC68B7D9E7E7&response_type=code&state=&nonce=&code_challenge=&code_challenge_method=S256&Device-ID=&User-Agent=bml-mobile-banking%2F348+%28Xiaomi%3B+Android+14%3B+22101320I%29&x-app-version=2.1.44.348' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --cookie 'blaze_session=; blaze_identity=' +``` + +### Response + +`302` redirect to: +``` +https://app.bankofmaldives.com.mv/oauth/mobile-callback?code=&state= +``` + +Extract the `code` query parameter from the `Location` header. This is the one-time authorization code. + +--- + +## Step 2 — Token Exchange + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/oauth/token +``` + +### Request + +**Content-Type:** `application/x-www-form-urlencoded` + +| Field | Value | +|---|---| +| `grant_type` | `authorization_code` | +| `code` | Auth code from Step 1 | +| `code_verifier` | PKCE verifier generated at login start | +| `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` | +| `redirect_uri` | `https://app.bankofmaldives.com.mv/oauth/mobile-callback` | +| `Device-ID` | Same device ID used in Step 1 | +| `User-Agent` | App user agent string | +| `x-app-version` | `2.1.44.348` | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/oauth/token' \ + --header 'User-Agent: Mozilla/5.0 (Android 14; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ + --data 'grant_type=authorization_code' \ + --data 'code=' \ + --data 'code_verifier=' \ + --data 'client_id=98C83590-513F-4716-B02B-EC68B7D9E7E7' \ + --data 'redirect_uri=https%3A%2F%2Fapp.bankofmaldives.com.mv%2Foauth%2Fmobile-callback' \ + --data 'Device-ID=' \ + --data 'User-Agent=bml-mobile-banking%2F348+%28Xiaomi%3B+Android+14%3B+22101320I%29' \ + --data 'x-app-version=2.1.44.348' +``` + +### Response + +```json +{ + "access_token": "eyJhbGciOiJSUzI1NiJ9...", + "refresh_token": "def50200aabbcc...", + "token_type": "Bearer", + "expires_in": 3600 +} +``` + +| Field | Type | Description | +|---|---|---| +| `access_token` | `string` | JWT Bearer token — use in `Authorization: Bearer ` on all REST API calls | +| `refresh_token` | `string` | Long-lived token — use to refresh without re-login | +| `token_type` | `string` | Always `"Bearer"` | +| `expires_in` | `number` | Token lifetime in seconds (typically `3600`) | + +Store `access_token`, `refresh_token`, `expires_in`, and `Device-ID` together as the session. The `access_token` expires at `now + expires_in * 1000` milliseconds. + +--- + +## Token Refresh + +When the access token expires (on `401` or `419` from any REST endpoint), obtain a new one using the refresh token. No web session or cookies are needed. + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/oauth/token +``` + +### Request + +**Content-Type:** `application/x-www-form-urlencoded` + +| Field | Value | +|---|---| +| `grant_type` | `refresh_token` | +| `refresh_token` | Stored refresh token | +| `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` | +| `Device-ID` | Same device ID from the original login | +| `User-Agent` | App user agent string | +| `x-app-version` | `2.1.44.348` | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/oauth/token' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --data 'grant_type=refresh_token' \ + --data 'refresh_token=def50200aabbcc...' \ + --data 'client_id=98C83590-513F-4716-B02B-EC68B7D9E7E7' \ + --data 'Device-ID=a1b2c3d4e5f60718' \ + --data 'User-Agent=bml-mobile-banking%2F348+%28Xiaomi%3B+Android+14%3B+22101320I%29' \ + --data 'x-app-version=2.1.44.348' +``` + +### Response + +Same structure as the token exchange response. The server may issue a new `refresh_token` (rotating tokens). If a new one is returned, replace the stored value; otherwise keep the original. + +```json +{ + "access_token": "eyJhbGciOiJSUzI1NiJ9...", + "refresh_token": "def50200...", + "token_type": "Bearer", + "expires_in": 3600 +} +``` + +**Failure:** If `access_token` is absent or blank in the response, the refresh token has expired. Re-run the full [login flow](01-login.md). + +--- + +## Session Storage + +Persist the following to represent a saved BML session: + +| Field | Description | +|---|---| +| `access_token` | Bearer token for REST API calls | +| `refresh_token` | Used to renew without re-login | +| `expires_at` | Unix timestamp (ms) when `access_token` expires | +| `device_id` | Must be sent with every refresh; ties tokens to the device | + +--- + +  + +--- + +[← Business Profile OTP](02-business-otp.md)     **Next →** [Dashboard](04-dashboard.md) diff --git a/docs/bmlapi/04-dashboard.md b/docs/bmlapi/04-dashboard.md new file mode 100644 index 0000000..40917fa --- /dev/null +++ b/docs/bmlapi/04-dashboard.md @@ -0,0 +1,228 @@ +# Dashboard + +Fetch all accounts for the active profile: CASA (savings/current), cards (prepaid/credit/debit), and loans. + +--- + +## Endpoint + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/dashboard +``` + +--- + +## Prerequisites + +- Valid `access_token` from [OAuth Token Exchange](03-oauth-token.md) + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `Authorization` | `Bearer ` | +| `User-Agent` | `bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)` | +| `x-app-version` | `2.1.44.348` | + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/dashboard' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +--- + +## Response + +```json +{ + "success": true, + "payload": { + "dashboard": [ + { + "id": "abc123def456", + "account": "7730000000001", + "account_type": "CASA", + "product": "Current Account", + "alias": "My Account", + "currency": "MVR", + "account_status": "Active", + "availableBalance": 1234.56, + "ledgerBalance": 1250.00, + "lockedAmount": 15.44 + }, + { + "id": "xyz789", + "account": "7730000000002", + "account_type": "CASA", + "product": "Savings Account", + "alias": "USD Savings", + "currency": "USD", + "account_status": "Active", + "availableBalance": 500.00, + "ledgerBalance": 500.00, + "lockedAmount": 0.0 + }, + { + "id": "card001", + "account": "4111111111111111", + "account_type": "Card", + "product": "Visa Debit", + "alias": "My Debit Card", + "currency": "MVR", + "account_status": "Active", + "prepaid_card": false, + "product_code": "VISA", + "account_visible": false, + "cardBalance": { + "AvailableLimit": 0.0, + "CurrentBalance": 0.0 + } + }, + { + "id": "card002", + "account": "4111111111112222", + "account_type": "Card", + "product": "Visa Prepaid", + "alias": "Prepaid", + "currency": "MVR", + "account_status": "Active", + "prepaid_card": true, + "product_code": "VISA", + "account_visible": true, + "cardBalance": { + "AvailableLimit": 200.00, + "CurrentBalance": 50.00 + } + }, + { + "id": "loan001", + "account": "9900000000001", + "account_type": "Loan", + "product": "Personal Finance", + "alias": "My Loan", + "currency": "MVR", + "account_status": "Active", + "availableBalance": -30000.0 + } + ] + } +} +``` + +--- + +## Response Fields + +### Top-level + +| Field | Type | Description | +|---|---|---| +| `success` | `bool` | `true` on success | +| `payload.dashboard` | `array` | List of account objects; order may vary | + +### Common Account Fields + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | Internal BML account ID — use this for history and loan detail calls | +| `account` | `string` | Account or card number (as displayed) | +| `account_type` | `string` | Account category — see table below | +| `product` | `string` | Product name (e.g. `"Current Account"`, `"Visa Prepaid"`) | +| `alias` | `string` | User-assigned label; may be blank | +| `currency` | `string` | ISO 4217 currency code (e.g. `"MVR"`, `"USD"`) | +| `account_status` | `string` | `"Active"` or other status strings | + +### Account Types + +| `account_type` | Description | +|---|---| +| `CASA` | Current Account or Savings Account | +| `Card` | Debit, credit, or prepaid card | +| `Loan` | Loan or financing account | + +--- + +## CASA Fields + +| Field | Type | Description | +|---|---|---| +| `availableBalance` | `number` | Available balance (funds available to use) | +| `ledgerBalance` | `number` | Book balance (may include pending transactions) | +| `lockedAmount` | `number` | Blocked/reserved amount | + +--- + +## Card Fields + +| Field | Type | Description | +|---|---|---| +| `prepaid_card` | `bool` | `true` for prepaid cards | +| `product_code` | `string` | Card scheme (e.g. `"VISA"`) | +| `account_visible` | `bool` | `true` for credit cards, `false` for debit cards | +| `cardBalance.AvailableLimit` | `number` | Available credit/prepaid balance | +| `cardBalance.CurrentBalance` | `number` | Current outstanding balance | + +### Card Profile Types + +| Condition | Type | +|---|---| +| `prepaid_card: true` | Prepaid card | +| `prepaid_card: false` AND `account_visible: true` | Credit card | +| `prepaid_card: false` AND `account_visible: false` | Debit card | + +### Product Code → Card Name + +The `product_code` field identifies the specific card product. Known mappings: + +| `product_code` | Card name | Asset | +|---|---|---| +| `C8201` | Mastercard Prepaid | `master_prepaid` | +| `C8205` | Mastercard Prepaid Travel | `master_prepaid_travel` | +| `C8005` | Mastercard Prepaid Travel (alt) | `master_prepaid_travel` | +| `C3007` | Amex Debit Green | `amex_debit_green` | +| `C1007` | Visa Debit | `visa_debit` | +| `C1003` | Visa Gold | `visa_gold` | +| `C8022` | Mastercard Gold | `master_gold` | +| `C1020` | Visa Debit Platinum | `visa_debit_platinum` | +| `C8902` | Mastercard Islamic | `master_islamic` | +| `C8101` | Mastercard Masveriyaa | `master_masveriyaa` | +| _(unknown)_ | Any other code | default card image | + +Additional product codes exist in the wild (Amex credit/gold/platinum, Mastercard Business, Odiveriyaa, Passport, World, Visa corporate/infinite/student etc.) but their codes have not been mapped yet. + +--- + +## Loan Fields + +| Field | Type | Description | +|---|---|---| +| `availableBalance` | `number` | Outstanding balance — returned as a negative number; use `abs()` for display | + +For full loan details (interest rate, repayment schedule, overdue info), call [GET /api/mobile/account/{id}](05-userinfo.md#loan-detail). + +--- + +## Error Responses + +### Expired session + +HTTP `401` or `419` — the access token has expired. Attempt [token refresh](03-oauth-token.md#token-refresh). + +### Server error + +HTTP `5xx` — BML server-side error. Retry after a delay. + +--- + +  + +--- + +[← OAuth Token](03-oauth-token.md)     **Next →** [User Info](05-userinfo.md) diff --git a/docs/bmlapi/05-userinfo.md b/docs/bmlapi/05-userinfo.md new file mode 100644 index 0000000..67bfd2f --- /dev/null +++ b/docs/bmlapi/05-userinfo.md @@ -0,0 +1,141 @@ +# User Info and Session Health + +Two endpoints for checking the current session and fetching the authenticated user's personal details. + +--- + +## Profile Health Check + +A lightweight call to verify that the access token is still valid. Returns HTTP `200` on success, `401`/`419` if expired. + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/profile +``` + +### Request + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/profile' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +Use this to probe the session before making more expensive calls. On `401` or `419`, proceed to [token refresh](03-oauth-token.md#token-refresh). + +--- + +## User Info + +Fetch personal details for the currently authenticated user. + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/userinfo +``` + +### Request + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/userinfo' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +### Response + +```json +{ + "success": true, + "payload": { + "user": { + "fullname": "MOHAMED ALI", + "email": "user@example.com", + "mobile_phone": "9600000001", + "customer_number": "C0000001", + "idcard": "A123456", + "birthdate": "1990-01-01" + } + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `fullname` | `string` | Full name (typically uppercase) | +| `email` | `string` | Registered email address | +| `mobile_phone` | `string` | Registered mobile number | +| `customer_number` | `string` | BML internal customer ID (e.g. `C0000001`) | +| `idcard` | `string` | National ID card number | +| `birthdate` | `string` | Date of birth (`YYYY-MM-DD`) | + +### Failure + +```json +{ "success": false } +``` + +Returns `null` payload if the account has no user info record, or `success: false` on any error. + +--- + +## Loan Detail + +Fetch extended details for a loan account. The `{id}` is the internal account ID (`id` field) from the [dashboard](04-dashboard.md) response. + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/account/{id} +``` + +### Request + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/account/loan001' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +### Response + +```json +{ + "success": true, + "payload": { + "loanAmount": 50000.00, + "outstandingAmt": -30000.00, + "repayAmount": 1500.00, + "intRate": 8.5, + "loanStatus": "Active", + "startDate": "2023-10-26T00:00:00+05:00", + "endDate": "2026-10-26T00:00:00+05:00", + "noOfRepayOverdue": 0, + "overdueAmount": 0.00 + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `loanAmount` | `number` | Original loan principal | +| `outstandingAmt` | `number` | Outstanding balance — returned as a **negative number**; use `abs()` for display | +| `repayAmount` | `number` | Periodic repayment amount | +| `intRate` | `number` | Interest rate (percentage) | +| `loanStatus` | `string` | Status string (e.g. `"Active"`) | +| `startDate` | `string` | Loan start date (ISO 8601) | +| `endDate` | `string` | Loan end date (ISO 8601) | +| `noOfRepayOverdue` | `number` | Number of overdue repayments | +| `overdueAmount` | `number` | Total overdue amount | + +On `401`/`419` the session has expired — attempt [token refresh](03-oauth-token.md#token-refresh). + +--- + +  + +--- + +[← Dashboard](04-dashboard.md)     **Next →** [Account History](06-account-history.md) diff --git a/docs/bmlapi/06-account-history.md b/docs/bmlapi/06-account-history.md new file mode 100644 index 0000000..28c92c4 --- /dev/null +++ b/docs/bmlapi/06-account-history.md @@ -0,0 +1,194 @@ +# Account Transaction History + +Fetch paginated transaction history for a CASA (savings/current) account. + +--- + +## Endpoint + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/account/{accountId}/history/{page} +``` + +| Path parameter | Description | +|---|---| +| `accountId` | Internal account ID (`id` field from [dashboard](04-dashboard.md)) | +| `page` | Page number, 1-based | + +--- + +## Prerequisites + +- Valid `access_token` from [OAuth Token Exchange](03-oauth-token.md) +- Internal account `id` from the [Dashboard](04-dashboard.md) response (not the account number) + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `Authorization` | `Bearer ` | +| `User-Agent` | `bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)` | +| `x-app-version` | `2.1.44.348` | + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/account/abc123def456/history/1' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +--- + +## Pagination + +The API uses 1-based page numbering. The response includes `totalPages` — increment the page number until you reach or exceed it. + +| Page | URL | +|---|---| +| First | `/api/mobile/account/{id}/history/1` | +| Second | `/api/mobile/account/{id}/history/2` | +| N-th | `/api/mobile/account/{id}/history/N` | + +Stop when the current page number exceeds `totalPages`, or when the `history` array is empty. + +--- + +## Response + +```json +{ + "success": true, + "payload": { + "totalPages": 5, + "history": [ + { + "id": "TXN001", + "bookingDate": "2026-05-16", + "description": "Transfer Debit", + "narrative1": "16-05-2026 15-10-25", + "narrative2": "Mohamed Ali", + "amount": -500.00, + "currency": "MVR", + "reference": "FT20260516123456" + }, + { + "id": "TXN002", + "bookingDate": "2026-05-15", + "description": "Transfer Credit", + "narrative1": "15-05-2026 10-30-00", + "narrative2": "Ahmed Hassan", + "amount": 1000.00, + "currency": "MVR", + "reference": "FT20260515103000" + }, + { + "id": "TXN003", + "bookingDate": "2026-05-14", + "description": "Purchase", + "narrative1": "14-05-2026 041500", + "narrative2": "", + "amount": -75.00, + "currency": "MVR", + "reference": "" + } + ] + } +} +``` + +--- + +## Response Fields + +### Top-level + +| Field | Type | Description | +|---|---|---| +| `success` | `bool` | `true` on success | +| `payload.totalPages` | `number` | Total number of pages | +| `payload.history` | `array` | List of transactions for this page | + +### Transaction Object + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | Transaction ID | +| `bookingDate` | `string` | Booking date (fallback date — prefer parsed `narrative1` where available) | +| `description` | `string` | Transaction type — see table below | +| `narrative1` | `string` | Encodes the precise timestamp; format depends on `description` | +| `narrative2` | `string` | Counterparty name (for transfers); may be blank | +| `amount` | `number` | Amount — **negative = debit, positive = credit** | +| `currency` | `string` | ISO 4217 currency code | +| `reference` | `string` | Transfer reference number; blank for non-transfer entries | + +--- + +## Transaction Descriptions + +| `description` | Meaning | +|---|---| +| `Transfer Debit` | Outgoing transfer | +| `Transfer Credit` | Incoming transfer | +| `Purchase` | Card purchase or point-of-sale transaction | +| Other | Various bank-generated transaction types | + +--- + +## Date Parsing from `narrative1` + +The `bookingDate` field is date-only. For precise timestamps, parse `narrative1`: + +### Transfer Debit / Transfer Credit + +Format: `DD-MM-YYYY HH-mm-ss` + +``` +"16-05-2026 15-10-25" → 2026-05-16 15:10:25 +``` + +Parse with `SimpleDateFormat("dd-MM-yyyy HH-mm-ss")`. + +### Purchase + +Format: `DD-MM-YYYY HHmmSS` (time is first 4 digits of the numeric suffix) + +``` +"14-05-2026 041500" → date: 14-05-2026, time part: "0415" → 04:15 +→ 2026-05-14 04:15:00 +``` + +Parse: split on space → date part `DD-MM-YYYY`, time part take first 4 chars → `HH:mm`. Combine and parse with `SimpleDateFormat("dd-MM-yyyy HH:mm:ss")`. + +### All other descriptions + +Fall back to `bookingDate` as-is. + +--- + +## Amount Sign Convention + +| Sign | Meaning | +|---|---| +| Positive (`+`) | Credit — money received | +| Negative (`-`) | Debit — money spent | + +--- + +## Error Responses + +| HTTP Code | Meaning | +|---|---| +| `401` / `419` | Access token expired — attempt [token refresh](03-oauth-token.md#token-refresh) | + +--- + +  + +--- + +[← User Info](05-userinfo.md)     **Next →** [Card Statement](07-card-statement.md) diff --git a/docs/bmlapi/07-card-statement.md b/docs/bmlapi/07-card-statement.md new file mode 100644 index 0000000..37aa63e --- /dev/null +++ b/docs/bmlapi/07-card-statement.md @@ -0,0 +1,164 @@ +# Card Statement + +Fetch transaction history for a card account (prepaid, credit, or debit). Returns three sets of entries: outstanding authorisations, unbilled transactions, and billed statement entries. + +--- + +## Endpoint + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/card/statement +``` + +--- + +## Prerequisites + +- Valid `access_token` from [OAuth Token Exchange](03-oauth-token.md) +- Internal card ID (`id` field from [dashboard](04-dashboard.md)) and a target month + +--- + +## Request + +**Content-Type:** `application/json` + +### Body + +```json +{ + "card": "card001", + "month": "2026-05" +} +``` + +| Field | Description | +|---|---| +| `card` | Internal card ID — the `id` field from the dashboard response (not the card number) | +| `month` | Target month in `YYYY-MM` format | + +### Headers + +| Header | Value | +|---|---| +| `Authorization` | `Bearer ` | +| `User-Agent` | `bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)` | +| `x-app-version` | `2.1.44.348` | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/card/statement' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'Content-Type: application/json' \ + --data '{"card":"card001","month":"2026-05"}' +``` + +--- + +## Response + +The payload contains up to three distinct sections, each may be absent or `null`: + +```json +{ + "success": true, + "payload": { + "outstanding": { + "CardOutStdAuthDetails": [ + { + "TranApprCode": "123456", + "DateTime": "2026-05-16T15:10:25", + "TranDesc": "Online Purchase - Amazon", + "BillingAmount": 50.00, + "BillingCcy": "USD" + } + ] + }, + "unbilled": { + "CardUnbillTxnDetails": [ + { + "TranApprCode": "789012", + "DateTime": "2026-05-15T10:30:00", + "TranDesc": "Supermarket", + "BillingAmount": 120.00, + "BillingCcy": "MVR" + } + ] + }, + "cardstatement": [ + { + "TranRef": "STMT20260501001", + "TransDate": "2026-05-01", + "TranDate": "2026-05-01", + "TranDesc": "Monthly Fee", + "Description": "Monthly Fee", + "TranAmount": 25.00, + "TranCcy": "MVR" + } + ] + } +} +``` + +--- + +## Response Sections + +### `outstanding.CardOutStdAuthDetails` — Pending Authorisations + +Transactions that have been authorised but not yet posted. Amounts are in the billing currency. + +| Field | Type | Description | +|---|---|---| +| `TranApprCode` | `string` | Authorisation approval code | +| `DateTime` | `string` | Authorisation timestamp | +| `TranDesc` | `string` | Merchant or transaction description | +| `BillingAmount` | `number` | Amount in billing currency (positive) | +| `BillingCcy` | `string` | Billing currency code | + +### `unbilled.CardUnbillTxnDetails` — Unbilled Transactions + +Transactions posted to the card but not yet included in a statement cycle. + +Same field structure as `CardOutStdAuthDetails`. + +### `cardstatement` — Billed Statement Entries + +Previously billed transactions from the statement cycle for the requested month. + +| Field | Type | Description | +|---|---|---| +| `TranRef` | `string` | Statement reference | +| `TransDate` / `TranDate` | `string` | Transaction date (check both keys; `TransDate` takes priority) | +| `TranDesc` / `Description` | `string` | Description (check both keys; `TranDesc` takes priority) | +| `TranAmount` | `number` | Amount — **stored as positive, displayed as debit** (negate for sign convention) | +| `TranCcy` | `string` | Transaction currency | + +> The `TranAmount` in `cardstatement` is always positive in the API response. Negate it to `−TranAmount` so it follows the standard debit-negative convention. + +--- + +## Amount Sign Convention + +| Section | Sign in response | Meaning | +|---|---|---| +| `outstanding` / `unbilled` | Positive | Debit (charge to card) | +| `cardstatement` | Positive (negate on display) | Debit (charge to card) | + +--- + +## Error Responses + +| HTTP Code | Meaning | +|---|---| +| `401` / `419` | Access token expired — attempt [token refresh](03-oauth-token.md#token-refresh) | + +--- + +  + +--- + +[← Account History](06-account-history.md)     **Next →** [Transfer](08-transfer.md) diff --git a/docs/bmlapi/08-transfer.md b/docs/bmlapi/08-transfer.md new file mode 100644 index 0000000..02d36bc --- /dev/null +++ b/docs/bmlapi/08-transfer.md @@ -0,0 +1,235 @@ +# Transfer + +Initiate and confirm a fund transfer. The process is two-step: an initial POST triggers an OTP to the user's chosen channel; a second POST with the OTP confirms and completes the transfer. + +The same endpoint (`POST /api/mobile/transfer`) handles both steps — the presence of an `otp` field distinguishes them. + +--- + +## Prerequisites + +- Valid `access_token` from [OAuth Token Exchange](03-oauth-token.md) +- Validated destination account from [Account Validation](10-validate.md) + +--- + +## Transfer Channels + +Before initiating a transfer, fetch the OTP channels available to the user. + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer +``` + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +### Response + +```json +{ + "success": true, + "payload": { + "transfer": { + "otpChannel": [ + { + "channel": "token", + "description": "Authenticator App", + "masked": "" + }, + { + "channel": "sms", + "description": "SMS", + "masked": "+960 9XX XXXX" + } + ] + } + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `channel` | `string` | Channel identifier — use in transfer requests | +| `description` | `string` | Human-readable channel name | +| `masked` | `string` | Partially masked destination (blank for authenticator) | + +--- + +## Transfer Types + +| `transfertype` | Description | Requires `bank` | +|---|---|---| +| `IAT` | Internal — BML account to BML account | No | +| `QTR` | Quick Transfer — via PayMV alias | No | +| `DOT` | Domestic Outside Transfer — BML to another bank (e.g. MIB) | Yes — BIC of the destination bank | + +--- + +## Step 1 — Initiate Transfer (Trigger OTP) + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer +``` + +### Request Body + +**Content-Type:** `application/json` + +```json +{ + "debitAccount": "7730000000001", + "creditAccount": "7730000000002", + "debitAmount": 100.00, + "transfertype": "IAT", + "currency": "MVR", + "channel": "token" +} +``` + +For `DOT` (outside bank) transfers, add `"bank"`: + +```json +{ + "debitAccount": "7730000000001", + "creditAccount": "90101000000001000", + "debitAmount": 250.00, + "transfertype": "DOT", + "currency": "MVR", + "channel": "sms", + "bank": "MIBVMVMV" +} +``` + +| Field | Type | Description | +|---|---|---| +| `debitAccount` | `string` | Source BML account number | +| `creditAccount` | `string` | Destination account number | +| `debitAmount` | `number` | Amount to transfer | +| `transfertype` | `string` | `IAT`, `QTR`, or `DOT` | +| `currency` | `string` | Currency code (e.g. `"MVR"`, `"USD"`) | +| `channel` | `string` | OTP channel from the channels list (e.g. `"token"`, `"sms"`) | +| `bank` | `string` | BIC of the destination bank — required for `DOT` only | + +### Headers + +| Header | Value | +|---|---| +| `Authorization` | `Bearer ` | +| `User-Agent` | `bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)` | +| `x-app-version` | `2.1.44.348` | +| `accept` | `application/json` | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'accept: application/json' \ + --header 'Content-Type: application/json' \ + --data '{"debitAccount":"7730000000001","creditAccount":"7730000000002","debitAmount":100.00,"transfertype":"IAT","currency":"MVR","channel":"token"}' +``` + +### Response — OTP Triggered + +```json +{ + "success": true, + "code": 22, + "message": "OTP sent to your authenticator app" +} +``` + +`success: true` AND `code: 22` together confirm that the OTP has been dispatched. Proceed to Step 2. + +Any other combination means the request failed. + +--- + +## Step 2 — Confirm Transfer (Submit OTP) + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer +``` + +Repeat the exact same body as Step 1, adding the `otp` field (and optionally `remarks`). + +### Request Body + +```json +{ + "debitAccount": "7730000000001", + "creditAccount": "7730000000002", + "debitAmount": 100.00, + "transfertype": "IAT", + "currency": "MVR", + "channel": "token", + "otp": "123456", + "remarks": "Rent payment" +} +``` + +| Additional field | Type | Description | +|---|---|---| +| `otp` | `string` | OTP received via the chosen channel | +| `remarks` | `string` | Optional transfer reference/memo (omit if blank) | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'accept: application/json' \ + --header 'Content-Type: application/json' \ + --data '{"debitAccount":"7730000000001","creditAccount":"7730000000002","debitAmount":100.00,"transfertype":"IAT","currency":"MVR","channel":"token","otp":"123456","remarks":"Rent payment"}' +``` + +--- + +## Responses + +### Success + +```json +{ + "success": true, + "message": "Transfer successful", + "payload": { + "reference": "FT202605160001", + "timestamp": "2026-05-16 15:10:25" + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `success` | `bool` | `true` | +| `message` | `string` | Confirmation message | +| `payload.reference` | `string` | Transfer reference number | +| `payload.timestamp` | `string` | Completion timestamp | + +### Failure + +```json +{ + "success": false, + "message": "Invalid OTP. Please try again." +} +``` + +`success: false` — the `message` field contains the reason. Common causes: wrong OTP, insufficient balance, invalid account. + +--- + +  + +--- + +[← Card Statement](07-card-statement.md)     **Next →** [Contacts](09-contacts.md) diff --git a/docs/bmlapi/09-contacts.md b/docs/bmlapi/09-contacts.md new file mode 100644 index 0000000..14d6f2b --- /dev/null +++ b/docs/bmlapi/09-contacts.md @@ -0,0 +1,157 @@ +# Contacts (Saved Beneficiaries) + +Manage the user's saved beneficiary list: list all contacts, save a new one, and delete an existing one. + +--- + +## List Contacts + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts +``` + +### Request + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' +``` + +### Response + +```json +{ + "success": true, + "payload": [ + { + "id": 1, + "account": "7730000000001", + "name": "Mohamed Ali", + "alias": "Ali", + "status": "S", + "currency": "MVR" + }, + { + "id": 2, + "account": "90101000000001000", + "name": "Ahmed Hassan", + "alias": "Hassan", + "status": "S", + "currency": "MVR" + } + ] +} +``` + +| Field | Type | Description | +|---|---|---| +| `id` | `number` | Internal contact ID — use for delete | +| `account` | `string` | Beneficiary account number | +| `name` | `string` | Account holder name | +| `alias` | `string` | User-assigned nickname; falls back to `name` if blank | +| `status` | `string` | Contact status (typically `"S"` for saved) | +| `currency` | `string` | Transfer currency for this contact | + +Entries where `account` is blank are skipped. + +--- + +## Save Contact + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts +``` + +### Request + +**Content-Type:** `application/json` + +```json +{ + "contact_type": "BML", + "account": "7730000000001", + "alias": "Ali", + "currency": "MVR", + "name": "Mohamed Ali" +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `contact_type` | `string` | Yes | Contact category (e.g. `"BML"`) | +| `account` | `string` | Yes | Beneficiary account number | +| `alias` | `string` | Yes | Display nickname | +| `currency` | `string` | No | Transfer currency | +| `name` | `string` | No | Full name of the beneficiary | +| `swift` | `string` | No | SWIFT/BIC code (for international contacts) | + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'Accept: application/json' \ + --header 'Content-Type: application/json' \ + --data '{"contact_type":"BML","account":"7730000000001","alias":"Ali","currency":"MVR","name":"Mohamed Ali"}' +``` + +### Response + +```json +{ "success": true } +``` + +`success: true` confirms the contact was saved. `success: false` on failure. + +--- + +## Delete Contact + +``` +POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts/{contactId} +``` + +BML does not support `DELETE` directly — the delete is sent as a POST with a `_method: delete` body override. + +| Path parameter | Description | +|---|---| +| `contactId` | The `id` from the contacts list | + +### Request + +**Content-Type:** `application/json` + +```json +{ "_method": "delete" } +``` + +```bash +curl --request POST \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts/1' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'accept: application/json' \ + --header 'Content-Type: application/json' \ + --data '{"_method":"delete"}' +``` + +### Response + +```json +{ "success": true } +``` + +`success: true` confirms the contact was deleted. + +--- + +  + +--- + +[← Transfer](08-transfer.md)     **Next →** [Account Validation](10-validate.md) diff --git a/docs/bmlapi/10-validate.md b/docs/bmlapi/10-validate.md new file mode 100644 index 0000000..9cd5820 --- /dev/null +++ b/docs/bmlapi/10-validate.md @@ -0,0 +1,175 @@ +# Account Validation + +Resolve and validate a destination account before initiating a transfer. Two endpoints cover different account types: + +- **BML / alias accounts** — `GET /api/mobile/validate/account/{input}` — resolves BML account numbers and PayMV aliases +- **MIB accounts** — `GET /api/mobile/favara/account-verification/{account}/MIB` — resolves MIB accounts via BML's Favara interbank network + +--- + +## Validate BML Account or Alias + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/validate/account/{input} +``` + +`{input}` is either a BML account number or a PayMV alias string (e.g. `"MALI"` or a phone number used as alias). + +### Request + +```bash +# Validate a BML account number +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/validate/account/7730000000001' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'Accept: application/json' + +# Validate an alias +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/validate/account/MALI' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'Accept: application/json' +``` + +--- + +## Responses + +The `validationType` in the response determines which transfer type to use. + +### BML Account (`validationType: "BML"`) — use `IAT` + +```json +{ + "success": true, + "payload": { + "trnType": "IAT", + "validationType": "BML", + "account": "7730000000001", + "name": "Mohamed Ali", + "alias": "MALI", + "currency": "MVR" + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `trnType` | `string` | Transfer type to use — `"IAT"` | +| `validationType` | `string` | `"BML"` | +| `account` | `string` | Resolved BML account number | +| `name` | `string` | Account holder name | +| `alias` | `string` | Alias (if any) — blank or `"null"` if none | +| `currency` | `string` | Account currency | + +--- + +### PayMV Alias (`validationType: "alias"`) — use `QTR` + +Returned when the input resolves to a PayMV alias (interbank quick transfer). + +```json +{ + "success": true, + "payload": { + "trnType": "QTR", + "validationType": "alias", + "CdtrAcct": { + "Acct": "7730000000001", + "FinInstnId": "" + }, + "contact_name": "Mohamed Ali", + "currency": "MVR" + } +} +``` + +| Field | Type | Description | +|---|---|---| +| `trnType` | `string` | Transfer type to use — `"QTR"` | +| `validationType` | `string` | `"alias"` | +| `CdtrAcct.Acct` | `string` | Resolved account number | +| `CdtrAcct.FinInstnId` | `string` | BIC of the destination institution (blank for BML-to-BML) | +| `contact_name` | `string` | Account holder name | +| `currency` | `string` | Account currency | + +> For `QTR` transfers, pass the **original alias input** as `creditAccount` (not the resolved account number), as the alias is what the server routes on. + +--- + +### Failure + +```json +{ "success": false } +``` + +Account or alias not found, or not eligible for transfer. + +--- + +## Verify MIB Account (Favara) + +``` +GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/favara/account-verification/{account}/MIB +``` + +Verifies a Maldives Islamic Bank (MIB) account number via BML's Favara interbank network. Use the result for a `DOT` transfer. + +### Request + +```bash +curl --request GET \ + --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/favara/account-verification/90101000000001000/MIB' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'Accept: application/json' +``` + +### Response + +```json +{ + "success": true, + "account": "90101000000001000", + "name": "Mohamed Ali", + "agnt": "MIBVMVMV" +} +``` + +| Field | Type | Description | +|---|---|---| +| `success` | `bool` | `true` if the account exists and is reachable | +| `account` | `string` | MIB account number | +| `name` | `string` | Account holder name | +| `agnt` | `string` | BIC of MIB — send as the `bank` field in the [transfer](08-transfer.md) request | + +### Failure + +```json +{ "success": false } +``` + +Account not found or not accessible via Favara. + +--- + +## Transfer Type Summary + +| `validationType` / source | `trnType` to use | `bank` required | +|---|---|---| +| `"BML"` | `IAT` | No | +| `"alias"` | `QTR` | No | +| MIB (Favara) | `DOT` | Yes — `agnt` value from verification response | + +--- + +  + +--- + +[← Contacts](09-contacts.md)     **Next →** [Foreign Limits](11-foreign-limits.md) diff --git a/docs/bmlapi/11-foreign-limits.md b/docs/bmlapi/11-foreign-limits.md new file mode 100644 index 0000000..abe3b3f --- /dev/null +++ b/docs/bmlapi/11-foreign-limits.md @@ -0,0 +1,147 @@ +# Foreign Transaction Limits + +Fetch the user's USD foreign currency transaction limits per card, broken down by channel (ATM, ECOM, POS) and category (general, medical). + +This endpoint uses a **different base URL** from the main REST API. + +--- + +## Endpoint + +``` +GET https://app.bankofmaldives.com.mv/api/v2/foreign-limits +``` + +--- + +## Prerequisites + +- Valid `access_token` from [OAuth Token Exchange](03-oauth-token.md) + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `Authorization` | `Bearer ` | +| `User-Agent` | `bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)` | +| `x-app-version` | `2.1.44.348` | +| `Accept` | `application/json` | + +```bash +curl --request GET \ + --url 'https://app.bankofmaldives.com.mv/api/v2/foreign-limits' \ + --header 'Authorization: Bearer ' \ + --header 'User-Agent: bml-mobile-banking/348 (Xiaomi; Android 14; 22101320I)' \ + --header 'x-app-version: 2.1.44.348' \ + --header 'Accept: application/json' +``` + +--- + +## Response + +```json +{ + "success": true, + "payload": [ + { + "type": "Debit", + "used": 150.00, + "totalLimit": 1000.00, + "generalCap": 1000.00, + "generalRemaining": 850.00, + "medicalRemaining": 500.00, + "isAtmEnabled": true, + "isPosEnabled": true, + "usageByCategory": { + "ATM": { + "remaining": 350.00, + "limit": 500.00 + }, + "ECOM": { + "remaining": 200.00, + "limit": 300.00 + }, + "POS": { + "remaining": 300.00, + "limit": 500.00 + } + } + }, + { + "type": "Credit", + "used": 0.00, + "totalLimit": 2000.00, + "generalCap": 2000.00, + "generalRemaining": 2000.00, + "medicalRemaining": 1000.00, + "isAtmEnabled": true, + "isPosEnabled": true, + "usageByCategory": { + "ATM": { "remaining": 500.00, "limit": 500.00 }, + "ECOM": { "remaining": 500.00, "limit": 500.00 }, + "POS": { "remaining": 1000.00, "limit": 1000.00 } + } + } + ] +} +``` + +--- + +## Response Fields + +### Top-level + +| Field | Type | Description | +|---|---|---| +| `success` | `bool` | `true` on success | +| `payload` | `array` | One entry per card type (e.g. Debit, Credit, Prepaid) | + +### Limit Object + +| Field | Type | Description | +|---|---|---| +| `type` | `string` | Card type — e.g. `"Debit"`, `"Credit"`, `"Prepaid"` | +| `used` | `number` | Total amount used this period (USD) | +| `totalLimit` | `number` | Overall foreign transaction limit (USD) | +| `generalCap` | `number` | General spending cap (USD) | +| `generalRemaining` | `number` | Remaining general limit (USD) | +| `medicalRemaining` | `number` | Remaining medical category limit (USD) | +| `isAtmEnabled` | `bool` | Whether ATM withdrawals are enabled | +| `isPosEnabled` | `bool` | Whether POS payments are enabled | + +### `usageByCategory` — Channel Breakdown + +| Channel | Description | +|---|---| +| `ATM` | ATM cash withdrawals | +| `ECOM` | E-commerce / online purchases | +| `POS` | Point-of-sale payments | + +Each channel object: + +| Field | Type | Description | +|---|---|---| +| `remaining` | `number` | Remaining limit for this channel (USD) | +| `limit` | `number` | Total limit for this channel (USD) | + +--- + +## Error Responses + +| HTTP Code | Meaning | +|---|---| +| `401` / `419` | Access token expired — attempt [token refresh](03-oauth-token.md#token-refresh) | + +--- + +  + +--- + +[← Account Validation](10-validate.md) diff --git a/docs/bmlapi/README.md b/docs/bmlapi/README.md new file mode 100644 index 0000000..d3da7fc --- /dev/null +++ b/docs/bmlapi/README.md @@ -0,0 +1,196 @@ +# 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)