TL;DR
Most of the migration is mechanical. The five changes that break existing code are:- Base URL —
/hl/v1becomes/hl/v2. - Pagination —
page/pageSize/pageCountis replaced by cursor pagination withlimit/startingAfterand the response fieldshasMore/nextStartingAfter. - Path and HTTP method changes — several endpoints are renamed and some V1
GETactions becomePOST(see section 5, “Endpoint mapping”). - Request body changes — the target
idmoves into the URL path for edits, some fields are added, and the webhook retry body is different. - Moved actions — V1
GETclose/reopen calls becomePOST .../{action}.
1. Base URL
/credit/v1/* becomes /credit/v2/*.
SaaS license endpoints use /saas/v1/license/* → /saas/v2/license/*.
2. Authentication
No change. V2 uses the same API key, created at https://web.mayar.id/api-keys, sent the same way:Authorization Header
GET
endpoints, a read-and-write key can call GET and POST. Because the base URL changes, make sure
your key is used against the matching environment (production key with api.mayar.id, sandbox key
with api.mayar.io).
3. Pagination (the biggest change)
Every V2 list endpoint uses cursor pagination. The V1 offset model no longer exists.How to migrate a list loop
V1 — offset pagination:startingAfter is an opaque cursor — pass the exact nextStartingAfter string from the previous
response. Do not compute or modify it. When hasMore is false, nextStartingAfter is null.4. Response envelope
V2 moves toward a single response envelope. Most endpoints return{ statusCode, messages, data } (list endpoints add hasMore/nextStartingAfter), but it is not
universal — some write endpoints return only { statusCode, messages }. Compare:
5. Endpoint mapping (V1 → V2)
Products & payment links
The product status action accepts
active, closed, and unlisted on V2. The old open/close
wording is still accepted as an alias, but use active/closed in new code. The status action is
a POST on V2 — the V1 GET /product/close|open/{id} calls will not work.Invoice & payments
POST /hl/v2/payments/simulate is sandbox only. On production it returns
403 { "statusCode": 403, "messages": "Simulate payment is not available in this environment" }.Customers, installments, coupons
Transactions, balance, QR, webhooks
Membership, reviews, licenses
6. Request body changes
Products / payment links
- Create payment link — request body is unchanged from V1:
nameandamountare required;description,redirectUrl,notes, andexpiredAtare optional;coverImage,limit, andtaxwere already supported in V1. - Edit any product —
name,description, andamountbecome optional (partial update), andlinkis added (changes the public URL slug). Editing still requires the productidin the body; the{id}in the URL is cosmetic. - Edit digital / webinar / event — every field except
idis optional on V2; at least one field must be provided. - Membership products —
amountis required when editing.
Invoice
- Create —
paymentMethodandcashtagare added.tax,redirectUrl,description,expiredAt, andextraDatabehave as in V1 (descriptionis still required). An itemratemay be negative to represent a discount line, as long as the invoice total is greater than zero. - Edit — the invoice
idis no longer sent in the body; it comes from the{uuid}path segment.itemsremains required;description,redirectUrl,expiredAt,notes, andtaxstay optional. An itemratemay be negative.
Payment request
- Create —
paymentMethodandcashtagare added.amountremains the only required field;email,mobile,description,redirectUrl,notes, andextraDatastay optional. - Edit — the payment
idis no longer sent in the body; it comes from the{uuid}path segment.amountis required;email,mobile,description,notes,extraData,paymentMethod, andcashtagare optional.
Webhook
- Retry — the request body changed. V1 sends
{ webhookHistoryId }; V2 sends{ paymentLinkId, type, payload, paymentLinkTransactionId }and uses theurlHookregistered on your account as the destination.
Coupon
- Validate — the request body is unchanged, but the error split changed: a missing or invalid
coupon now returns
404, while a coupon that exists but does not apply to the product returns400. V1 returned404for both. - Create — request body is unchanged from V1 (
expiredAt,discount.minimumPurchase, andcoupon.codewere already optional, andeligibleCustomerTypealready acceptedall/new/old).
Customer
7. Response field changes
- Product list items — the embedded
transactionsarray is no longer returned on the product list (V1 always returned an empty[]). UseGET /hl/v2/products/{uuid}/transactionsfor transaction data. - Paid transactions — the item gains
transactionId(alias ofpaymentLinkTransactionId) andamount(alias ofcredit). All V1 fields are still present. - Installment create / detail —
createdAtis now an ISO 8601 string. The response addsamount,totalInterest,totalAmount,description,customer, andstatus; invoice lines now exposedueDate(mapped from the oldexpiredAt) andpaymentUrl, and dropcustomerId,customer,category, anddescription. The detail response no longer returnspaymentLinkId,updatedAt,userId, orpaymentLink. - Membership member read — the new by-id read
GET /hl/v2/memberships/members/{memberId}returns a richer object: the flattenedcustomerEmail/customerName/customerMobilebecome a nestedcustomer { id, email, name, mobile }, and it addspaymentLink,membershipTier, and status/trial fields. - QR create — the
dataobject addsqrString; the success message is lowercased to"success".
8. Status codes and errors
V1 pages document almost no error responses. V2 consistently documents:
In most cases the HTTP status matches the
statusCode field, but not always: some V2 write
endpoints return HTTP 200 with a non-200 statusCode in the body (for example the product and
payment status action). Treat the body statusCode as authoritative. Two behavior changes worth
noting:
- Coupon validate now distinguishes a missing coupon (
404 "Gagal! Kode diskon ini tidak ditemukan.") from a coupon that exists but is not applicable (400). - Some V2 write endpoints return
messages(plural) on success butmessage(singular) on membership/SaaS writes. Don’t assume a single key across all endpoints.
9. Removed or moved endpoints
The following V1 calls are no longer drop-in compatible in V2:- The V1
GET-based close/reopen pattern for products, payment requests, and invoices is replaced byPOST /hl/v2/products/{uuid}/{action}andPOST /hl/v2/payments/{uuid}/{action}. Invoices are payment links, so the generic product action endpoint applies to them too. Useactive,closed, orunlisted;open/closeare still accepted as aliases.
GET /hl/v1/product?search= and GET /hl/v1/payment?status= are not
separate endpoints in V2 — the same filters are query parameters on GET /hl/v2/products and
GET /hl/v2/payments.
10. Migration checklist
- Replace the base URL
/hl/v1with/hl/v2(and/credit/v1→/credit/v2,/saas/v1/license→/saas/v2/license). - Rewrite every list call to cursor pagination:
limit+startingAfter, loop onhasMore, and stop whenhasMoreisfalse. Setlimitno higher than50. - Stop reading
page,pageSize,pageCount, andtotalfrom list responses. - Update renamed paths and change V1
GETclose/open calls to V2POSTstatus actions. - Update request bodies: send
idvia the path for invoice/payment/product edits, use the new webhook retry body, and update the customer update toPOST /hl/v2/customers/{uuid}/updatewithtoEmail. - Update parsers for changed response fields (product list, installments, membership member detail, transactions).
- Handle the documented
400/401/404/409/429error responses. - Replace the V1 close/reopen calls described in section 9 with the V2
POST .../{action}endpoints. - Re-test against the sandbox base URL
https://api.mayar.io/hl/v2before switching production.
