bml-connect — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited bml-connect (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
BML Connect is the Bank of Maldives merchant payment platform — the primary way to accept online payments in the Maldives. This skill contains everything needed to integrate with the BML Connect API.
Every API call uses a static API key in the Authorization header. No OAuth, no token exchange, no expiry.
Authorization: YOUR_SECRET_API_KEYTwo types of API keys exist per app:
pk_ prefixed. Only for client-side card tokenization (PomeloJS). Not needed for server-side operations.The merchant also has an Application ID (UUID format) which may be needed for some SDK-based calls.
| Environment | Base URL | Dashboard |
|---|---|---|
| Production | https://api.merchants.bankofmaldives.com.mv/public | dashboard.merchants.bankofmaldives.com.mv |
| Sandbox (UAT) | https://api.uat.merchants.bankofmaldives.com.mv/public | dashboard.uat.merchants.bankofmaldives.com.mv |
Default to sandbox when writing integration code unless the user specifies production. This prevents accidental real charges during development.
| Operation | Method | Endpoint |
|---|---|---|
| Create transaction | POST | /public/v2/transactions |
| Get transaction | GET | /public/v2/transactions/{id} |
| Update transaction | PATCH | /public/v2/transactions/{id} |
| Send payment SMS | POST | /public/transactions/{id}/sms |
| Send payment Email | POST | /public/transactions/{id}/email |
| Create customer | POST | /public-customers |
| Get customer | GET | /public-customers/{id} |
| List customers | GET | /public-customers |
| Get customer tokens | GET | /public-customers/{id}/tokens |
| Charge token | POST | /public-customers/charge |
| Create shop | POST | /public/shops |
| Get shops | GET | /public/shops |
| Update shop | PATCH | /public/shops/{id} |
Critical URL quirk: Customer endpoints use /public-customers (dash), not /public/customers (slash). This is the most common integration mistake — getting this wrong produces confusing 404 errors.
This is the most common operation. Send amount in laari (smallest currency unit — 100 laari = MVR 1.00).
POST /public/v2/transactions
{
"amount": 15000,
"currency": "MVR",
"localId": "your-internal-id",
"customerReference": "Human-readable description",
"webhook": "https://your-domain.com/webhooks/bml",
"redirectUrl": "https://your-domain.com/payment/complete"
}Response contains:
id — BML transaction IDurl — full payment page URL (redirect the customer here or embed in iframe)shortUrl — short URL (useful for SMS)qr.url — QR code image URLstate — QR_CODE_GENERATED initiallyThe localId field is your internal reference — use it to match BML transactions back to your orders/invoices.
| State | Meaning |
|---|---|
QR_CODE_GENERATED | Transaction created, QR ready, awaiting payment |
CONFIRMED | Payment received — money is in |
CANCELLED | Transaction cancelled |
FAILED | Payment failed |
EXPIRED | Transaction timed out |
Only CONFIRMED means money received. Always verify via webhook or polling before marking anything as paid. Never trust client-side redirects alone — a user can manipulate the redirect URL.
Known limitation: MIB (Maldives Islamic Bank) customers cannot pay through BML Connect. MIB transfers must be handled manually outside of BML Connect. If the user's product serves MIB customers, suggest adding a manual bank transfer option alongside BML Connect.
BML sends a POST request to your webhook URL when a transaction state changes. This is the most reliable way to confirm payments.
BML sends three headers for signature verification:
X-Signature-NonceX-Signature-TimestampX-SignatureVerify by generating sha256(nonce + timestamp + apiKey) and comparing to the signature header using a timing-safe comparison (to prevent timing attacks):
generated = sha256(nonce + timestamp + your_api_key)
valid = timing_safe_equals(generated, X-Signature)Use the language-appropriate timing-safe comparison:
crypto.timingSafeEqual()hmac.compare_digest()hash_equals()subtle.ConstantTimeCompare()| Event | When it fires |
|---|---|
NOTIFY_TRANSACTION_CHANGE | Any state change on a transaction |
NOTIFY_TOKENISATION_STATUS | Card saved successfully (tokenization) |
The payload includes eventType, state, transactionId, and other transaction details. Only process CONFIRMED state to mark payments as complete.
Important: Your webhook endpoint must be excluded from CSRF protection. BML cannot send a CSRF token. Framework-specific examples:
$except in VerifyCsrfToken middlewareexpress.raw() or express.json() on the webhook route specifically@csrf_exempt decoratorPOST /public/transactions/{transactionId}/sms
{
"phoneNumber": "9607771234"
}POST /public/transactions/{transactionId}/email
{
"email": "[email protected]"
}Known issue: SMS and Email sending may fail with AWS authentication errors on BML's side. This is a BML infrastructure issue, not your code. If this happens, use the shortUrl from the transaction response and send it yourself via your own SMS/email provider. Always implement this fallback — don't rely solely on BML's SMS/email.
BML supports saving customer cards for one-click future payments.
tokenizationDetails — BML saves card, returns tokencustomerId + tokenId — charged instantly, no card re-entryPOST /public-customers
{
"name": "Customer Name",
"email": "[email protected]",
"phone": "9607771234"
}POST /public/v2/transactions
{
"amount": 15000,
"currency": "MVR",
"customerId": "bml-customer-id",
"tokenizationDetails": {
"tokenize": true,
"paymentType": "UNSCHEDULED",
"recurringFrequency": "UNSCHEDULED"
},
"webhook": "https://your-domain.com/webhooks/bml"
}POST /public-customers/charge
{
"customerId": "bml-customer-id",
"transactionId": "new-transaction-id",
"tokenId": "saved-card-token-id"
}Note: You must first create a new transaction (to get a transactionId), then charge the token against it. The token charge is a two-step process.
Webhook event NOTIFY_TOKENISATION_STATUS fires when a card is saved successfully.
MVR 150.00When writing helper functions, convert at the boundary:
toLaari(mvr) → mvr * 100 (integer)
toMVR(laari) → laari / 100 (for display only)BML webhooks require a public HTTPS URL. localhost doesn't work.
Solutions:
When generating dev setup code, prefer option 2 (simulation route) for simplicity, but mention option 1 as the more thorough alternative.
If building a platform where multiple merchants each have their own BML Connect account:
bml_api_key and bml_app_id in your database (encrypted at rest)companyId or matching the transactionId to your records)These are the mistakes that trip up almost every developer integrating BML Connect:
/public-customers not /public/customersshortUrl yourself~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.