update bml api docs
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 5s

This commit is contained in:
2026-05-23 23:33:31 +05:00
parent a3f8852163
commit 0a27de4a34
12 changed files with 2250 additions and 0 deletions

256
docs/bmlapi/01-login.md Normal file
View File

@@ -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=<token>; Path=/; SameSite=Lax
Set-Cookie: blaze_session=<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: <xsrf_token>' \
--cookie 'XSRF-TOKEN=<xsrf_token>; blaze_session=<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=<xsrf_token>; blaze_session=<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: <xsrf_token_2>' \
--cookie 'XSRF-TOKEN=<xsrf_token_2>; blaze_session=<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=<xsrf_token>; blaze_session=<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)**
---
&nbsp;
---
[← README](README.md) &nbsp;&nbsp;&nbsp; **Next →** [Business Profile OTP](02-business-otp.md)

View File

@@ -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 15](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=<xsrf_token>; blaze_session=<session>; blaze_identity=<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: <xsrf_token>' \
--cookie 'XSRF-TOKEN=<xsrf_token>; blaze_session=<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: <xsrf_token_fresh>' \
--cookie 'XSRF-TOKEN=<xsrf_token_fresh>; blaze_session=<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)**
---
&nbsp;
---
[← Login](01-login.md) &nbsp;&nbsp;&nbsp; **Next →** [OAuth Token](03-oauth-token.md)

View File

@@ -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=<state>&nonce=<nonce>&code_challenge=<code_challenge>&code_challenge_method=S256&Device-ID=<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=<session>; blaze_identity=<identity>'
```
### Response
`302` redirect to:
```
https://app.bankofmaldives.com.mv/oauth/mobile-callback?code=<auth_code>&state=<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=<auth_code>' \
--data 'code_verifier=<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=<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 <token>` 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...<new>",
"refresh_token": "def50200...<new or same>",
"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 |
---
&nbsp;
---
[← Business Profile OTP](02-business-otp.md) &nbsp;&nbsp;&nbsp; **Next →** [Dashboard](04-dashboard.md)

228
docs/bmlapi/04-dashboard.md Normal file
View File

@@ -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 <access_token>` |
| `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 <access_token>' \
--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.
---
&nbsp;
---
[← OAuth Token](03-oauth-token.md) &nbsp;&nbsp;&nbsp; **Next →** [User Info](05-userinfo.md)

141
docs/bmlapi/05-userinfo.md Normal file
View File

@@ -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 <access_token>' \
--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 <access_token>' \
--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 <access_token>' \
--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).
---
&nbsp;
---
[← Dashboard](04-dashboard.md) &nbsp;&nbsp;&nbsp; **Next →** [Account History](06-account-history.md)

View File

@@ -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 <access_token>` |
| `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 <access_token>' \
--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) |
---
&nbsp;
---
[← User Info](05-userinfo.md) &nbsp;&nbsp;&nbsp; **Next →** [Card Statement](07-card-statement.md)

View File

@@ -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 <access_token>` |
| `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 <access_token>' \
--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) |
---
&nbsp;
---
[← Account History](06-account-history.md) &nbsp;&nbsp;&nbsp; **Next →** [Transfer](08-transfer.md)

235
docs/bmlapi/08-transfer.md Normal file
View File

@@ -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 <access_token>' \
--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 <access_token>` |
| `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 <access_token>' \
--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 <access_token>' \
--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.
---
&nbsp;
---
[← Card Statement](07-card-statement.md) &nbsp;&nbsp;&nbsp; **Next →** [Contacts](09-contacts.md)

157
docs/bmlapi/09-contacts.md Normal file
View File

@@ -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 <access_token>' \
--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 <access_token>' \
--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 <access_token>' \
--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.
---
&nbsp;
---
[← Transfer](08-transfer.md) &nbsp;&nbsp;&nbsp; **Next →** [Account Validation](10-validate.md)

175
docs/bmlapi/10-validate.md Normal file
View File

@@ -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 <access_token>' \
--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 <access_token>' \
--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 <access_token>' \
--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 |
---
&nbsp;
---
[← Contacts](09-contacts.md) &nbsp;&nbsp;&nbsp; **Next →** [Foreign Limits](11-foreign-limits.md)

View File

@@ -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 <access_token>` |
| `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 <access_token>' \
--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) |
---
&nbsp;
---
[← Account Validation](10-validate.md)

196
docs/bmlapi/README.md Normal file
View File

@@ -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 <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="{&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)