SDK reference
Client configuration, method signatures, return values and automatic behavior.
Download the official SDK, OpenAPI 3.1 contract, and runnable quickstart. The package is distributed as a tarball here, not yet through the npm registry.
New integration? Follow the quickstart first. This page is for looking up SDK behavior. To implement requests without the SDK, use the HTTP API reference.
For complete chart and order-book loading plus streaming, use
createPolymarketMarketData() from the
browser-safe @crisp-trade/partner-sdk/market-data entry point. It owns the public
HTTP/WebSocket lifecycle without CRISP credentials.
For independent, shared account reads, use
createAccountStore() from
@crisp-trade/partner-sdk/account-state (0.13.0). It exposes cashBalance,
availableCash, and token-scoped availableShares without putting credentials
in the browser or adding a mandatory request before order submission.
Public market discovery
SDK 0.13.0 includes a separate @crisp-trade/partner-sdk/discovery entry point
for categories, hot topics, curated carousels, event feeds and search. These
helpers call Polymarket directly without CRISP signing. Start with
Build the Market Discovery like Polymarket does.
The authenticated CrispClient below continues to handle account and trading operations.
Create a server client
Looking up a specific call? Open the method-by-method SDK reference: arguments, released return types, examples and linked HTTP operations for every client helper. For raw typed calls, the endpoint reference documents each operation's input and response fields.
import { CrispClient } from '@crisp-trade/partner-sdk';
const crisp = new CrispClient({
origin: process.env.CRISP_PARTNER_API_BASE_URL,
keyId: process.env.CRISP_PARTNER_API_KEY,
secret: process.env.CRISP_PARTNER_API_SECRET,
// timeoutMs: 15_000,
// orderRouting: 'direct',
});| Constructor option | Required / default | Meaning |
|---|---|---|
origin | Required | Approved HTTPS origin with no path, query, fragment or URL credentials |
keyId, secret | Required | Your server-only API credentials |
timeoutMs | 15,000 | Per-request deadline; 1–120,000 ms. For order helpers, bounds the entire submission/fallback path. |
orderRouting | 'direct' | 'direct' prefers the execution cell; 'api' uses the standard order endpoint |
fetch | Global fetch | Optional compatible transport, useful for offline tests |
Reuse a client across backend requests for one credential set. Create separate clients for different partners/credentials; never change credentials on a shared client based on browser input. The SDK does not persist your intent records.
Named methods
Every public operation has a typed method with autocomplete for arguments and response fields. Prefer these methods in new integrations:
const funding = await crisp.getFunding(user.id, { details: 'trading' });
const referrals = await crisp.getReferralSummary(user.id, { limit: 20, offset: 0 });
const account = await crisp.createAccount({ externalUserId: user.id }, persistedKey);Arguments follow the same convention: path identifiers first (in URL order),
then the request body if present, required idempotency key, query options if
supported, and finally transport options { signal, traceId }. Not every
operation needs every argument. To pass transport options while omitting an
optional query, use undefined for that query argument:
const result = await crisp.getFunding(user.id, undefined, { signal });Methods are generated from the same contract as the API, not a separately
maintained list. Ordinary methods use the existing signed transport without an
extra request. placeOrder() uses the optimized order path described below.
Low-level compatibility calls
crisp.call(operationName, input, options?) remains supported for existing code
and generic operation tooling. It is not required for ordinary integrations:
const result = await crisp.call('getReferralSummary', {
path: { externalUserId: user.id }, query: { limit: 20, offset: 0 },
});
// result.data, result.status, result.requestId, result.retryAfterMsAutocomplete lists operation names, required inputs and response fields. OpenAPI includes request bodies, query parameters, responses, errors and signing headers. Venue-owned extensions are open JSON, not stable CRISP fields.
input groups path, query, body and idempotencyKey; only include fields
required/allowed for that operation. These groups are not sent together as JSON:
the SDK maps them to path segments, query parameters, the body and a header.
Find the exact operation and its input in the endpoint catalogue.
For cash balance and available to trade, call getFunding with
{ details: 'trading' } as the second argument. Read result.data.balance?.formatted and
result.data.availableToTrade?.formatted respectively. See the dedicated
balance guide for SDK/HTTP examples and null handling.
Return envelope
All request helpers return a promise for the following wrapper (exceptions are
prepareTrading, tradeFills, and the standalone realtime/webhook helpers):
| Field | Meaning |
|---|---|
data | Typed operation-specific response data; inspect resource/command state |
status | HTTP status, including 202 for acceptance |
requestId | CRISP correlation ID; empty if a direct-cell response omitted it |
retryAfterMs | Parsed Retry-After in milliseconds, or null |
asOf, freshness | Optional read freshness metadata when supplied by the API |
Order helpers
All accept externalUserId, the input below, a persisted idempotencyKey, and
optional { signal, traceId }. traceId must be a UUID; the SDK generates one
when omitted. All return Response<OrderResult> with data.command and
data.order. An order ID/fill may not be available yet. externalUserId is
optional in a direct response. See the order guide.
| Method | Input | Equivalent API operation |
|---|---|---|
marketBuy(user, input, key, options?) | { marketId, tokenId, spendAmount, priceLimit? }; spendAmount is cash | placeOrder: BUY, market, amount = spendAmount |
marketSell(user, input, key, options?) | { marketId, tokenId, shares, priceLimit? }; shares is quantity | placeOrder: SELL, market, amount = shares |
limitOrder(user, input, key, options?) | { marketId, tokenId, side, price, shares, expiresInSeconds? } | placeOrder: limit, size = shares |
submitOrder(user, order, key, options?) | Full placeOrder body for advanced order options | placeOrder |
placeOrder(user, order, key, options?) | Same full body and optimized routing as submitOrder | placeOrder |
The first three helpers remove unit ambiguity; none requires a quote request before submission. The server still validates current funds, rules and prices.
Normalized read data
listMarkets, getMarket, listPositions, listTrades, getBook and
getDepositInstructions retain their existing venue payloads and additionally
return typed data.normalized. Use this for new integrations. Market IDs are
condition IDs, outcome token IDs remain strings, and prices/share quantities/USD
values are decimal strings. Missing values are null, not invented zeros or false
flags. These are display snapshots, never spendable-balance or execution authority.
Quote and bridge-transfer extensions remain venue-owned; do not infer their units.
Preparation, recovery and reporting
| Signature | Returns / behavior | API operation |
|---|---|---|
prepareTrading(user, tokenIds, { signal }?) | { routeReady, warmed } advisory booleans; 1–2 unique token IDs, no order submitted | createOrderCellCapability + prewarmOrders |
recoverOrder(user, originalKey, { signal }?) | One read of data.operation; does not wait or resubmit | findOperation with order.place |
recoverOperation(user, kind, originalKey, { signal }?) | One read; also supports cancellation and redemption | findOperation |
recoverWithdrawal(user, originalKey, { signal }?) | Reads the durable withdrawal; no replay-cache expiry | findWithdrawal |
waitForWithdrawal(user, id, { timeoutMs, signal }?) | Stops at confirmed, failed or manual review; confirmation is source-chain only | getWithdrawal |
waitForClaim(user, id, { timeoutMs, signal }?) | Stops at settled or manual review; inspect which one | getReferralClaim |
waitForAccount(user, { timeoutMs, signal }?) | Response whose data.status is ready, failed or revoked; inspect which one | getAccount |
waitForOperation(user, id, { timeoutMs, signal }?) | Response whose data.operation is terminal or outcome-unknown; inspect which one | getOperation |
claimAll(user, key, { signal }?) | Claim response with claims, amountUsdc, dayCount; does not wait for payout | claimReferralRewards with { all: true } |
tradeFills(user, query, { signal, maxPages }?) | Async iterable of response pages; consume page.data.fills | listTradeFills |
Wait helpers default to a 60-second deadline, configurable from 1–600,000 ms. They back off from 1 to 15 seconds and honor Retry-After for 429/502/503 reads. Timeout/abort throws; it never cancels execution. Other API errors propagate.
tradeFills requires from/to ISO timestamps (up to 366 days), supports limit
and a starting cursor, and defaults to 100 pages maximum. It throws on a
non-advancing cursor or exhausted page budget. Save the last returned cursor to
continue. See fill history.
Errors and retries
Import CrispApiError and CrispTransportError from the main server package.
| Error | Fields / handling |
|---|---|
CrispApiError | status, code, requestId, retryAfterMs, outcomeUnknown; inspect the code, not just the HTTP status |
CrispTransportError | outcomeUnknown; financial response lost/malformed or fallback unable to establish outcome |
| Local validation error | Invalid options/input are rejected before sending; correct the input |
| Abort/deadline | May throw an abort reason before dispatch; after order dispatch it can be outcome-unknown |
When outcomeUnknown is true, preserve the intent and use recovery.
Do not catch every error and submit with a fresh key. Neither the error class nor
a polling timeout proves that a financial action did not happen.
Use decimal strings and caller-persisted intent keys. Low-level call makes one
HTTP attempt. High-level order helpers implement the optimized routing policy below.
Polling helpers retry transient reads with bounded backoff and Retry-After; pass
an AbortSignal to stop waiting.
Low-latency execution
An execution cell is CRISP's order-execution entry point. You do not provision one, configure its URL, or manage its authorization tickets; the SDK discovers and refreshes the account-scoped route for you.
Keep a server-side client instance alive across requests, using one credential set for your organization’s
CRISP integration. marketBuy, marketSell, limitOrder, placeOrder, and submitOrder prefer
the execution cell by default:
| State | Requests on the submission path |
|---|---|
| Cold account | One capability lookup, then one direct cell request |
| Cached/prepared account | One direct cell request |
| Expired cell authorization | One capability refresh and one retry |
| Unavailable cell or lost response | At most one same-intent standard API fallback |
// Authenticated backend: start this when the trading ticket opens/token changes.
// Do not make the submit button wait for it.
void crisp.prepareTrading(user.id, [tokenId]).catch(() => {});
// Authenticated submit handler: persist the exact order and intent key first.
const result = await crisp.submitOrder(user.id, persistedIntent.order, persistedIntent.id, {
traceId: persistedIntent.traceUuid, // optional UUID; SDK generates one otherwise
signal,
});
// Return result.data immediately. It includes command state and any immediate fill.Preparation acquires the route and prewarms market inputs in parallel, with shared
in-flight requests and a 10-second cache for successful identical warmups. Its
routeReady/warmed flags are advisory. A warmup failure never disables trading.
Capabilities are isolated by account and client, bounded to 1,000 cached entries,
and refreshed 30 seconds before expiry. Credentials and capabilities stay on the
server. Redirects are rejected and authenticated responses are not HTTP-cached.
Fallback retains the exact snapshotted body, idempotency key and trace UUID; both
ingresses reserve the same durable command. It never creates a second order intent.
Validation, balance, rate-limit and explicit ambiguous errors do not trigger
fallback. Expired authorization refreshes once. If fallback fails after cell
dispatch, the result remains uncertain. Use recoverOrder and do not generate a
replacement key. An explicitly disabled capability endpoint is cached for 10 seconds.
The client's timeoutMs (15 seconds by default) bounds the entire submission,
including lookup/refresh/fallback. Abort/deadline stops further requests, not an
already submitted trade. A shared capability load can finish for other waiters.
Set orderRouting: 'api' to opt out; low-level call('placeOrder', ...) remains
the standard API path and never enables routing implicitly.
Cell responses currently omit externalUserId and requestId. OrderResult
marks the former optional; result.requestId is empty when absent. Retain the
trace UUID for correlation. Read envelopes preserve asOf/freshness when present.
Trading UX checklist
The SDK removes routing/retry plumbing; you still control your application’s UI behavior:
- Keep the last valid quote visible during background refresh. Ignore stale responses after account, token, side or amount changes. Refreshing a quote or prewarming must not disable an otherwise valid trade.
- Persist one key and exact input per action and prevent duplicate clicks while that action is submitting. Do not generate a new key after a timeout.
- Render immediate confirmed fill information without waiting for another read. Only poll queued/running/retryable commands. Accepted limit orders are not fills.
- Show uncertain orders as pending confirmation with recovery. Both
CrispTransportError.outcomeUnknownandCrispApiError.outcomeUnknownidentify uncertainty; an HTTP error does not always mean the trade failed. - Use realtime invalidations to refresh balances/positions, not arithmetic on repeated events. Keep existing data visible during refresh, and stop watchers on account switch/logout. Canceling a wait is not canceling an order.
Test these behaviors in your rendered application before launch; installing a transport SDK alone cannot guarantee your frontend behaves correctly.
Server versus browser
Import the main SDK only on your backend. Browser code can import the separate
@crisp-trade/partner-sdk/realtime entry point, which contains no HMAC code.
The API key identifies your organization’s CRISP integration; it does not authenticate individual users. Your backend must derive
the external user ID from its authenticated session, never act as an unrestricted
SDK dispatcher for browsers.
Browser realtime helper
Recommended: createAccountSync(options), imported from
@crisp-trade/partner-sdk/realtime, provides a framework-neutral account-data store.
Pass getSession(signal) and a loaders map for the resources your screen needs:
account, funding, orders, positions, trades, withdrawals, referrals.
Loaders return promises for data fetched through your authenticated backend.
Their return types are preserved in snapshot.data.
| Method | Behavior |
|---|---|
start() | Starts reads and realtime; returns cleanup. Idempotent while active; can restart after cleanup |
stop() | Aborts this run, clears displayed data and closes the socket; does not cancel orders |
refresh(resources?) | Refreshes selected registered resources, or all by default; rejects when stopped or on failure |
getSnapshot() | Stable snapshot until state changes: data, connection, refreshing, stale, lastUpdatedAt, error |
subscribe(listener) | Change notification; returns unsubscribe |
getServerSnapshot() | Empty initial snapshot for React server rendering/hydration |
connection is idle, connecting, connected, reconnecting or stopped.
lastUpdatedAt is the last successful refresh time, not an upstream freshness
guarantee for every field. stale covers pending/failed refreshes and disconnected
realtime. Inspect nullable/partial resource responses as well.
Optional storage defaults to sessionStorage with memory fallback; null selects
memory only. Storage contains cursors, never session tokens or account balances.
signal, callbackTimeoutMs (15 seconds by default), and createSocket are
optional. syncAccount(options) returns the same store and starts it immediately;
use createAccountSync in React so rendering does not start network activity.
The helper batches event targets, serializes refresh rounds, retains trailing invalidations, and commits cursors after successful refreshes. Initial reads do not wait for WebSocket availability. Reconnect/resync refresh all registered resources. Unknown event types also refresh all registered resources. Use one store per signed-in account; construct a new one when the account changes. See the complete integration and React adapter.
Advanced transport watcher
watchAccount(options) from @crisp-trade/partner-sdk/realtime returns a
synchronous stop() function. It reconnects, renews sessions and replays from
the last committed cursor; it never submits trades.
Required callbacks are getSession(signal), loadCursor(cursorKey, signal),
saveCursor(cursorKey, cursor, signal), onInvalidate(event, signal) and onResync(signal).
Existing callbacks ignoring the extra argument remain compatible. Pass the signal
to fetch and check it before committing asynchronous UI or cursor writes.
callbackTimeoutMs defaults to 15 seconds (1–120,000 ms). A hung callback is aborted
and the watcher reconnects from the last committed cursor. Optional signal stops
the watcher on logout/account switch. A callback ignoring abort can still perform
its own late side effects; the SDK cannot undo application writes.
They return promises. getSession returns { endpoint, protocol, token, expiresAt, cursorKey } from your authenticated backend. Optional onError
receives a safe error; createSocket can override the WebSocket constructor.
Callbacks must finish before the cursor is committed. See the complete
realtime integration.
Optional onInvalidateBatch(events, signal) replaces per-event invalidation and
commits the batch's last cursor after success; onReady(signal) runs before replay
callbacks at each handshake. onConnectionChange(state) observes connection
lifecycle. A stalled handshake reconnects after handshakeTimeoutMs (default
15 seconds). Existing per-event callbacks retain serial behavior.
Server webhook helper
Webhooks require separate assessment and enablement by CRISP and are not part of the standard integration offering. This helper's availability does not imply webhook access or delivery guarantees. Prefer account sync or backend cursor reads for account updates.
verifyWebhook({ rawBody, secret, timestamp, signature, eventId, cursor, now?, maxSkewMs? }) verifies and returns the parsed event, or throws. Pass raw bytes
before JSON parsing, the separate webhook secret and the corresponding headers.
Timestamp skew defaults to 300,000 ms. This is signature verification, not durable
deduplication: store processed event IDs yourself. See webhooks.
Compatibility
Existing routes and the small signing client remain. Operation lookup by ID/intent and referral summary are included in the current contract. Onboarding's referrer field is optional. Confirm capabilities against your approved environment; do not assume older deployments implement every route.
The contract, SDK types and route coverage are checked together. Download hashes are recorded in provenance.