ZipToken

SDK & Agent Tools

Programmatic access for agents and scripts: the @ziptoken/mcp MCP server and @ziptoken/cli command-line tool cover ZipToken's public read-only data plane today. The TypeScript trading SDK further down this page is a separate, institutional surface (KYC, custody, order placement) -- see the note before Installation.

@ziptoken/mcp

An MCP (Model Context Protocol) stdio server that exposes ZipToken's public, read-only data plane to AI agents: index values (Zillow ZHVI first prints), prediction markets, resolutions, geo search, and a local hedge-sizing calculator. It is a thin tool layer over the same REST API documented on the API Documentation page -- point it at ZIPTOKEN_API_URL and it works from anywhere. v1 ships stdio transport only; remote/HTTP transport is a documented follow-up.

Zillow attribution

Every response that surfaces an index value carries attribution: "Data from Zillow". The get_index_value tool always prints this line -- it is a Zillow data-license requirement, not cosmetic, and should not be dropped when relaying results to a user.

Tools

ToolDescription
get_index_valueCurrent ZHVI value, YoY change, as-of month, and recent series for one geo node (state:TX, county:48201, city:tx-austin, zip:11215).
list_marketsList/filter ZipToken markets by zip, level, status, metric type, or metro area (paginated).
get_marketFull detail for one market by id (db UUID or frontend id like mkt-002).
get_resolutionSettlement outcome for a market: the Zillow first print it settled on, YES/NO outcome, dispute-window state.
search_geoFree-text search over the geo hierarchy; returns canonical node ids for use in the other tools.
size_hedgePure local calculation -- sizes a strike-ladder hedge per the hedge-sizing methodology. No network call; caller supplies live strike prices (e.g. from list_markets).

Environment variables

VarDefaultNotes
ZIPTOKEN_API_URLhttps://zip-token-production.up.railway.appBase URL of the ZipToken REST API.
ZIPTOKEN_API_KEY(none)Sent as x-api-key. Optional -- read routes work unauthenticated; a key raises the rate limit.

Claude Desktop configuration

Add an entry to claude_desktop_config.json (Settings > Developer > Edit Config):

json
{
  "mcpServers": {
    "ziptoken": {
      "command": "node",
      "args": ["/absolute/path/to/zip_token/packages/mcp/dist/index.js"],
      "env": {
        "ZIPTOKEN_API_URL": "https://zip-token-production.up.railway.app",
        "ZIPTOKEN_API_KEY": ""
      }
    }
  }
}

Omit ZIPTOKEN_API_KEY entirely (or leave it blank) if you don't have one yet -- read endpoints work without it. Any other MCP stdio client launches the server the same way: run node dist/index.js as a child process and set the two env vars.

Install & build

bash
# From the repo root -- part of the ZipToken npm workspace monorepo
npm install
npm run build -w packages/mcp   # -> packages/mcp/dist/index.js

@ziptoken/cli

A command-line interface over the same read-only public API the MCP server exposes to agents -- for humans and scripts. Binary name: ziptoken. Index values, markets, resolutions, geo search, plus the local hedge-sizing calculator.

Install & build

bash
npm install
npm run build -w packages/cli   # -> packages/cli/dist/cli.js

# link the `ziptoken` binary for the workspace
npm link -w packages/cli
ziptoken index state:TX

Global flags / environment variables

VarFlagDefaultNotes
ZIPTOKEN_API_URL--api-url <url>https://zip-token-production.up.railway.appBase URL of the ZipToken REST API.
ZIPTOKEN_API_KEY--api-key <key>(none)Sent as x-api-key. Optional -- read routes work unauthenticated; a key raises the rate limit.

Global flags override env vars and must come before the subcommand, e.g. ziptoken --api-url http://localhost:4000 index state:TX.

ziptoken index <nodeId>

Current ZHVI value, YoY change, and as-of month for a geo node.

bash
$ ziptoken index state:TX
Node:      state:TX
Value:     312,400
YoY:       4.85%
As of:     2026-05
Source:    zhvi_first_print

Data from Zillow

ziptoken markets list [--level] [--state] [--zip] [--status] [--limit]

List/filter markets, paginated.

bash
$ ziptoken markets list --level county --state FL --limit 3
ID       GEO             DESCRIPTION                               STATUS    PRICES
-------  --------------  ----------------------------------------  --------  -----------------------
mkt-006  county:12086    Miami-Dade County ZHVI YoY below 3%        ACTIVE    YES 0.34 / NO 0.66
mkt-011  county:12057    Hillsborough County ZHVI level Q3 2026     ACTIVE    YES 0.51 / NO 0.49
mkt-014  county:12099    Palm Beach County ZHVI YoY below 5%        RESOLVED  YES 1.00 / NO 0.00

Page 1 of 4 (total 11, hasMore=true)

ziptoken markets get <id>

Full detail for one market by db id or frontend id.

bash
$ ziptoken markets get mkt-002
ID:          mkt-002
On-chain id: 0xabc123...
Geo node:    zip:10001
Type:        BINARY
Status:      FINALIZED
Metric:      median_sale_price
Threshold:   1300000
Resolves:    2026-06-30T00:00:00.000Z
Description: Will the ZHVI level for zip 10001 be at or above $1,300,000 in June 2026?

Outcomes:
  [0] YES: 1
  [1] NO: 0

ziptoken resolution <marketId>

Structured resolution status: settlement terms, outcome, dispute-window state.

bash
$ ziptoken resolution mkt-002
{
  "marketId": "mkt-002",
  "status": "FINALIZED",
  "outcome": "YES",
  "settledOnObservation": { "month": "2026-06", "value": 1341800 },
  "finalizedAt": "2026-07-03T06:00:00.000Z"
}

ziptoken geo search <query> [--level] [--state]

Free-text search over the geo hierarchy; returns canonical node ids.

bash
$ ziptoken geo search austin --level city
ID              LEVEL  NAME    STATE
--------------  -----  ------  -----
city:tx-austin  city   Austin  TX

ziptoken hedge size --exposure --class --downside --fraction --price [--strikes] [--attach] [--beta-case]

Sizes a strike-ladder hedge per docs/research/hedge-sizing-methodology.md (proxy hedge ratios by asset class, digital-contract math, ladder weighting). Pure local calculation -- no network call; you supply live strike prices yourself (e.g. copied from ziptoken markets list). Asset classes: single-family, condo, sfr-btr, land, multifamily-value, multifamily-noi, retail, office, industrial.

bash
$ ziptoken hedge size --exposure 10000000 --class single-family --downside 0.10 --fraction 0.5 --price 0.14
Asset class: single_family  (beta_class=1, beta_geo=1, h_total=1.0000)
Hedge fraction: requested 50.0% -> capped 50.0%
Coverage target (C): $500,000

#  STRIKE  PRICE    PRICE(EFF)  WEIGHT  COVERAGE  CONTRACTS  PREMIUM
-  ------  -------  ----------  ------  --------  ---------  --------
1  -10.0%  $0.1400  $0.1414     100.0%  $500,000    582,344  $82,343

Total contracts:  582,344
Total premium:    $82,343  (0.82% of exposure)
Expected variance removed: ~43.8% (HE_mid=0.875 x F_capped=0.50)

With no --strikes given and exactly one --price, the single strike defaults to --downside. --attach defaults to 0 if omitted; --beta-case (low/base/high) picks the β_class column when no override is given.

Development: npm run build -w packages/cli (tsc), npm run test -w packages/cli (vitest -- format helpers + hedge-arg parsing), npm run lint -w packages/cli (tsc --noEmit).

About the sections below

The rest of this page documents @ziptoken/sdk, a planned TypeScript client for the institutional Trading API (KYC, custody, order placement, audit). It targets a different consumer (KYC'd trading accounts) than the MCP server and CLI above, which are for read-only agentic/programmatic access and are what has actually shipped for that use case today.

Installation

bash
npm install @ziptoken/sdk

The SDK requires Node.js 18+ and TypeScript 5.0+.

typescript
import { kyc, custody, trading, audit, contractBuilder, liquidity } from '@ziptoken/sdk';

Authentication

Configure the SDK with your API key before making requests. You can also set the base URL for development.

typescript
import { configure } from '@ziptoken/sdk';

configure({
  apiKey: process.env.ZIPTOKEN_API_KEY,
  baseUrl: 'https://api.ziptoken.io',  // optional, this is the default
});

Security

Never expose your API key in client-side code. Use environment variables and server-side calls only. The SDK is designed for Node.js server environments.

kyc

Identity verification and compliance.

createInquiry

createInquiry(tier: 'basic' | 'enhanced' | 'institutional'): Promise<KYCRecord>

Start a KYC verification process for the specified tier.

typescript
const record = await kyc.createInquiry('enhanced');
console.log(record.status); // "pending"

getStatus

getStatus(): Promise<KYCRecord>

Get the current KYC verification status and limits.

typescript
const status = await kyc.getStatus();
if (status.status === 'approved') {
  console.log('Verified at tier:', status.tier);
}

getLimits

getLimits(): Promise<KYCLimits>

Get the position and daily trading limits for your current tier.

typescript
const limits = await kyc.getLimits();
console.log('Max daily:', limits.maxDaily);

custody

Institutional wallet management via BitGo or Anchorage.

createWallet

createWallet(provider: 'bitgo' | 'anchorage', label: string, coin?: string): Promise<CustodyWallet>

Create a new custody wallet.

typescript
const wallet = await custody.createWallet('bitgo', 'Main Trading');
console.log(wallet.address);

listWallets

listWallets(provider?: string): Promise<CustodyWallet[]>

List all custody wallets, optionally filtered by provider.

typescript
const wallets = await custody.listWallets('anchorage');

getWallet

getWallet(walletId: string): Promise<CustodyWallet>

Get a specific wallet by ID.

typescript
const wallet = await custody.getWallet('wallet_abc123');

getBalance

getBalance(walletId: string): Promise<CustodyBalance>

Get wallet balance and available balance.

typescript
const balance = await custody.getBalance('wallet_abc123');
console.log(balance.availableBalance);

initiateTransfer

initiateTransfer(walletId: string, destinationAddress: string, amount: string, coin?: string, memo?: string): Promise<CustodyTransaction>

Initiate a transfer from a custody wallet. Requires multi-sig approval.

typescript
const tx = await custody.initiateTransfer(
  'wallet_abc123',
  '0xdest...addr',
  '5000.00'
);
console.log(tx.status); // "pending_approval"

listTransactions

listTransactions(walletId: string, limit?: number, status?: string): Promise<CustodyTransaction[]>

List transactions for a wallet with optional filters.

typescript
const txs = await custody.listTransactions('wallet_abc123', 20, 'confirmed');

trading

API key management and order placement.

createApiKey

createApiKey(label: string, permissions: string[], rateLimit?: number, expiresInDays?: number): Promise<ApiKey & { secret: string }>

Create a new API key. The secret is returned only once.

typescript
const key = await trading.createApiKey(
  'Bot v2',
  ['read:markets', 'create:orders', 'cancel:orders'],
  300  // 300 req/min
);
// Store key.secret securely

listApiKeys

listApiKeys(): Promise<ApiKey[]>

List all API keys (secrets are not included).

typescript
const keys = await trading.listApiKeys();

revokeApiKey

revokeApiKey(keyId: string): Promise<{ success: boolean }>

Permanently revoke an API key.

typescript
await trading.revokeApiKey('key_abc123');

placeOrder

placeOrder(marketId: string, outcomeIndex: number, side: 'BUY' | 'SELL', amount: string, price: number, expiry?: number): Promise<TradingOrder>

Place a limit order on a prediction market.

typescript
const order = await trading.placeOrder(
  'mkt_10025_price',
  0,       // YES outcome
  'BUY',
  '500.00',
  0.62     // limit price
);

placeBatchOrders

placeBatchOrders(orders: OrderInput[]): Promise<TradingOrder[]>

Place multiple orders atomically. All succeed or all fail.

typescript
const orders = await trading.placeBatchOrders([
  { marketId: 'mkt_10025_price', outcomeIndex: 0, side: 'BUY', amount: '200', price: 0.60 },
  { marketId: 'mkt_10036_price', outcomeIndex: 1, side: 'BUY', amount: '300', price: 0.45 },
]);

cancelOrder

cancelOrder(orderId: string): Promise<{ success: boolean }>

Cancel a single open order.

typescript
await trading.cancelOrder('order_xyz');

cancelAllOrders

cancelAllOrders(marketId?: string): Promise<{ cancelledCount: number }>

Cancel all open orders. Optionally filter by market.

typescript
const result = await trading.cancelAllOrders();
console.log('Cancelled:', result.cancelledCount);

audit

SOC 2 compliant audit trail.

getLogs

getLogs(params?: { userId?: string; action?: string; severity?: string; limit?: number; offset?: number }): Promise<AuditEntry[]>

Query audit logs with optional filters.

typescript
const logs = await audit.getLogs({
  severity: 'critical',
  limit: 100
});

getStats

getStats(startTime?: number, endTime?: number): Promise<AuditStats>

Get aggregated audit statistics over a time range.

typescript
const stats = await audit.getStats(
  Date.now() / 1000 - 86400 * 7  // last 7 days
);

verifyIntegrity

verifyIntegrity(): Promise<AuditIntegrity>

Verify the hash chain integrity of the entire audit log.

typescript
const result = await audit.verifyIntegrity();
if (!result.valid) {
  console.error('Chain broken at entry:', result.brokenAt);
}

exportLogs

exportLogs(startTime?: number, endTime?: number): Promise<AuditEntry[]>

Export audit logs for compliance reporting.

typescript
const logs = await audit.exportLogs(startTime, endTime);

contractBuilder

Build custom prediction market contracts.

getTemplates

getTemplates(): Promise<ContractTemplate[]>

List all available contract templates.

typescript
const templates = await contractBuilder.getTemplates();

getTemplate

getTemplate(id: string): Promise<ContractTemplate>

Get a specific template by ID.

typescript
const tpl = await contractBuilder.getTemplate('tpl_single_zip');

buildMarket

buildMarket(zipCode: string, metricType: string, threshold: number, resolutionTimestamp: number): Promise<MarketSpec>

Build a single zip code prediction market.

typescript
const market = await contractBuilder.buildMarket(
  '10025', 'zhvi', 1200000, 1735689600
);
console.log(market.question);

buildBasket

buildBasket(name: string, zipCodes: string[], metricType: string, threshold: number, resolutionTimestamp: number, weights?: number[]): Promise<BasketSpec>

Build a weighted basket market across multiple zip codes.

typescript
const basket = await contractBuilder.buildBasket(
  'Manhattan Core',
  ['10001', '10002', '10003'],
  'zhvi',
  1000000,
  1735689600,
  [0.4, 0.3, 0.3]
);

buildPortfolioHedge

buildPortfolioHedge(exposures: Exposure[], metricType: string, threshold: number, resolutionTimestamp: number, additionalMetrics?: string[], minBasketSize?: number): Promise<HedgeBundle>

Generate a hedge bundle for portfolio risk management.

typescript
const hedge = await contractBuilder.buildPortfolioHedge(
  [
    { zipCode: '10025', notionalAmount: 500000 },
    { zipCode: '10036', notionalAmount: 300000 },
  ],
  'zhvi',
  1200000,
  1735689600
);
console.log('Total markets:', hedge.totalMarkets);

buildFromTemplate

buildFromTemplate(templateId: string, metricType: string, resolutionTimestamp: number, threshold?: number, exposures?: Exposure[]): Promise<MarketSpec | BasketSpec | HedgeBundle>

Build a market from a predefined template.

typescript
const result = await contractBuilder.buildFromTemplate(
  'tpl_single_zip', 'zhvi', 1735689600, 1200000
);

liquidity

Position limits, margin health, and fee management.

getTiers

getTiers(): Promise<LiquidityTier[]>

List all liquidity tiers and their limits.

typescript
const tiers = await liquidity.getTiers();

getTier

getTier(tier: string): Promise<LiquidityTier>

Get a specific liquidity tier.

typescript
const tier = await liquidity.getTier('institutional');

getRequirements

getRequirements(metricType: string): Promise<Record<string, unknown>>

Get liquidity requirements for a specific metric type.

typescript
const reqs = await liquidity.getRequirements('zhvi');

validatePosition

validatePosition(positionSize: number, marketId: string): Promise<{ valid: boolean; reason?: string }>

Check if a proposed position size is within limits.

typescript
const check = await liquidity.validatePosition(50000, 'mkt_10025_price');
if (!check.valid) {
  console.warn(check.reason);
}

getMarginHealth

getMarginHealth(): Promise<MarginHealth>

Get current account margin health status.

typescript
const health = await liquidity.getMarginHealth();
if (health.healthStatus === 'warning') {
  // Reduce positions or add collateral
}

getFeeEstimate

getFeeEstimate(amount: number, tier: string): Promise<FeeEstimate>

Estimate trading fees for a given amount.

typescript
const est = await liquidity.getFeeEstimate(1000, 'standard');
console.log(est.feePercent); // "1.00%"

getAlerts

getAlerts(limit?: number): Promise<Alert[]>

Get liquidity and margin alerts.

typescript
const alerts = await liquidity.getAlerts(10);

Type Reference

All types are exported from the SDK package and available for import.

typescript
import type { KYCRecord, TradingOrder, MarginHealth } from '@ziptoken/sdk';
typescript
// Core Market Types
type MarketType = 'binary' | 'spread' | 'range'
type MarketCategory = 'price_threshold' | 'volume' | 'growth' | 'comparison'
type MarketStatus = 'active' | 'resolved' | 'pending'

// KYC
type KYCStatus = 'none' | 'pending' | 'approved' | 'rejected' | 'expired'
type KYCTier = 'none' | 'basic' | 'enhanced' | 'institutional'

interface KYCRecord {
  status: KYCStatus
  tier: KYCTier
  sanctionsCleared: boolean
  pepScreened: boolean
  adverseMediaCleared: boolean
  countryCode: string | null
  expiresAt: number | null
  limits: KYCLimits | null
}

interface KYCLimits {
  maxPosition: number
  maxDaily: number
  maxTotal: number
}

// Custody
interface CustodyWallet {
  id: string
  provider: 'bitgo' | 'anchorage'
  label: string
  coin: string
  address: string
  createdAt: number
}

interface CustodyBalance {
  walletId: string
  coin: string
  balance: string
  availableBalance: string
}

interface CustodyTransaction {
  id: string
  walletId: string
  type: 'deposit' | 'withdrawal' | 'transfer'
  amount: string
  coin: string
  destinationAddress: string
  status: 'pending_approval' | 'approved' | 'signed' | 'broadcast' | 'confirmed' | 'failed' | 'cancelled'
  createdAt: number
}

// Trading
type ApiKeyPermission = 'read:markets' | 'read:portfolio' | 'create:orders' | 'cancel:orders' | 'read:oracle' | 'manage:custody'

interface ApiKey {
  id: string
  label: string
  prefix: string
  permissions: ApiKeyPermission[]
  rateLimit: number
  createdAt: number
  lastUsed: number | null
  expiresAt: number | null
}

interface TradingOrder {
  id: string
  maker: string
  marketId: string
  outcomeIndex: number
  side: 'BUY' | 'SELL'
  amount: string
  price: number
  status: 'OPEN' | 'FILLED' | 'PARTIALLY_FILLED' | 'CANCELLED'
  filledAmount: string
  expiry: number
  createdAt: number
}

// Audit
interface AuditEntry {
  id: string
  action: string
  userId: string
  severity: 'info' | 'warning' | 'critical'
  resource: string
  details: Record<string, unknown>
  success: boolean
  timestamp: number
  hash: string
  previousHash: string
}

interface AuditStats {
  totalEvents: number
  byAction: Record<string, number>
  bySeverity: Record<string, number>
  timeRange: { start: number; end: number }
}

interface AuditIntegrity {
  valid: boolean
  totalEntries: number
  checkedAt: number
  brokenAt?: number
}

// Contract Builder
interface ContractTemplate {
  id: string
  name: string
  description: string
  category: string
  primaryUsers: string[]
}

interface MarketSpec {
  zipCode: string
  metricType: string
  threshold: number
  resolutionTimestamp: number
  question: string
  resolutionSource: string
}

interface BasketSpec {
  name: string
  zipCodes: string[]
  weights: number[]
  metricType: string
  threshold: number
  resolutionTimestamp: number
  markets: MarketSpec[]
}

interface HedgeBundle {
  exposures: Array<{ zipCode: string; notionalAmount: number }>
  baskets: BasketSpec[]
  individualMarkets: MarketSpec[]
  totalMarkets: number
}

// Liquidity
interface LiquidityTier {
  tier: string
  maxPositionSize: number
  maxDailyVolume: number
  maxOpenPositions: number
  marginRequirement: number
  tradingFee: number
}

interface MarginHealth {
  collateralBalance: string
  totalExposure: string
  marginHealth: number
  frozen: boolean
  healthStatus: 'healthy' | 'warning' | 'critical' | 'liquidation'
}

interface FeeEstimate {
  fee: number
  netAmount: number
  amount: number
  tier: string
  feePercent: string
}

Error Handling

All SDK methods throw on non-2xx responses. Wrap calls in try/catch for proper error handling.

typescript
import { trading } from '@ziptoken/sdk';

try {
  const order = await trading.placeOrder(
    'mkt_10025_price', 0, 'BUY', '100.00', 0.65
  );
  console.log('Order placed:', order.id);
} catch (error) {
  if (error instanceof Error) {
    console.error('API error:', error.message);

    // Common error messages:
    // "Insufficient balance"
    // "Market is closed"
    // "Position exceeds limit"
    // "Invalid price range"
    // "Rate limit exceeded"
  }
}

Rate Limiting

The API returns HTTP 429 when rate limits are exceeded. The SDK does not retry automatically. Implement exponential backoff in your application logic.

Network Errors

Network timeouts and connection failures throw standard Error objects. The message will contain the underlying fetch error description.

Validation Errors

Invalid parameters return HTTP 400 with a descriptive error message. Check parameter types and ranges before sending requests.