Skip to content

Repository files navigation

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
# or run without installing:
npx mayar --help

From source:

git clone https://github.com/mayarid/mayar-cli.git
cd mayar-cli
npm link        # exposes a `mayar` command on your PATH

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
# 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.json

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

# 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}'

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.

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.

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" }
  ]
}'
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

3. Register a member and bill them

# 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-42

Reading things back

mayar 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.

Config

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.

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.

About

Zero-dependency Node.js CLI for the Mayar API (production endpoint).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages