MisarMisar Docs
MisarMailMisarBlogMisarReachMisarPostMisarSEOMisarDevMisarCoderMisar PlatformMisar SSO
Wallet

Earn (credit earned income)

Credit a user's earned, cash-out-eligible wallet balance. POST /io/wallet/earn — the single server-to-server entry point for creator revenue, referral/affiliate, received P2P transfers, and earned admin grants. 1 credit = $1.

POST /io/wallet/earn credits a user's earned, cash-out-eligible balance. It is the single entry point that raises withdrawable_balance (alongside the spendable balance), so it is the only way earned income becomes eligible for withdrawal.

POST https://api.misar.io/io/wallet/earn

Server-to-server only — earned income only

This endpoint mints cash-out-eligible credit, so it is locked tighter than /grant: it accepts only a service key (x-wallet-service-key), never an SSO bearer. Use it only for genuinely earned income that arrived on the user's behalf from a third party. Card top-ups, subscription-bundled perks, promo bonuses and migration adjustments must never flow through here — that would open a card-refund → withdraw arbitrage path. Those use /topup-session and /grant, which touch only the spendable balance.

Earned sources

The source field is stored on the ledger row (ledger type earning):

sourceMeaningStatus
creator_revenueMisarBlog author ad / read / citation / audio monetization, settled from revenue_eventsLive (primary mechanism)
referral / affiliateReferral / affiliate commissionsHook point (dormant until a referral program exists)
p2p_transferCredits received from another userHook point (dormant until a P2P send flow exists)
admin_grant / prize_payoutSupport comps / prize payouts explicitly marked earnedLive

Authentication

Service key only. See Authentication.

x-wallet-service-key: <WALLET_SERVICE_KEY>

Request

{
  "user_id": "0b3f…",          // federated SSO subject id
  "credits": 12.34,             // > 0; raises BOTH spendable and withdrawable. 1 credit = $1
  "source": "creator_revenue",  // one of the sources above
  "idempotency_key": "blog-rev-settle:8c1f…"
}

Idempotency is mandatory and must be namespaced

The grant is idempotent on idempotency_key against the global ledger, so a retried settlement or webhook can never double-credit. Keys must be namespaced per source, e.g. blog-rev-settle:<settlement_id>, referral:<id>, p2p:<transfer_id>, admin-earn:<grant_id>. See Idempotency.

Response

{ "success": true, "balance": 112.34, "withdrawable": 12.34, "idempotent": false }
FieldDescription
successtrue when the grant applied (or was an idempotent replay)
balanceSpendable balance after the grant
withdrawableCash-out-eligible balance after the grant
idempotenttrue when the key was already applied (no-op replay)

Fail-closed

Any error (bad auth, bad input, RPC failure) returns { "success": false }. Callers must retry with the same idempotency_key rather than assume success — a transient outage can never silently double-grant.

SDK

import { createWalletClient } from "@misar/wallet";

const wallet = createWalletClient({
  baseUrl: "https://api.misar.io/io/wallet",
  serviceKey: process.env.WALLET_SERVICE_KEY,
});

const res = await wallet.creditEarnings({
  userId,
  credits: 12.34,
  source: "creator_revenue",
  idempotencyKey: `blog-rev-settle:${settlementId}`,
});
// res.success, res.withdrawable, res.idempotent