Skip to main content
API V1 is deprecated on 1 October 2026. After that date, all https://api.mayar.id/hl/v1/* endpoints will stop working. This guide lists every breaking change you must handle to move your integration to API V2 before the deadline.

TL;DR

Most of the migration is mechanical. The five changes that break existing code are:
  1. Base URL/hl/v1 becomes /hl/v2.
  2. Paginationpage/pageSize/pageCount is replaced by cursor pagination with limit/startingAfter and the response fields hasMore/nextStartingAfter.
  3. Path and HTTP method changes — several endpoints are renamed and some V1 GET actions become POST (see section 5, “Endpoint mapping”).
  4. Request body changes — the target id moves into the URL path for edits, some fields are added, and the webhook retry body is different.
  5. Moved actions — V1 GET close/reopen calls become POST .../{action}.

1. Base URL

Credit endpoints use a different prefix in both versions: /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
The Read Only / Read & Write scopes behave the same: a read-only key can only call 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:
V2 — cursor pagination:
Pseudocode:
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:
One naming quirk to keep in mind: most V2 endpoints return messages (plural), but some write endpoints (for example membership writes and SaaS license activate/deactivate) return message (singular). Read the exact shape on each endpoint page before parsing it.

5. Endpoint mapping (V1 → V2)

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

Membership routes were reshaped. The V1 detail reads map to V2 as GET /hl/v1/membership/{productId}GET /hl/v2/memberships/{productId}/tiers and GET /hl/v1/membership-member-detail/{productId}?memberId=GET /hl/v2/memberships/{productId}/members?memberId=. V2 also adds cursor list endpoints (GET /hl/v2/memberships/tiers?productId=, GET /hl/v2/memberships/members?productId=) and a member-by-id read where the member id is the path parameter: GET /hl/v2/memberships/members/{memberId}?productId=.

6. Request body changes

  • Create payment link — request body is unchanged from V1: name and amount are required; description, redirectUrl, notes, and expiredAt are optional; coverImage, limit, and tax were already supported in V1.
  • Edit any productname, description, and amount become optional (partial update), and link is added (changes the public URL slug). Editing still requires the product id in the body; the {id} in the URL is cosmetic.
  • Edit digital / webinar / event — every field except id is optional on V2; at least one field must be provided.
  • Membership productsamount is required when editing.

Invoice

  • CreatepaymentMethod and cashtag are added. tax, redirectUrl, description, expiredAt, and extraData behave as in V1 (description is still required). An item rate may be negative to represent a discount line, as long as the invoice total is greater than zero.
  • Edit — the invoice id is no longer sent in the body; it comes from the {uuid} path segment. items remains required; description, redirectUrl, expiredAt, notes, and tax stay optional. An item rate may be negative.

Payment request

  • CreatepaymentMethod and cashtag are added. amount remains the only required field; email, mobile, description, redirectUrl, notes, and extraData stay optional.
  • Edit — the payment id is no longer sent in the body; it comes from the {uuid} path segment. amount is required; email, mobile, description, notes, extraData, paymentMethod, and cashtag are optional.

Webhook

  • Retry — the request body changed. V1 sends { webhookHistoryId }; V2 sends { paymentLinkId, type, payload, paymentLinkTransactionId } and uses the urlHook registered 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 returns 400. V1 returned 404 for both.
  • Create — request body is unchanged from V1 (expiredAt, discount.minimumPurchase, and coupon.code were already optional, and eligibleCustomerType already accepted all/new/old).

Customer

The V1 body field fromEmail is not used by V2. V2 identifies the customer by the path parameter and only accepts the new email in the body.
V1
V2

7. Response field changes

  • Product list items — the embedded transactions array is no longer returned on the product list (V1 always returned an empty []). Use GET /hl/v2/products/{uuid}/transactions for transaction data.
  • Paid transactions — the item gains transactionId (alias of paymentLinkTransactionId) and amount (alias of credit). All V1 fields are still present.
  • Installment create / detailcreatedAt is now an ISO 8601 string. The response adds amount, totalInterest, totalAmount, description, customer, and status; invoice lines now expose dueDate (mapped from the old expiredAt) and paymentUrl, and drop customerId, customer, category, and description. The detail response no longer returns paymentLinkId, updatedAt, userId, or paymentLink.
  • Membership member read — the new by-id read GET /hl/v2/memberships/members/{memberId} returns a richer object: the flattened customerEmail/customerName/customerMobile become a nested customer { id, email, name, mobile }, and it adds paymentLink, membershipTier, and status/trial fields.
  • QR create — the data object adds qrString; 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 but message (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 by POST /hl/v2/products/{uuid}/{action} and POST /hl/v2/payments/{uuid}/{action}. Invoices are payment links, so the generic product action endpoint applies to them too. Use active, closed, or unlisted; open/close are still accepted as aliases.
The standalone V1 pages 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/v1 with /hl/v2 (and /credit/v1/credit/v2, /saas/v1/license/saas/v2/license).
  • Rewrite every list call to cursor pagination: limit + startingAfter, loop on hasMore, and stop when hasMore is false. Set limit no higher than 50.
  • Stop reading page, pageSize, pageCount, and total from list responses.
  • Update renamed paths and change V1 GET close/open calls to V2 POST status actions.
  • Update request bodies: send id via the path for invoice/payment/product edits, use the new webhook retry body, and update the customer update to POST /hl/v2/customers/{uuid}/update with toEmail.
  • Update parsers for changed response fields (product list, installments, membership member detail, transactions).
  • Handle the documented 400/401/404/409/429 error 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/v2 before switching production.