MisarMisar Docs
MisarMailMisarBlogMisarReachMisarPostMisarDevMisarCoderMisarSEOMisar PlatformMisar SSO
Universal Wallet

Grant Credits

POST /io/wallet/grant — mint non-payment credit (plan perks, promos) idempotently. Service-key only, fail-closed.

Grants a non-payment credit bonus to a wallet — a subscription plan's monthly wallet_credit_monthly perk, a promotional bonus, a support credit. Unlike a top-up, no money changes hands: the credits are minted directly into the ledger.

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

Service key only — never expose this to end users

This endpoint mints free credit. It requires x-wallet-service-key and rejects SSO bearer tokens outright. There is no user-facing auth mode.

Authentication

x-wallet-service-key: <WALLET_SERVICE_KEY>
Content-Type: application/json
POST/io/wallet/grant

Mints credits into the wallet of user_id, keyed on idempotency_key so a retried webhook or a duplicate call for the same billing period never double-grants.

Request body

user_idstringbodyrequired

The Misar SSO user id to credit. Must be a valid UUID.

creditsnumberbodyrequired

How many credits to mint. Must be a finite number greater than 0. Rounded to 4 decimal places server-side.

idempotency_keystringbodyrequired

A stable key that identifies this grant. Re-sending the same key returns the original outcome with idempotent: true instead of granting again. Use something period-scoped, e.g. plan-perk:mail-pro:2026-08.

productstringbody

Optional product slug recorded on the ledger entry (mail, blog, reach, …). Defaults to null.

Response fields

successboolean

true only when credits were minted (or the grant was already applied under this idempotency_key). Anything else is false.

balancenumber

The wallet balance after the grant. Omitted when the grant did not run.

idempotentboolean

true when this idempotency_key had already been applied and no new credit was minted.

Request
curl -X POST "https://api.misar.io/io/wallet/grant" \
  -H "x-wallet-service-key: $WALLET_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "3f1a2b4c-5d6e-4f70-8a91-b2c3d4e5f607",
    "credits": 25,
    "idempotency_key": "plan-perk:mail-pro:2026-08",
    "product": "mail"
  }'
200 — Granted
{
  "success": true,
  "balance": 67,
  "idempotent": false
}
200 — Already granted
{
  "success": true,
  "balance": 67,
  "idempotent": true
}
200 — Fail-closed
{
  "success": false
}

Status codes

CodeMeaning
200The grant ran. Read success — a 200 with success: false means the ledger call failed.
400Malformed JSON, missing or non-UUID user_id, credits not a positive finite number, or empty idempotency_key.
401Missing or invalid x-wallet-service-key.
429Wallet write rate limit exceeded (50 write ops/user/60 s).

Rate limit

50 write ops per user per 60 s, shared with the other wallet write endpoints. Over the limit returns 429 with { "success": false }.

Fail-closed behaviour

A failure never silently succeeds

If the ledger RPC errors or returns a malformed row, the response is 200 with { "success": false } — deliberately, so a caller cannot mistake an outage for a completed grant. Retry with the same idempotency_key; the ledger will apply the grant at most once regardless of how many times you retry.

Grant vs. top-up vs. earn

EndpointMoney involvedTypical caller
/grantNone — credit is mintedBilling webhook applying a plan perk
/topup-sessionYes — Stripe CheckoutThe user's own browser
/earnNone — credit is earned from platform activityProduct backend recording a payout-eligible action