client.vip is the VIP policy surface. A tier sets the base maker and taker percents an account
trades at, and it is held on the root account: every subaccount inherits it, and there is no
per-subaccount VIP.
There are two ways up. Rolling 30-day settled spot volume earns one tier, rolling 30-day average portfolio value (AOP) earns another, and the account gets whichever is higher. The paths are evaluated separately and never added together. Catalog percents are the policy rates, before symbol-specific adjustments; for what an account is actually charged per market, see Fees.
Percents, USD amounts, and rolling metrics are decimal strings with no currency or % sign.
Timestamps are epoch milliseconds.
Methods
| Method | Summary |
|---|---|
listTiers | Public. The whole VIP0–VIP10 catalog: entry thresholds, fee percents, policy version. |
getStatus | Authenticated. The root account's tier, how each path scored, and the next thresholds. |
listTiers(options?)
Fetches the complete active catalog as a VipTierCatalog: 11 rows, VIP0 through VIP10, ordered
by tier ascending. Not paginated, and no credentials are required. The SDK sends it as a cacheable
ConnectRPC GET request. Use this to render a schedule or
to look up the thresholds for a tier you already know.
const catalog = await client.vip.listTiers();
console.log(catalog.policyVersion, new Date(catalog.effectiveFrom));
for (const tier of catalog.tiers) {
const aop = tier.tier === 0 ? "n/a" : tier.aopThresholdUsd;
console.log(tier.tier, tier.volumeThresholdUsd, aop, tier.makerFeeRatePercent);
}VipTier is a union. VIP0 is the entry tier and has no AOP threshold at all, so aopThresholdUsd exists on VIP1 and up only. Narrow with tier.tier !== 0 before reading it, or TypeScript will
reject the access.
volumeThresholdUsd is the rolling 30-day settled spot volume needed to enter the tier. retentionThresholdBp sits on the catalog rather than the row: it is the average-portfolio-value
retention threshold shared by every tier, in basis points (1 through 10000, where one basis point
is 0.01%).
VipTierCatalog
interface VipTierCatalog {
policyVersion: string;
effectiveFrom: number; // epoch ms
retentionThresholdBp: number; // 1-10000
tiers: VipTier[];
}
type VipTier =
| {
tier: 0;
volumeThresholdUsd: string;
makerFeeRatePercent: string; // -100 to 100; negative is a rebate
takerFeeRatePercent: string; // 0 to 100
}
| {
tier: number; // integer >= 1
volumeThresholdUsd: string;
aopThresholdUsd: string; // VIP1 and up only
makerFeeRatePercent: string;
takerFeeRatePercent: string;
};getStatus(options?)
Fetches VipStatus for the authenticated caller's root account: the applied tier (0 through 10),
what each qualification path scored on its own, the rolling metrics behind them, and what the next
tier takes.
account field: getStatus always reads the root account, and switching the active
subaccount does not change the result.const status = await client.vip.getStatus();
console.log(status.tier, status.volumeTier, status.aopTier);
console.log(status.settledVolume30dUsd, status.averageAop30dUsd);
if (status.nextTierThresholds) {
console.log(
status.nextTierThresholds.tier,
status.nextTierThresholds.volumeThresholdUsd,
status.nextTierThresholds.aopThresholdUsd
);
}tier is what the account trades at. volumeTier and aopTier say which path got it there, which
is the difference between telling someone to trade more and telling them to hold more. nextTierThresholds is omitted at VIP10, where there is nothing above.
Treat the optional fields as "not reported," never as zero. settledVolume30dUsd and averageAop30dUsd depend on their metric quality, below. metricsAsOf (the UTC cutoff of the
rolling window) is absent when neither is present, and effectiveFrom / evaluatedAt are absent
while a new account is still on the default VIP0 assignment instead of an evaluated one.
settledVolumeQuality and averageAopQuality say how complete each rolling metric is, as { status, incompleteHours }. incompleteHours counts the hourly buckets of the 720-hour window
that are missing or incomplete, and is 0 when status is "complete". Both are absent when the
assignment carries no qualification evidence, such as a new account's default VIP0.
status | Meaning |
|---|---|
"complete" | Every hour in the window has complete evidence. |
"price_unavailable" | Every hour is present, but some executions or holdings had no USD price. Those are left out, not estimated. |
"incomplete_history" | Some hours in the window are missing or incomplete. |
With "price_unavailable", settledVolume30dUsd is a lower bound. It is absent with "incomplete_history". averageAop30dUsd is present only when averageAopQuality.status is "complete". An incomplete volume metric can raise the volume tier when its lower bound meets a
higher threshold but never lowers it; an incomplete AOP metric keeps the previous AOP tier.
if (status.settledVolumeQuality?.status === "price_unavailable") {
console.log(`at least ${status.settledVolume30dUsd} USD`);
}VipStatus
interface VipStatus {
tier: number; // applied qualification
volumeTier: number; // volume-path tier
aopTier: number; // equity-path tier
settledVolume30dUsd?: string;
averageAop30dUsd?: string;
policyVersion: string;
policyEffectiveFrom: number; // epoch ms
effectiveFrom?: number;
evaluatedAt?: number;
metricsAsOf?: number;
nextTierThresholds?: NextVipTierThresholds;
settledVolumeQuality?: VipMetricQuality;
averageAopQuality?: VipMetricQuality;
}
interface VipMetricQuality {
status: "complete" | "price_unavailable" | "incomplete_history" | "unspecified";
incompleteHours: number;
}
interface NextVipTierThresholds {
tier: number; // integer >= 1
volumeThresholdUsd: string;
aopThresholdUsd: string;
}Related
- Fees to read the effective spot rates for an account.
- Profile for the
vipTierfield on the public profile. - VIP tiers for the product-facing qualification write-up.