mayar
Command-line interface for the Mayar API. Zero runtime dependencies — Node.js 18+ stdlib only.
Install
One-liner (no npm required):
curl -fsSL https://raw.githubusercontent.com/mayarid/mayar-cli/main/install.sh | sh
Downloads the latest tarball from the npm registry, extracts to ~/.local/share/mayar, and symlinks the binary to ~/.local/bin/mayar. Requires only node>=18, curl, and tar. Override paths or pin a version:
MAYAR_VERSION=0.1.9 MAYAR_BIN_DIR=/usr/local/bin \
curl -fsSL https://raw.githubusercontent.com/mayarid/mayar-cli/main/install.sh | sh
Via npm:
npm install -g mayar
npx mayar --help
From source:
git clone https://github.com/mayarid/mayar-cli.git
cd mayar-cli
npm link
Authentication
Preferred — browser login:
mayar login
Opens your browser for Google OAuth sign-in and saves the auth token automatically — no copy-pasting keys. Use mayar login --no-browser on headless machines to get a URL to open elsewhere.
Alternatives (resolution order: --api-key flag → MAYAR_API_KEY env → saved config file):
Option 1 — environment variable (recommended for CI/agents):
export MAYAR_API_KEY=your_api_key_here
mayar whoami
Option 2 — saved config (interactive first run):
The first time you invoke any command without a key configured, the CLI prompts for your production API key (input is masked). Paste it once; it's stored at ~/.config/mayar/config.json (chmod 600, follows XDG Base Directory) and reused on subsequent runs.
mayar balance
Option 3 — non-interactive setup:
mayar api-key YOUR_KEY_HERE
Option 4 — per-command override:
mayar --api-key YOUR_KEY_HERE balance
Get your API key from web.mayar.id → Integration → API Key.
v1.0.0 — Mayar API v2. All commands now target /hl/v2/.... Pagination switched from --page/--pageSize to cursor-based --limit/--after (returned in the previous response's nextStartingAfter). The --pageSize flag is still accepted as an alias for --limit.
Commands
Setup
init First-time setup (interactive, masked input)
login [--no-browser] Sign in via Google OAuth
api-key <key> Save API key non-interactively
config show|reset Inspect or remove the saved config
Account
whoami Merchant identity (JWT decode + live verify)
balance GET /hl/v2/balances
Invoices
invoice list [--limit --after --status --search]
invoice get <id>
invoice close <id> | reopen <id> POST /hl/v2/invoices/{id}/{open|close}
invoice status <id> <action> action ∈ open|close|active|closed|unlisted
invoice edit <id> --data <json|@file>
invoice filter --email <email> [--limit --after --status --search]
invoice create --data <json|@file>
Products
product list [--limit --after --search --type --stock]
product search <keyword>
product type <ebook|course|membership|saas|event|webinar|…>
product get <id>
product close | reopen | status <id> <action>
product transactions <id> [--limit --after --status --customerId]
product create --type <T> --data <json|@file>
T ∈ ebook|digital|event|webinar|generic|payment-link
product edit <id> --type <T> --data <json|@file>
product sort <generic_link|event|webinar|digital_product> [--limit --after]
POST /hl/v2/payment-links/sort/{type}
Payment links (alt route)
payment-link edit <id> --data <json|@file>
POST /hl/v2/payment-links/{id}/update
(alias for `product edit --type payment-link`)
Single payment requests
payment list [--limit --after --status]
payment get <id>
payment close | reopen | status <id> <action>
payment edit <id> --data <json|@file>
payment create --data <json|@file>
Customers
customer list GET /hl/v2/customers
customer create --data <json|@file>
customer search <email> GET /hl/v2/customers/detail?email=
customer update <fromEmail> <toEmail>
customer magic-link <email> POST /hl/v2/customers/portal-login
Transactions
tx list [--limit --after --status --customerId --type --startAt --endAt]
tx unpaid [--limit --after --status --customerId --startAt --endAt]
tx daily
tx product <productId> [--limit --after --status]
Reviews
review list [--limit --after --status --paymentLinkId --rating]
review stats [productId] merchant-wide or per-product
review create --data <json|@file>
review update <id> --data <json|@file>
review bulk-status --data <json> [{id,status: ACTIVE|ARCHIVED|INACTIVE}, …]
review product <paymentLinkId> [--limit --after --rating]
review product-customer <paymentLinkId> --customerId <id>
Discounts (coupons)
discount create --data <json|@file>
discount get <id>
discount validate <code> <paymentLinkId>
discount check <code>
Bundling
bundling list [--limit --after]
bundling get <id>
Installments
installment list [--limit --after --status --customerId]
installment get <id>
installment create --data <json|@file>
Memberships (see the Memberships guide below)
membership product create --data <json|@file> {name, description, membershipInfo}
membership product get <productId>
membership tier create --data <json|@file> {productId, name, description, periods[]}
membership tier get <tierId> --productId <id>
membership tiers --productId <id> [--limit --after] list every tier of a product
membership register --data <json|@file>
membership members --productId <id> [--limit --after]
membership get <memberId> --productId <id>
membership update <memberId> --productId <id> [--data <json|@file>]
membership cancel <memberId> --productId <id>
membership create-invoice <memberId> --productId <id>
Credit wallets
credit balance --customerId <id> --productId <id> [--tierId <id>]
credit add --data <json|@file> {customerId, productId, amount}
credit spend --data <json|@file> {customerId, productId, amount}
credit history <customerId> --productId <id> [--page N --limit N]
credit register-usage --data <json|@file>
credit register-membership --data <json|@file>
credit checkout --data <json|@file>
SaaS licensing
saas activate <licenseCode> <productId> POST /saas/v2/license/activate
saas deactivate <licenseCode> <productId> POST /saas/v2/license/deactivate
saas verify <licenseCode> <productId> POST /saas/v2/license/verify
Software licensing
software verify <licenseCode> <productId> POST /software/v2/license/verify
QR & payment channels
qrcode <amount> POST /hl/v2/qr-codes/create
qrcode static GET /hl/v2/qr-codes/static
qrcode channels GET /hl/v2/payment-channels
Webhooks
webhook register <url> POST /hl/v2/webhooks/update
webhook test <url>
webhook history [--limit --after --status --type --startAt --endAt]
webhook new-history [--limit --after]
webhook retry <historyId>
Global flags
--json Output raw JSON instead of pretty tables
--api-key <key> Use this API key for the run (also accepts --api-key=KEY)
--limit N Page size (default 10, max 50)
--after CURSOR Pagination cursor (from previous nextStartingAfter)
--pageSize N Alias for --limit
--data <json|@file> Inline JSON, or @path to a JSON file
-h, --help Show help
-v, --version Show version
Environment
MAYAR_API_KEY Used when --api-key is absent and no config is saved
MAYAR_API_URL Override API base URL
MAYAR_AUTH_URL Override auth server base URL (used by 'login')
NODE_ENV=development Target the sandbox (*.mayar.club) instead of production
Examples
mayar whoami
mayar balance
mayar invoice list --limit 20
mayar invoice list --limit 20 --after 1730000000000
mayar product type ebook --limit 50
mayar product search "kelas python"
cat > /tmp/inv.json <<'JSON'
{
"name": "Andre",
"email": "andre@example.com",
"mobile": "08123456789",
"redirectUrl": "https://example.com/thanks",
"description": "Order #1234",
"expiredAt": "2026-12-31T23:59:59.000Z",
"items": [{ "quantity": 1, "rate": 50000, "description": "1x Course" }]
}
JSON
mayar invoice create --data @/tmp/inv.json
mayar customer create --data '{"name":"Raihan","email":"r@example.com","mobile":"081234567890"}'
mayar payment create --data '{"name":"X","email":"x@y.com","amount":170000,"mobile":"08123","redirectUrl":"https://m.com","description":"Test","expiredAt":"2026-12-31T00:00:00.000Z"}'
mayar qrcode 10000
mayar webhook register https://example.com/hooks/mayar
mayar webhook test https://example.com/hooks/mayar
mayar webhook history --limit 20
mayar membership members --productId prd-42 --limit 20
mayar saas verify LIC-123 prd-42
mayar invoice list --json | jq '.data[] | {id, status}'
Memberships
A membership in Mayar is three nested objects. Create them in this order — each level needs the id of the one above it:
membership product the subscription product itself ("Kelas Premium")
└── tier a plan inside it ("Basic", "Pro") — carries the pricing
└── period one billing option of that tier (1 month, 12 months, lifetime)
└── member a customer subscribed to that tier
1. Create the product
mayar membership product create --data '{
"name": "Kelas Premium",
"description": "Akses penuh ke seluruh materi",
"membershipInfo": { "showMembers": true, "type": "MEMBERSHIP" }
}'
Returns the product; keep its id — every command below needs it as --productId.
name | yes | Product name shown to buyers |
description | yes | Product description |
redirectUrl | no | Where the buyer lands after a successful payment |
coverImage | no | Cover image URL |
hidePortalAccessInEmails | no | true to omit the member-portal link from transactional emails |
membershipInfo | yes | Object — see below |
membershipInfo fields:
showMembers | yes | true to list members publicly on the product page |
type | yes | Uppercase enum: MEMBERSHIP, SAAS, or CREDIT. Anything else is rejected |
creditValue | no | Credits granted per billing cycle (CREDIT type) |
enableCreditTopup | no | Let members buy extra credit |
isAccumulateCredit | no | Unused credit rolls over to the next cycle instead of resetting |
isAccumulateTopupCredit | no | Same rollover rule, for topped-up credit |
minCreditTopup | no | Minimum credit per top-up |
maxCreditTopup | no | Maximum credit per top-up |
The creditValue/*Credit* fields only do anything when type is CREDIT — omit them for a plain MEMBERSHIP.
2. Create a tier
A tier holds the prices. periods is the list of billing options a buyer can pick, so a tier with monthly and annual pricing is one tier with two periods — not two tiers.
mayar membership tier create --data '{
"productId": "prd-42",
"name": "Pro",
"description": "Semua materi + sesi live bulanan",
"trialPeriodInDays": 7,
"isTrialAvailable": true,
"periods": [
{ "monthPeriod": 1, "amount": 99000, "status": "ACTIVE" },
{ "monthPeriod": 12, "amount": 990000, "status": "ACTIVE" },
{ "isLifetime": true, "amount": 2500000, "status": "ACTIVE" }
]
}'
productId | yes | Id from step 1 — passed in the body, not as a flag |
name | yes | Tier name |
description | yes | Tier description |
notes | no | Internal note, not shown to buyers |
limit | no | Cap on how many members may join this tier |
upfrontFee | no | One-off joining fee charged on top of the first period |
finishMembershipAt | no | ISO date when the membership ends regardless of billing |
gracePeriodInDays | no | Days of continued access after a missed payment |
trialPeriodInDays | no | Free-trial length |
trialCredit | no | Credits granted during the trial (CREDIT products) |
isTrialAvailable | no | Set true to actually enable the trial |
redirectUrl | no | Post-checkout redirect for this tier |
periods | yes | Array — at least one entry |
Each periods[] entry:
monthPeriod | Billing cycle in months (1 = monthly, 12 = yearly). Omit when isLifetime |
amount | Price in IDR for this cycle |
credit | Credits granted per cycle (CREDIT products) |
isLifetime | true for a one-time payment with no renewal |
status | ACTIVE to sell it, otherwise it stays hidden |
3. Register a member and bill them
mayar membership tiers --productId prd-42
mayar membership register --data '{
"productId": "prd-42",
"membershipTierId": "tier-7",
"name": "Andre",
"email": "andre@example.com",
"mobile": "08123456789"
}'
mayar membership members --productId prd-42 --limit 20
mayar membership get <memberId> --productId prd-42
mayar membership update <memberId> --productId prd-42 --data '{"membershipTierId":"tier-9"}'
mayar membership create-invoice <memberId> --productId prd-42
mayar membership cancel <memberId> --productId prd-42
Reading things back
mayar membership product get prd-42
mayar membership tier get tier-7 --productId prd-42
mayar membership tiers --productId prd-42 --json | jq '.data[] | {id, name, amount}'
Two traps worth remembering: tiers (plural) lists a product's tiers while tier (singular) is the create/get sub-namespace, and tier get needs --productId even though the tier id alone looks sufficient. Every write command takes --data as either inline JSON or @path/to/file.json.
Config
| Path | $XDG_CONFIG_HOME/mayar/config.json, defaulting to ~/.config/mayar/config.json (chmod 600) |
| Endpoint | MAYAR_API_URL env or https://api.mayar.id (default) |
Legacy installs that wrote to ~/.mayar/config.json are migrated automatically on first run.
To rotate keys: mayar config reset && mayar init.
Notes
- All requests use
Authorization: Bearer <key>. Errors print API <status> — <message> and exit non-zero.
--data @file.json reads from disk; --data '{...}' reads inline JSON.
MAYAR_API_URL overrides the base URL — useful for staging or custom proxy environments.
mayar whoami decodes the JWT locally and verifies the key live against /hl/v2/balances.
- v1.0.0 migration: all API paths moved from
/hl/v1/* to /hl/v2/*. --page is no longer used (v2 has no page numbers); the cursor is returned as nextStartingAfter in each response and passed as --after for the next page.