Command-line interface for the Mayar API. Zero runtime dependencies — Node.js 18+ stdlib only.
One-liner (no npm required):
curl -fsSL https://raw.githubusercontent.com/mayarid/mayar-cli/main/install.sh | shDownloads 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 | shVia npm:
npm install -g mayar
# or run without installing:
npx mayar --helpFrom source:
git clone https://github.com/mayarid/mayar-cli.git
cd mayar-cli
npm link # exposes a `mayar` command on your PATHPreferred — browser login:
mayar loginOpens 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 whoamiOption 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
# Welcome to Mayar CLI.
# No API key found. Get yours from https://web.mayar.id → Integration → API Key.
# Paste your production API key: ************
# ✓ Saved to /Users/you/.config/mayar/config.jsonOption 3 — non-interactive setup:
mayar api-key YOUR_KEY_HEREOption 4 — per-command override:
mayar --api-key YOUR_KEY_HERE balanceGet 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/--pageSizeto cursor-based--limit/--after(returned in the previous response'snextStartingAfter). The--pageSizeflag is still accepted as an alias for--limit.
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
# Verify active user / API key
mayar whoami
# Account balance
mayar balance
# Paginated lists (v2 cursor pagination)
mayar invoice list --limit 20
mayar invoice list --limit 20 --after 1730000000000 # next page
mayar product type ebook --limit 50
# Search
mayar product search "kelas python"
# Create an invoice from a JSON file
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
# Create a customer inline
mayar customer create --data '{"name":"Raihan","email":"r@example.com","mobile":"081234567890"}'
# Create a payment request
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"}'
# Dynamic QR for IDR 10,000
mayar qrcode 10000
# Webhooks
mayar webhook register https://example.com/hooks/mayar
mayar webhook test https://example.com/hooks/mayar
mayar webhook history --limit 20
# Membership members
mayar membership members --productId prd-42 --limit 20
# SaaS license verify
mayar saas verify LIC-123 prd-42
# Pipe raw JSON to jq
mayar invoice list --json | jq '.data[] | {id, status}'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
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.
| Field | Required | Notes |
|---|---|---|
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:
| Field | Required | Notes |
|---|---|---|
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.
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" }
]
}'| Field | Required | Notes |
|---|---|---|
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:
| Field | Notes |
|---|---|
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 |
# Who can they subscribe to?
mayar membership tiers --productId prd-42
# Subscribe a customer to a tier — the body identifies the product, the tier, and the buyer.
# Run `mayar docs membership` for the authoritative member field list.
mayar membership register --data '{
"productId": "prd-42",
"membershipTierId": "tier-7",
"name": "Andre",
"email": "andre@example.com",
"mobile": "08123456789"
}'
# Then, per member
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 # bill the next cycle now
mayar membership cancel <memberId> --productId prd-42mayar membership product get prd-42
mayar membership tier get tier-7 --productId prd-42 # --productId is required here
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.
| Key | Value |
|---|---|
| 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.
- All requests use
Authorization: Bearer <key>. Errors printAPI <status> — <message>and exit non-zero. --data @file.jsonreads from disk;--data '{...}'reads inline JSON.MAYAR_API_URLoverrides the base URL — useful for staging or custom proxy environments.mayar whoamidecodes 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/*.--pageis no longer used (v2 has no page numbers); the cursor is returned asnextStartingAfterin each response and passed as--afterfor the next page.