Kesles Merchant Partner API Reference
This document summarizes the Kesles Merchant APIs that are relevant to partner cooperation needs for accessing merchant data, transactions, dashboards, and reference data.
This document is not a new partner API contract. Its content reads from the backend implementation already active in:
merchant_core_api
Goals of this document:
- give an overview of the endpoints already available
- explain the authentication patterns currently in place
- distinguish between endpoints that are safe for partner integration and endpoints that are still internal-only
- provide a basis for discussion if a more formal
partner APIis created later
Status Summary
Kesles Merchant already has the initial foundation of a partner namespace:
/api/partner/v1/...
The implementation has since progressed significantly — a formal /api/partner/v1 namespace is now active (implemented in partner_service on port 8086 and reverse-proxied through merchant_core_api) including referral portfolio and commission reporting. What is currently available falls into four large groups:
public endpointbearer endpointfor the merchant user/app/dashboardinternal endpointfor internal service/operator needspartner endpointread-only for transactions, merchant referrals, and scoring
This means that for current partner cooperation there are three realistic approaches:
- partner accesses data on behalf of a merchant user using a bearer token
- partner is whitelisted as a server-to-server internal integration using
X-Internal-API-Key - partner uses the
/api/partner/v1endpoints withcredential_keyandcredential_secret
The formal partner contract that is now starting to be prepared uses the pattern:
partner_idcredential_keycredential_secret- per-partner audit log
- per-partner rate limit
- partner-specific endpoint whitelist
This document focuses on the APIs that already exist today, including the early implementation of /api/partner/v1.
Base URL
Common base URLs in use today:
- local:
http://localhost:8080 - production:
https://kesles.com/merchant/api
Authentication Types
1. Public
No token required.
Used for:
- legal documents
- UMKM academy
- some reference lookups
2. Bearer Token
Use the header:
Authorization: Bearer <access_token>
This pattern is used when the partner acts on behalf of a merchant/user.
Endpoints relevant for merchant data:
GET /merchant/home-dashboardGET /merchant/transactionsPOST /merchant/registrationGET /merchant/registration/device-offerPOST /merchant/feedbackGET /auth/profilePUT /auth/profileGET /auth/account-settingsPUT /auth/account-settings
3. Internal API Key
Use the header:
X-Internal-API-Key: <INTERNAL_NOTIFICATION_API_KEY>
These endpoints are not recommended to be used directly by general external partners, because their scope is still internal-operational.
Internal endpoints currently active:
POST /internal/notifications/whatsapp/otpPOST /internal/notifications/push/testPOST /internal/merchant/registration/reviewPOST /internal/merchant/registration/approvePOST /internal/merchant/status
4. Partner Credential
Use the endpoint:
POST /api/partner/v1/auth/token
Request:
{
"credential_key": "partner-demo-key",
"credential_secret": "dev-secret-partner-demo"
}
Response:
{
"token_type": "Bearer",
"access_token": "<jwt>",
"expires_in_secs": 900,
"scope": [
"merchant.read",
"transaction.read",
"reference.read"
],
"partner_id": "uuid",
"partner_code": "PRT-SALES-001",
"partner_name": "PT Sales Perdana",
"credential_name": "Sales Dev Credential"
}
Demo development credentials prepared today:
partner-demo-key / dev-secret-partner-demo— PT Sales Perdana (sales, scope:merchant.read,transaction.read)finance-demo-key / dev-secret-finance-demo— Bank Mandiri (bank, scope:merchant.read,scoring.read)vc-demo-key / dev-secret-vc-demo— East Ventures (venture_capital, scope:merchant.readonly)
Common Error Format
Most JSON errors use the pattern:
{
"error": "human readable message",
"code": "machine_readable_code",
"retry_after_secs": 60
}
Notes:
retry_after_secsis not always presentcodeshould be used by partners for handling logicerroris more suitable for logging or debugging
Recommended Partner Integration Patterns
Before opening transaction data access to partners, the partner model must be clearly distinguished. The current document is still generic; from a business perspective it makes more sense to split partners into at least two groups:
1. Sales / referral partners
This kind of partner may not see all merchants. The safest scope is only merchants that:
- are referred by that partner, or
- have a
referral_codepointing to the related sales partner
Implications:
- transaction access must be restricted per partner
- partner must not be able to read merchants outside its referral portfolio
- ideally there is a mapping table such as:
partner_idmerchant_idreferral_codeaccess_status
Data typically sufficient for sales partners:
- merchant identity
- merchant status
- onboarding/approval status
- summary of referral merchant transactions
- referral merchant performance
2. Banking / finance / scoring partners
This kind of partner may access transaction data only for business scoring purposes, not to freely view all merchant data.
This means their access should:
- be based on merchant consent or a formal cooperation agreement
- be limited to fields relevant to underwriting/scoring
- not expose sensitive data that is not needed
Examples of data relevant to scoring:
- daily/weekly/monthly revenue
- transaction count
- average ticket size
- transaction growth trend
- merchant status
Examples of data that should be restricted or masked:
- user NIK
- full account number
- raw payer reference if not needed
- personal identity details that are not relevant
A. If a partner needs to read merchant data of a specific user
Use the flow:
POST /auth/resolve-phonePOST /auth/request-otpPOST /auth/verify-otp- store
access_token - access merchant endpoints via bearer token
This pattern is suitable when the partner:
- is embedded in the merchant flow
- works with the merchant's consent
- needs access to the dashboard and transactions of the related merchant
Important notes:
- the partner must not get terminal device control access
- the partner must not get terminal pairing access
- the partner must not get access to read merchant terminal details
B. If a partner only needs master/reference data
Use public/reference endpoints without bearer when possible.
C. If a partner needs operational server-to-server integration
There is currently no dedicated partner API. If this still has to be done, it usually requires:
- whitelisting use of internal APIs
- a security agreement
- additional audit log
- IP / API key restrictions
For sales partners and finance partners, I recommend not using internal endpoints directly. Both are better served by formal partner endpoints with different scope and payloads.
Existing Endpoints
/api/partner/v1 Endpoints Already Implemented
The following endpoints are now available in the backend (implemented in partner_service):
POST /api/partner/v1/auth/tokenGET /api/partner/v1/partners/{partner_id}/referral-merchantsGET /api/partner/v1/partners/{partner_id}/referral-merchants/summaryGET /api/partner/v1/partners/{partner_id}/commission-basisGET /api/partner/v1/partners/{partner_id}/settlementsGET /api/partner/v1/partners/{partner_id}/portfolioGET /api/partner/v1/merchants/{merchant_id}GET /api/partner/v1/merchants/{merchant_id}/transactionsGET /api/partner/v1/merchants/{merchant_id}/scoring-summaryGET /api/partner/v1/merchants/{merchant_id}/dashboardPOST /api/partner/v1/merchant-registrationsGET /api/partner/v1/merchant-registrations/device-offerGET /api/partner/v1/references/*
Access rules:
- partner can only read merchants that fall within the referral scope, access mapping, or consent
- partner does not get access to terminals, pairings, or device control
- the scoring endpoint is intended for finance/scoring partners, not for unrestricted access to all merchant data
POST /api/partner/v1/auth/token
- Auth: bearer not required, uses
credential_keyandcredential_secret - Function: issues a partner access token
GET /api/partner/v1/partners/{partner_id}/referral-merchants
- Auth: partner bearer
- Scope:
merchant.read - Function: list of referral merchants belonging to the partner
GET /api/partner/v1/partners/{partner_id}/referral-merchants/summary
- Auth: partner bearer
- Scope:
merchant.read - Function: aggregate of referral merchants and their transaction performance
GET /api/partner/v1/merchants/{merchant_id}/transactions
- Auth: partner bearer
- Scope:
transaction.read - Function: merchant transactions within the partner's scope
- Supported queries:
periodanchor_date
GET /api/partner/v1/merchants/{merchant_id}/scoring-summary
- Auth: partner bearer
- Scope:
scoring.readorrisk-summary.read - Function: business scoring summary based on transactions
Main fields:
average_monthly_gross_amountaverage_monthly_transaction_countavg_ticket_sizegrowth_30dgrowth_90dsettlement_consistency_scoretransaction_stability_score
GET /api/partner/v1/partners/{partner_id}/commission-basis
- Auth: partner bearer
- Scope:
transaction.read - Function: commission breakdown per revenue-share rule for a billing period
- Supported queries:
period—YYYY-MMformat, default = current calendar month
Response:
{
"schema_version": "1",
"partner_id": "uuid",
"period": "2026-05",
"period_start_at": "2026-05-01T00:00:00Z",
"period_end_at": "2026-06-01T00:00:00Z",
"currency": "IDR",
"total_commission_amount": 1500000,
"rules_matched": 2,
"rows": [
{
"rule_id": "uuid",
"fee_type": "qris_mdr",
"basis": "gross_amount",
"rate_bps": 30,
"fixed_amount": null,
"source_partner_id": "uuid",
"source_partner_name": "PT Sales Perdana",
"merchant_count": 150,
"gross_amount": 5000000000,
"net_amount": 4850000000,
"transaction_count": 2500,
"commission_amount": 1500000
}
]
}
Error: 400 bad_request if period is not in YYYY-MM format.
GET /api/partner/v1/partners/{partner_id}/portfolio
- Auth: partner bearer
- Scope:
merchant.read - Function: partner-wide rollup for the last 30 days — totals, tier distribution
- No query parameters
Response:
{
"schema_version": "1",
"partner_id": "uuid",
"window": {
"days": 30,
"computed": "materialized_view",
"currency": "IDR"
},
"totals": {
"merchant_count": 500,
"active_merchant_count": 380,
"inactive_merchant_count": 90,
"churned_merchant_count": 30,
"new_30d": 25,
"gross_amount_30d": 15000000000,
"net_amount_30d": 14500000000,
"transaction_count_30d": 45000
},
"tiers": [
{ "grade": "A", "count": 50, "gross_share": 0.45 },
{ "grade": "B", "count": 150, "gross_share": 0.35 },
{ "grade": "C", "count": 180, "gross_share": 0.20 }
]
}
Note: tiers[].gross_share is a decimal fraction (0–1), not a percentage.
1. Health & Observability
GET /health
- Auth: not required
- Purpose: health check of the main service
Example response:
{
"service": "merchant_core_api",
"status": "ok",
"db_reference_status": "connected"
}
GET /health/otp-runtime
- Auth: not required
- Purpose: check the active OTP runtime store
Example response:
{
"service": "merchant_core_api",
"status": "ok",
"otp_runtime_store": "redis"
}
GET /metrics
- Auth: not required
- Purpose: observability/metrics
- Note: more suitable for internal monitoring, not for business partner consumption
2. Auth & Session
These endpoints are important if the partner needs to obtain a bearer token to access merchant data.
POST /auth/resolve-phone
- Auth: not required
- Function:
- check the phone number
- determine whether OTP is needed
- or authenticate directly if the device is already known
Request body:
{
"phone": "08114169868",
"platform_code": "mobile_user",
"device_id": "android-123",
"device_name": "Samsung A54"
}
Possible responses:
{
"status": "verification_required",
"phone": "08114169868",
"reason": "new_device",
"verification_channel": "whatsapp"
}
or:
{
"status": "authenticated",
"phone": "08114169868",
"token_type": "Bearer",
"access_token": "jwt-access-token",
"refresh_token": "jwt-refresh-token",
"expires_in_secs": 3600
}
POST /auth/request-otp
- Auth: not required
- Function: send OTP to the user's phone number
Request body:
{
"phone": "08114169868",
"method": "whatsapp",
"platform_code": "mobile_user",
"device_id": "android-123",
"device_name": "Samsung A54"
}
Example response:
{
"status": "otp_sent",
"request_id": "uuid",
"phone": "08114169868",
"method": "whatsapp",
"expires_in_secs": 300,
"retry_after_secs": 60
}
POST /auth/verify-otp
- Auth: not required
- Function: verify the OTP and issue an access token
Request body:
{
"request_id": "uuid",
"phone": "08114169868",
"code": "123456",
"platform_code": "mobile_user",
"device_id": "android-123",
"device_name": "Samsung A54"
}
Example response:
{
"status": "verified",
"phone": "08114169868",
"verified": true,
"token_type": "Bearer",
"access_token": "jwt-access-token",
"refresh_token": "jwt-refresh-token",
"expires_in_secs": 3600
}
GET /auth/device-login/cancel
- Auth: not required
- Required query:
token
- Function: cancel a new-device login
POST /auth/push-tokens
- Auth: bearer
- Function: register the device push token
Request body:
{
"push_token": "firebase-token",
"device_id": "android-123",
"device_name": "Samsung A54"
}
Response:
{
"status": "registered"
}
3. Profile & Account Settings
These endpoints are appropriate when the partner needs to read or update the profile of the currently logged-in merchant user.
GET /auth/profile
- Auth: bearer
- Function: read the current user's profile
Common data in the response:
user_idphoneemailfull_namenikgenderaddress_lineavatar_urlmerchant_status- email/phone verification status
PUT /auth/profile
- Auth: bearer
- Function: update the user profile
Request fields supported by the current implementation:
full_namenikgenderaddress_lineavatar_urlemail
Notes:
- if the email is already used by another user, the backend may return
409 Conflict
GET /auth/account-settings
- Auth: bearer
- Function: read the user's account settings
Response fields:
notification_push_enablednotification_transactions_enablednotification_promotions_enablednotification_daily_summary_enabledapp_languageprivacy_analytics_enabledprivacy_data_sharing_enabledupdated_at
PUT /auth/account-settings
- Auth: bearer
- Function: update account settings
Example request:
{
"notification_push_enabled": true,
"notification_transactions_enabled": true,
"notification_promotions_enabled": false,
"notification_daily_summary_enabled": true,
"app_language": "id",
"privacy_analytics_enabled": true,
"privacy_data_sharing_enabled": false
}
POST /auth/profile/email/request-otp
- Auth: bearer
- Function: request OTP for email verification/update
Request body:
{
"email": "merchant@example.com"
}
POST /auth/profile/email/verify-otp
- Auth: bearer
- Function: verify the email OTP
Request body:
{
"email": "merchant@example.com",
"code": "123456"
}
4. Merchant Registration
These endpoints are suitable for partners helping with the new merchant onboarding process.
GET /merchant/registration/device-offer
- Auth: bearer
- Function: fetch the active device offer for merchant registration
Example response:
{
"offer_code": "QRIS-PLUS-STD",
"terminal_type_code": "qris_plus",
"terminal_type_name": "QRIS Plus",
"device_type_name": "EDC Android",
"device_name": "Sunmi P2",
"device_price_amount": 2500000,
"shipping_fee_amount": 100000,
"promo_amount": 500000
}
POST /merchant/registration
- Auth: bearer
- Function: submit a new merchant application
Important request fields from the current implementation:
merchant_namebusiness_entity_typebusiness_type_idaverage_monthly_revenueemployee_countcontact_phonecontact_emailaddress_linecountry_idcountry_nameprovince_idprovince_namecity_idcity_namedistrict_iddistrict_namesubdistrict_idsubdistrict_namepostal_code_idpostal_codebank_nameaccount_numberaccount_holder_namedevice_offer_codedevice_quantitydevice_price_amountshipping_fee_amountpromo_amountdevice_fee_service_acceptedterms_of_service_acceptedprivacy_policy_acceptedreferral_code
Important validation notes:
business_entity_typemust bePerseorangan(Individual) orBadan Usaha(Business Entity)bank_nameis validated against the PSP settlement bank referencedevice_offer_codeis validated against active offers- device fee values are checked against the active offer
- if the user already has an active merchant or an open registration, the backend may return
409 Conflict
Successful response:
- status
pending merchantobject- the latest user profile
POST /merchant/feedback
- Auth: bearer
- Function: send merchant feedback
- Suitable for operational needs or in-app feedback
5. Merchant Data Access
These are the endpoints most relevant when a partner needs to read operational merchant data.
GET /merchant/home-dashboard
- Auth: bearer
- Function: fetch a summary of the merchant dashboard data
The response contains:
merchant_idmerchant_namemerchant_statusbalance_totaltoday_summarygross_amountmdr_fee_amountservice_fee_amountnet_amounttransaction_count
recent_transactionsweekly_gross_pointsweekly_reference_date
Example partner use cases:
- embedded dashboard
- partner analytics panel
- partner monitoring of merchant revenue
GET /merchant/transactions
- Auth: bearer
- Function: read the merchant transaction list
Supported query parameters:
periodanchor_date
Notes:
- the limit is currently hard-coded in the backend to
50
The response contains:
merchant_idmerchant_namemerchant_statusperiodperiod_start_atperiod_end_atsummaryitems
Important fields per item:
idtransaction_codeexternal_referencetransaction_statuspayment_methodpayment_channeldevice_idnmidgross_amountmdr_ratemdr_fee_amountnet_amount_after_mdrdaily_service_fee_amounttransaction_atsettled_atpayer_referencenotes
6. Reference Data
These endpoints are appropriate for partners needing to display merchant onboarding master data.
GET /references/provinces
- Auth: not required
- Response:
{
"items": [
{
"id": "uuid",
"code": "73",
"name": "Sulawesi Selatan"
}
]
}
GET /references/cities?province_id=<id>
- Auth: not required
- Required query:
province_id
GET /references/districts?city_id=<id>
- Auth: not required
- Required query:
city_id
GET /references/subdistricts?district_id=<id>
- Auth: not required
- Required query:
district_id
GET /references/postal-codes?subdistrict_id=<id>
- Auth: not required
- Required query:
subdistrict_id
GET /references/business-scales
- Auth: not required
- Function: list of business scales
Response fields:
idcodenamemodal_usaha_minmodal_usaha_maxomzet_per_bulan_minomzet_per_bulan_maxjumlah_karyawan_minjumlah_karyawan_maxsort_order
GET /references/business-types
- Auth: not required
- Function: list of business types
Response fields:
idcodenamecategory_idcategory_codecategory_namebenchmark_transactionsbenchmark_ticket_sizebase_operational_cost_rate
7. Public Content
GET /public/legal-documents?code=<code>
- Auth: not required
- Available codes:
terms_of_serviceprivacy_policy
GET /public/umkm-academy/home
GET /public/umkm-academy/topics
GET /public/umkm-academy/topic
GET /public/umkm-academy/article
Auth: not required
These endpoints are more suitable for:
- content/academy integration
- public information embedding
8. Internal Endpoints
The endpoints below exist today, but their status is internal-only:
POST /internal/notifications/whatsapp/otpPOST /internal/notifications/push/testPOST /internal/merchant/registration/reviewPOST /internal/merchant/registration/approvePOST /internal/merchant/status
For partner cooperation, these endpoints should:
- not be opened directly to the public
- not be used without a clear whitelist and security contract
Endpoints Most Ready for Partner Use Today
If the goal is reading merchant data already in production, the most ready endpoints today are:
POST /auth/resolve-phonePOST /auth/request-otpPOST /auth/verify-otpGET /auth/profileGET /merchant/home-dashboardGET /merchant/transactionsGET /references/business-typesGET /references/business-scales- region endpoints
references/*
Important notes:
- the endpoints above are not yet sufficiently safe for direct use by sales partners viewing all merchants
- the endpoints above also do not yet separate finance/scoring-specific payloads
- so for serious external partners, the existing endpoints are more suitable as a design basis, not a final contract
Architecture Decisions (Updated 2026-06-02)
db_kesles_merchantis the permanent core DB — nodb_kesles_merchant_corewill be createdmerchant_core_api(port 8080) = "core-service" — no separate Go service will be built for the merchant domainCORE_SERVICE_URL=http://localhost:8080permanently — not a temporary placeholderGET /internal/merchants/{id}/contactexists (returns name/phone/email/status);GET /internal/merchants/{id}also exists, returning code+name+referral_partner_id+status
Gaps That Still Exist Before Becoming a Formal Partner API
Status per 2026-06-02. ✅ = addressed, ❌ = still open.
- ✅
/partner/...namespace — active as/api/partner/v1/ - ✅ per-partner scoped API key —
api_credentialswithallowed_scopesarray - ✅ per-partner audit log — every request logged to
partner.api_audit_logs - ✅ formal API versioning
/v1— all partner endpoints under/api/partner/v1/ - ✅ separation between referral/sales and finance/scoring partners — done via
access_scope_mode+ separatescoring.readscope - ✅ merchant filtering based on
referral_code/ partner-merchant mapping —ResolveMerchantScopeenforces at query level - ✅ pagination and filter/sorting — active on
referral-merchantsandsettlementsendpoints - ✅
GET /internal/merchants/{id}withmerchant_status— implemented (returns code+name+referral_partner_id+status), alongside the/contactsubpath - ❌ explicit partner rate limiting — not yet implemented per-partner (only global server-side limits)
- ❌ partner webhooks for transactions or merchant status — webhook config storage, delivery logging (
partner.webhook_deliveries), and URL validation exist, but no event-driven dispatcher fires webhooks on transaction/merchant-status events yet - ❌ partner self-service portal — dashboard for partners to manage their own credentials and view their own analytics (planned post Phase 3)
- ❌ explicit enforcement prohibiting terminal/device access in formal API contract — still no contract document; enforcement is code-only
- ❌ per-partner-type data minimization policy (e.g. bank sees only scoring fields, not full tx detail) — app-level separation exists via scopes but no formal field-level masking policy document
Recommended Next Steps
Status per 2026-06-02. ✅ = done.
- ✅ determine which endpoints are allowed to be opened to partners
- ✅ create a
Partner API Specseparate from the mobile/dashboard internal API - ✅ add partner credentials (
credential_key/credential_secret+ HMAC option) - ✅ create an audit log of all partner requests (
partner.api_audit_logs) - ✅ separate access policy for referral/sales partners and finance/scoring partners
- set up per-partner rate limiting (still open)
- prepare partner webhooks for transaction events and merchant status (planned Phase 3)
- publish formal API contract document with terminal/device access prohibition
- implement field-level data minimization policy per partner type
Source References
This document is based on the implementation currently active in:
merchant_core_api/internal/httpapi/routes_auth.gomerchant_core_api/internal/httpapi/routes_merchant.gomerchant_core_api/internal/httpapi/routes_reference.gomerchant_core_api/internal/httpapi/routes_public.gomerchant_core_api/internal/httpapi/routes_internal.gomerchant_core_api/internal/httpapi/server.gomerchant_core_api/internal/httpapi/merchant_home_dashboard_handlers.gomerchant_core_api/internal/httpapi/merchant_transaction_handlers.gomerchant_core_api/internal/httpapi/merchant_handlers.gomerchant_core_api/internal/httpapi/merchant_device_offer_handlers.gomerchant_core_api/internal/httpapi/reference_handlers.gomerchant_core_api/internal/httpapi/account_settings_handlers.go