SkinShark stores every amount as bigint cents internally. On the wire two
shapes appear, depending on whether you’re reading or writing.
Reading
| Field suffix | Type | Example | Meaning |
|---|
Cents | number | 1500 | Integer cents as a JSON number. Safe through ~$90T (2^53 cents). |
no suffix (balance, gmv, totalPrice) | number | 15.00 | Decimal 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
}
| Endpoint | Field | Example |
|---|
POST /merchant/users/{id}/fund | amount | "25.00" |
POST /market/buy (items[].maxPrice) | maxPrice | "1.50" |
POST /market/buy/quick | maxPrice | "1.50" |
POST /user/wallet/deposit/gate/quote | amount | "100.00" |
POST /user/wallet/deposit/onramp/quote | payAmount / receiveAmount | 100.00 (number, fine for on-ramp) |
POST /user/wallet/deposit/crypto/quote | amount | 100.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.