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
| Tool | Description |
|---|---|
| get_index_value | Current ZHVI value, YoY change, as-of month, and recent series for one geo node (state:TX, county:48201, city:tx-austin, zip:11215). |
| list_markets | List/filter ZipToken markets by zip, level, status, metric type, or metro area (paginated). |
| get_market | Full detail for one market by id (db UUID or frontend id like mkt-002). |
| get_resolution | Settlement outcome for a market: the Zillow first print it settled on, YES/NO outcome, dispute-window state. |
| search_geo | Free-text search over the geo hierarchy; returns canonical node ids for use in the other tools. |
| size_hedge | Pure 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
| Var | Default | Notes |
|---|---|---|
| ZIPTOKEN_API_URL | https://zip-token-production.up.railway.app | Base 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):
{
"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
# 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
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:TXGlobal flags / environment variables
| Var | Flag | Default | Notes |
|---|---|---|---|
| ZIPTOKEN_API_URL | --api-url <url> | https://zip-token-production.up.railway.app | Base 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.
$ ziptoken index state:TX
Node: state:TX
Value: 312,400
YoY: 4.85%
As of: 2026-05
Source: zhvi_first_print
Data from Zillowziptoken markets list [--level] [--state] [--zip] [--status] [--limit]
List/filter markets, paginated.
$ 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.
$ 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: 0ziptoken resolution <marketId>
Structured resolution status: settlement terms, outcome, dispute-window state.
$ 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.
$ ziptoken geo search austin --level city
ID LEVEL NAME STATE
-------------- ----- ------ -----
city:tx-austin city Austin TXziptoken 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.
$ 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
npm install @ziptoken/sdkThe SDK requires Node.js 18+ and TypeScript 5.0+.
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.
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.
const record = await kyc.createInquiry('enhanced');
console.log(record.status); // "pending"getStatus
getStatus(): Promise<KYCRecord>Get the current KYC verification status and limits.
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.
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.
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.
const wallets = await custody.listWallets('anchorage');getWallet
getWallet(walletId: string): Promise<CustodyWallet>Get a specific wallet by ID.
const wallet = await custody.getWallet('wallet_abc123');getBalance
getBalance(walletId: string): Promise<CustodyBalance>Get wallet balance and available balance.
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.
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.
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.
const key = await trading.createApiKey(
'Bot v2',
['read:markets', 'create:orders', 'cancel:orders'],
300 // 300 req/min
);
// Store key.secret securelylistApiKeys
listApiKeys(): Promise<ApiKey[]>List all API keys (secrets are not included).
const keys = await trading.listApiKeys();revokeApiKey
revokeApiKey(keyId: string): Promise<{ success: boolean }>Permanently revoke an API key.
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.
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.
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.
await trading.cancelOrder('order_xyz');cancelAllOrders
cancelAllOrders(marketId?: string): Promise<{ cancelledCount: number }>Cancel all open orders. Optionally filter by market.
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.
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.
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.
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.
const logs = await audit.exportLogs(startTime, endTime);contractBuilder
Build custom prediction market contracts.
getTemplates
getTemplates(): Promise<ContractTemplate[]>List all available contract templates.
const templates = await contractBuilder.getTemplates();getTemplate
getTemplate(id: string): Promise<ContractTemplate>Get a specific template by ID.
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.
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.
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.
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.
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.
const tiers = await liquidity.getTiers();getTier
getTier(tier: string): Promise<LiquidityTier>Get a specific liquidity tier.
const tier = await liquidity.getTier('institutional');getRequirements
getRequirements(metricType: string): Promise<Record<string, unknown>>Get liquidity requirements for a specific metric type.
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.
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.
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.
const est = await liquidity.getFeeEstimate(1000, 'standard');
console.log(est.feePercent); // "1.00%"getAlerts
getAlerts(limit?: number): Promise<Alert[]>Get liquidity and margin alerts.
const alerts = await liquidity.getAlerts(10);Type Reference
All types are exported from the SDK package and available for import.
import type { KYCRecord, TradingOrder, MarginHealth } from '@ziptoken/sdk';// 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.
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.