Skip to main content
SkinShark stores every amount as bigint cents internally. On the wire two shapes appear, depending on whether you’re reading or writing.

Reading

Field suffixTypeExampleMeaning
Centsnumber1500Integer cents as a JSON number. Safe through ~$90T (2^53 cents).
no suffix (balance, gmv, totalPrice)number15.00Decimal in the wallet’s currency, two decimal places.
Both shapes are present on most endpoints. Pick whichever matches your storage:
interface MerchantStats {
  totals: {
    gmvCents: number;       // 1234500 — exact integer cents
    gmv: number;            // 12345.00 — decimal number
    feesEarnedCents: number;
    feesEarned: number;
  };
}
For accounting and ledger math, work with *Cents integers — exact in JS up to 2^53. For UI display, the decimal field is fine.
const stats = await api<MerchantStats>("/merchant/stats");

const gmvCents = stats.totals.gmvCents;        // exact integer
const display = stats.totals.gmv.toFixed(2);   // "12345.00" for the UI

Writing

Endpoints that accept money take decimal strings:
{
  "amount": "5.50"   // ✅ string, two decimals max
}
EndpointFieldExample
POST /merchant/users/{id}/fundamount"25.00"
POST /market/buy (items[].maxPrice)maxPrice"1.50"
POST /market/buy/quickmaxPrice"1.50"
POST /user/wallet/deposit/gate/quoteamount"100.00"
POST /user/wallet/deposit/onramp/quotepayAmount / receiveAmount100.00 (number, fine for on-ramp)
POST /user/wallet/deposit/crypto/quoteamount100.00 (number, ≤ $10M)
The decimal-string format ("25.00") avoids floating-point loss on the wire. The schema rejects more than two decimal places.
// ✅
fund("user_42", "25.00");

// ✅ — integer is fine
fund("user_42", "25");

// ❌ — three decimals: 422 VALIDATION_FAILED
fund("user_42", "25.005");

// ❌ — float: lossy, but the schema accepts strings only
fund("user_42", String(25.50));  // works, but be careful

A safe converter

Convert from cents to decimal string without Number / 100:
function centsToDecimal(cents: number | bigint, decimals = 2): string {
  const c = BigInt(cents);
  const negative = c < 0n;
  const abs = negative ? -c : c;
  const divisor = 10n ** BigInt(decimals);
  const whole = abs / divisor;
  const fraction = (abs % divisor).toString().padStart(decimals, "0");
  return `${negative ? "-" : ""}${whole}.${fraction}`;
}

centsToDecimal(2550);      // "25.50"
centsToDecimal(99999999);  // "999999.99" — exact
The reverse — decimal string to cents — is just Number(decimal.replace(".", "")) after padding.

Currency

Every wallet has exactly one currency: USD or EUR. Merchant accounts are USD-only — attempting to set them to anything else returns MERCHANT_MUST_BE_USD (1521). Sub-users default to USD on creation; pass currency: "EUR" on POST /merchant/users to override per sub-user. Funding a sub-user from the merchant spot wallet requires both wallets to share a currency. EUR sub-users must be funded via deposit, not internal transfer:
{
  "code": 1520,
  "key": "TRANSFER_CURRENCY_MISMATCH",
  "message": "Merchant and sub-user currencies do not match"
}
Cross-currency transfers go through the deposit flow (deposit fiat in the sub-user’s currency) rather than direct fund transfers.