CRISP partners

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 optionRequired / defaultMeaning
originRequiredApproved HTTPS origin with no path, query, fragment or URL credentials
keyId, secretRequiredYour server-only API credentials
timeoutMs15,000Per-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
fetchGlobal fetchOptional 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.retryAfterMs

Autocomplete 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):

FieldMeaning
dataTyped operation-specific response data; inspect resource/command state
statusHTTP status, including 202 for acceptance
requestIdCRISP correlation ID; empty if a direct-cell response omitted it
retryAfterMsParsed Retry-After in milliseconds, or null
asOf, freshnessOptional 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.

MethodInputEquivalent API operation
marketBuy(user, input, key, options?){ marketId, tokenId, spendAmount, priceLimit? }; spendAmount is cashplaceOrder: BUY, market, amount = spendAmount
marketSell(user, input, key, options?){ marketId, tokenId, shares, priceLimit? }; shares is quantityplaceOrder: 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 optionsplaceOrder
placeOrder(user, order, key, options?)Same full body and optimized routing as submitOrderplaceOrder

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

SignatureReturns / behaviorAPI operation
prepareTrading(user, tokenIds, { signal }?){ routeReady, warmed } advisory booleans; 1–2 unique token IDs, no order submittedcreateOrderCellCapability + prewarmOrders
recoverOrder(user, originalKey, { signal }?)One read of data.operation; does not wait or resubmitfindOperation with order.place
recoverOperation(user, kind, originalKey, { signal }?)One read; also supports cancellation and redemptionfindOperation
recoverWithdrawal(user, originalKey, { signal }?)Reads the durable withdrawal; no replay-cache expiryfindWithdrawal
waitForWithdrawal(user, id, { timeoutMs, signal }?)Stops at confirmed, failed or manual review; confirmation is source-chain onlygetWithdrawal
waitForClaim(user, id, { timeoutMs, signal }?)Stops at settled or manual review; inspect which onegetReferralClaim
waitForAccount(user, { timeoutMs, signal }?)Response whose data.status is ready, failed or revoked; inspect which onegetAccount
waitForOperation(user, id, { timeoutMs, signal }?)Response whose data.operation is terminal or outcome-unknown; inspect which onegetOperation
claimAll(user, key, { signal }?)Claim response with claims, amountUsdc, dayCount; does not wait for payoutclaimReferralRewards with { all: true }
tradeFills(user, query, { signal, maxPages }?)Async iterable of response pages; consume page.data.fillslistTradeFills

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.

ErrorFields / handling
CrispApiErrorstatus, code, requestId, retryAfterMs, outcomeUnknown; inspect the code, not just the HTTP status
CrispTransportErroroutcomeUnknown; financial response lost/malformed or fallback unable to establish outcome
Local validation errorInvalid options/input are rejected before sending; correct the input
Abort/deadlineMay 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:

StateRequests on the submission path
Cold accountOne capability lookup, then one direct cell request
Cached/prepared accountOne direct cell request
Expired cell authorizationOne capability refresh and one retry
Unavailable cell or lost responseAt 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.outcomeUnknown and CrispApiError.outcomeUnknown identify 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.

MethodBehavior
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.

CRISP Partner Documentation