How it works
From news event to tradable basket.You keep the licence, money, and customers.
Cascade builds news-driven baskets and related compute. You choose which modules we run; the rest stays on your stack.
Introduction
A customer who wants exposure to a news theme (for example rising oil prices) can take a single contract on a basket of related stocks, long some names and short others.
Cascade handles the steps from that order intent through to a booked, hedged position, for as many of those steps as you buy.
We never hold your customers' money. In any configuration.
Broker and Venue
Cascade uses two roles throughout the product:
You are the Broker. Your customers stay with you. The Venue is where the hedge is placed.
Broker
The licensed firm that faces retail customers, books contracts, holds money in trust, and transmits hedges.
Venue
Where the hedge is executed: an exchange, liquidity provider, or cover counterparty. The Broker holds the account there.
The five modules
Five separate products, not five phases of one product. A module never reaches inside another. That is why you can buy three and build two yourself.
01 Basket intelligence
LiveMaps a news theme to a basket of constituents with direction and weight.
- Consumes
- News / themes
- Produces
- A basket: constituents, direction, weight, disclosure
- If you turn it off
- You write your own baskets
02 Index and marks
LivePublishes an index level and live marks for each basket.
- Consumes
- A basket + licensed market-data feed
- Produces
- A mark: basket, index level, timestamp
- If you turn it off
- You calculate your own index and license your own data feed
- The index is a level, not a price. It starts at 1,000.
- It is spread-free. Cascade publishes the raw index; you apply your own spread.
03 Exposure and netting
LiveNets customer order intent into hedge deltas across the book.
- Consumes
- Basket, mark, order intent
- Produces
- Net exposure, hedge delta
- If you turn it off
- You do your own netting and hedge sizing
04 Risk compute
Not builtComputes valuation and advisory risk signals from marks and positions.
- Consumes
- Marks, positions
- Produces
- Valuation and advisory risk signals
- If you turn it off
- You do your own margin and loss-cut maths
- Signals are advisory. Cascade computes; the Broker acts.
- Not available on the live landing path today.
05 Ledger of record
Not on saleCascade holds the contract (full-service tier).
- Consumes
- Order intent, risk signals
- Produces
- Contract state
- If you turn it off
- You keep your own customer ledger (the normal case)
- Not available in Japan. Do not treat as the default path.
Pick your configuration
The more licensed and built-out you are, the fewer modules you need.
If you hold the licence and run your own platform, take modules 1 to 3 (or 1 to 4) and keep your customer relationship entirely. If you have an app and no dealer platform, take all five, where the jurisdiction allows.
| # | You are | You take | We do | You do |
|---|---|---|---|---|
| 1 | A media company, or a Broker who just wants ideas | M1 | Baskets | Everything else |
| 2 | A Broker who wants a branded index to quote against | M1–M2 | Baskets + index | Spread, ledger, margin, netting, hedging |
| 3 | A licensed Broker with your own platform | M1–M3 | Baskets, index, netting, hedge deltas | Ledger, margin, loss-cut, order transmission |
| 4 | A Broker with a ledger but no derivatives risk maths | M1–M4 | The above + advisory risk signals | Ledger, and you still enforce |
| 5 | An app with no dealer platform | M1–M5 | Everything up to the contract itself | The app, and the licence |
How money and risk flow
The customer relationship never leaves the Broker. Cascade computes; the Broker books and transmits.
01
Customer
Taps Invest on a published basket in your app.
02
Broker
Quotes an indicative index, books the contract in your ledger, holds margin in your trust account.
03
Broker
Reports the order intent to Cascade.
04
Cascade
Decomposes into signed legs, nets across customers, emits hedge deltas.
05
Broker
Transmits cover orders to the Venue from your own OMS.
06
Broker → Cascade
Reports fills; Cascade updates cover held and streams fresh marks.
Getting started
- 01
Choose Broker or Venue
Create an organisation. Brokers launch indices; Venues route cover flow.
- 02
Complete due diligence
Licence checks and profile assignment decide which modules are enabled for your jurisdiction.
- 03
Connect and go live
Issue API keys, wire webhooks and SSE marks, connect your Venue, then publish your first basket.
API integration
Wire Cascade into your product from your Broker backend. Call Business with an API key; Business proxies allowlisted Engine routes and serves live SSE. Full request/response schemas live in Swagger.
| Cascade holds | Cascade never holds |
|---|---|
A customerRef — an opaque string you choose | Customer name, email, address or account number |
| Contracts, exposure and marks keyed on that string | Customer money, balances or funding currency |
| Your configuration and entitlements | Your KYC records or suitability questionnaire answers |
One currency per organisation
Your organisation has a single exposure currency, USD unless configured otherwise. Cascade converts nothing. You hold your customer's funding currency and convert on your own books at your own rate; that rate never reaches Cascade. A request naming a different currency is rejected with currency_mismatch, naming both — never coerced.
Start here
OpenAPI · Swagger UI
Broker Server OpenAPI is served on your Business host at /docs/tenant. Use your sandbox Business URL when exploring the API.
/docs/tenant
Auth
Authorization: Bearer csk_…
Keys: csk_test_ / csk_live_. Issue keys in the Broker console under API keys.
Bases
- /v1/engine/*REST: catalog, index, trading, positions
- /v1/stream/*SSE: live marks and event fan-out
API keys & scopes
Keys are issued from the Cascade dashboard. csk_test_… keys are available immediately; csk_live_… keys require an approved organisation — so you can build the entire integration before due diligence completes.
| Operation | Endpoint |
|---|---|
| Issue a key | POST /v1/console/api-keys — environment, scopes, optional modules and expiresAt |
| List keys | GET /v1/console/api-keys — metadata only |
| Rotate | POST /v1/console/api-keys/{keyId}/rotate |
| Revoke | DELETE /v1/console/api-keys/{keyId} |
| What you may request | GET /v1/console/api-keys/catalogue |
The secret is shown once
Cascade stores a hash and a short display prefix. No endpoint will show you the key again — if you lose it, rotate.
Scopes are exact strings. There is no hierarchy and there are no wildcards — read does not imply baskets:read, and admin does not imply trade.
| Scope | Grants |
|---|---|
baskets:read | Catalog browsing, event lists and detail, basket reads, dossiers |
index:read | Index levels, index history, the live mark stream |
read | Exposure reads, reconciliation breaks, the event stream |
admin | Publishing and lifecycle writes: publish, patch, close, resolve, unpublish, declare a subscription or spread policy, submit a reconciliation statement |
trade | Order submission (Modules 3 and 5) |
config:read | Your resolved configuration and clamp list |
quotes:write | Indicative quoting, where enabled |
positions:write | Margin preview and valuation (Module 4) |
risk:read | Risk signals and risk.* events (Module 4) |
Separate keys keep a leak bounded and let you rotate one service without an outage across your platform.
Display
baskets:read index:read
Renders markets, charts and event pages.
Treasury
read
Watches exposure and runs reconciliation.
Authoring
admin baskets:read
The back-office job that publishes and resolves markets.
Ops
config:read
Compliance and operations tooling.
Modules & entitlements
Module 1 is always available. Modules 2–5 are enabled per organisation. Every request is checked in three stages, and each one fails differently.
| Stage | Checks | Failure |
|---|---|---|
| 1 · Authentication | Valid, unrevoked key | 401 unauthorized |
| 2 · Scope | The key carries the scope this endpoint needs | 403 forbidden |
| 3 · Module | The module that owns this endpoint is enabled for you | 403 module_not_enabled |
Managing entitlements
| What | Where |
|---|---|
| See what you hold | GET /v1/console/org/configuration → modules |
| Request a module | POST /v1/console/org/module-requests (Modules 2–5) |
| Track a request | GET /v1/console/org/module-requests/{ticketId} |
Related codes
| Code | Meaning |
|---|---|
module_forbidden_by_profile | Your organisation cannot hold this module, for jurisdiction or deployment reasons. Requesting it will not help. |
profile_not_available | The requested deployment profile is unknown, switched off, or wrong for your jurisdiction. |
Branch on the code, not the status
forbidden means fix the key. module_not_enabled means enable the module. Surface the module field to whoever operates your integration. If the configuration read reports that values could not be retrieved, treat your entitlements as unknown, not empty.
How to integrate into your product
- 01
Authenticate
Call Business with your API key. Unknown /v1/engine/* paths return 404; only the allowlisted surface is open.
Authorization: Bearer csk_test_xxxxxxxx Content-Type: application/json
- 02
Load catalog into your product
List published events and templates, then show constituents to customers.
GET /v1/engine/events GET /v1/engine/events/{eventId} GET /v1/engine/catalog/templates - 03
Quote against the live index
Read the current level (starts at 1,000) and subscribe to SSE marks. Apply your own spread in your app. Cascade is spread-free.
GET /v1/engine/baskets/{basketId}/index GET /v1/stream/marks - 04
Record suitability, then trade
A first buy is refused without a passing suitability row. Preview writes nothing; reserve holds capacity; commit opens the contract.
PUT /v1/engine/traders/{traderId}/suitability POST /v1/engine/buy/preview POST /v1/engine/buy/reserve POST /v1/engine/buy/commit - 05
Show positions and closes
Read the book your customers hold, preview a close, then queue the sell.
GET /v1/engine/positions GET /v1/engine/portfolio POST /v1/engine/sell/preview POST /v1/engine/sell
- 06
Listen for hedge and lifecycle events
Subscribe to the event stream for hedge-delta and trade lifecycle fan-out from Business.
GET /v1/stream/events
API reference by module
Every allowlisted Broker Server route, with the same request and response detail as the Swagger UI. Click an endpoint to expand it. Scope hints must be present on your API key.
Module 1
Basket intelligence
Where markets come from: the licensed templates that define them, the events you publish from those templates, the baskets a customer buys, and the disclosure documents behind both. Always available — there is no module gate here, so a failure is the key or the request.
Module 2
Index and marks
The published level for each basket, its history, and a live feed. Requires the Module 2 grant and the index:read scope — baskets:read does not imply it, because basket intelligence and the index are separately licensed.
Module 3
Exposure and netting
Your firm's book: the running net per underlying and how much is covered. Exposure is not positions — positions are per-customer contracts, exposure is what has to be hedged. Nothing in this module moves a customer's money.
Events
Streaming
A live feed of the event catalogue for your organisation, spanning every module you hold.
Trading, positions & book
Modules 3 and 5 in the licensed sense — order lifecycle and the book a customer holds. Outside the Cascade API Reference's scope, so summarised here rather than expanded.
| Method | Path | Summary | Scope |
|---|---|---|---|
| PUT | /v1/engine/traders/{traderId}/suitability | Record suitability | trade |
| POST | /v1/engine/buy/preview | Preview a buy | trade |
| POST | /v1/engine/buy/reserve | Reserve a buy | trade |
| POST | /v1/engine/buy/commit | Commit a buy | trade |
| POST | /v1/engine/pending-orders/{pendingOrderId}/cancel | Cancel a queued buy | trade |
| POST | /v1/engine/sell/preview | Preview a sell | trade |
| POST | /v1/engine/sell | Queue a sell | trade |
| GET | /v1/engine/positions | List positions | read |
| GET | /v1/engine/portfolio | Portfolio summary | read |
| GET | /v1/engine/trades | List trades | read |
| GET | /v1/engine/trades/{tradeId}/status | Trade status | read |
| GET | /v1/stream/events | Lifecycle / hedge events (SSE) | API key |
Idempotency
Send an Idempotency-Key on every non-idempotent write.
- Repeating a request with the same key and same payload returns the original result.
- Reusing a key with a different payload is
409 conflict— never a silent overwrite. - Keys are scoped to your organisation.
- Reuse one key across retries of the same logical request; generate a new one for a genuinely new request.
Some endpoints carry their own natural handle — publishKey on POST /v1/engine/events — and behave the same way.
Replay flags — treat every one as success
| Flag | Endpoint |
|---|---|
replayed | POST /v1/engine/events |
alreadyClosed | POST /v1/engine/events/{id}/close |
alreadyStarted | POST /v1/engine/events/{id}/resolve |
alreadyUnpublished | POST /v1/engine/events/{id}/unpublish |
statementsAlreadyOnRecord | POST /v1/engine/reconciliation/snapshot |
Errors & retries
Every error uses the same envelope. code is a closed set — branch on it. message is for humans and may be reworded at any time.
| Field | Type | Description |
|---|---|---|
| code | string | A closed set. Branch on this. |
| message | string | For humans. May be reworded. |
| module | string? | Present only on module_not_enabled. |
| ruleId | string? | Present only on intake_halted. Stable, so you can key support tooling on it. |
| requestId | string | Log this on every failure — it is what makes a support request answerable. |
load_shed and unavailable are not the same
Both are 503. load_shed means nothing is broken: your request was declined before it was attempted and the same call will succeed shortly — retry with jitter. unavailable means a dependency is down — retry with backoff, and alert.
You can always close, even when you cannot open
Under sustained load Cascade declines work in a fixed order: reporting and administrative endpoints first, opening next, and closing, exposure reads and the mark feed last. So never build a close path that depends on a reporting or administrative call — and if exposure reads or marks start failing, escalate rather than degrading quietly.
attempt (with Idempotency-Key)
429 -> wait the advertised delay, retry
503 load_shed -> jittered backoff, retry
(expected to succeed)
503 unavailable, 500 -> jittered exponential backoff,
cap attempts, then alert
other 4xx -> do not retry; it will fail
identicallyRate limits & pagination
Requests are metered per organisation with a token bucket: capacity is your burst, refilled at your requests-per-second. Over the limit returns 429, with the retry delay in the body.
Read x-ratelimit-remaining on every response and throttle before you get there. Back off on 429; do not hot-loop.
| Style | Endpoints | How |
|---|---|---|
| Cursor | GET /v1/engine/events, GET /v1/engine/baskets/{id}/index/history | ?limit=50&cursor=…. An empty page with no nextCursor means there is no more data. Never construct a cursor — pass back exactly what you were given. |
| Offset | GET /v1/engine/reconciliation/breaks | ?limit=50&offset=0; the response carries nextOffset. |
Index history pages backwards
Its cursor walks towards from, oldest-last. That is how you find the start of a basket's series.
Conventions
- Money fields are decimal strings, never JSON numbers.
- customerRef / traderId are opaque. Cascade holds no customer identity.
- Index values are levels starting at 1,000, not currency prices.
- Buy preview always returns indicative: true and never a firm entry price.
- POST /quotes is not on the Broker Server allowlist. You own the pricing layer.
Webhooks
Cascade publishes the same catalogue two ways: webhooks for anything your back office must not miss, and the event stream for anything a screen is watching. Most integrations use both — if you take only one, take webhooks, because they retry.
| Event | Module | Extra scope on the stream |
|---|---|---|
basket.published | 1 | baskets:read |
basket.updated | 1 | baskets:read |
mark.published | 2 | index:read — on the webhook channel this carries status changes only; every level goes out on the stream |
mark.impaired | 2 | Marks stopped being good enough to value a position against |
mark.restored | 2 | Closes an outstanding mark.impaired |
hedge.delta.ready | 3 | |
hedge.fill.recorded | 3 | read |
reconciliation.break | 3 | read |
risk.margin_call | 4 | risk:read |
risk.losscut_breach | 4 | risk:read |
policy.clamp_applied | — | config:read |
module.blocked | — | read |
Delivery headers
| Header | Description |
|---|---|
X-Cascade-Signature | v1=<hex> — HMAC over the timestamp and the body. |
X-Cascade-Timestamp | When the delivery was signed. |
Content-Type | application/json. |
Verify in this order
- Read the raw body bytes. Do not parse, re-serialise or canonicalise first — re-serialising can reorder a map, and you would then verify a signature over something that was never sent.
- Compute the HMAC over the timestamp and the body. The signature covers both, so a replay cannot be given a fresh timestamp.
- Compare in constant time.
- Reject a timestamp outside your tolerance window.
- Only then parse.
Delivery is at least once
Deduplicate on the event id and make handlers idempotent. Respond 2xx quickly and do the work asynchronously — a slow handler looks like a failure and earns a retry.
An event for a module you do not hold is not discarded
It waits. If you enable the module later, what was owed is delivered. And module.blocked is never gated on the module it reports — route it somewhere a human reads, because it is how you discover part of your integration is calling something you do not hold.
POST /cascade/webhook HTTP/1.1
Host: yourfirm.example.com
X-Cascade-Signature: v1=4a7f9c…
X-Cascade-Timestamp: 2026-09-23T21:05:00Z
Content-Type: application/json
{ "eventType": "reconciliation.break",
"eventId": "9f30…",
"occurredAt": "2026-09-23T21:05:00Z",
"data": { "checkId": "e1f2…", "symbol": "MSFT",
"severity": "high", "frozen": true } }import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, headers, secret) {
const ts = headers["x-cascade-timestamp"];
const sig = (headers["x-cascade-signature"] || "").replace(/^v1=/, "");
if (Math.abs(Date.now() - Date.parse(ts)) > 5 * 60_000) return false;
const expected = createHmac("sha256", secret)
.update(ts).update(rawBody)
.digest("hex");
const a = Buffer.from(sig, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Error codes
The complete set, with whether retrying is worth it.
| Status | Code | Meaning | Retry |
|---|---|---|---|
| 400 | invalid_request | Malformed or missing fields | No |
| 401 | unauthorized | Missing, invalid or revoked key | No |
| 403 | forbidden | Valid key, wrong scope | No — reissue the key |
| 403 | module_not_enabled | Module not enabled (see module) | No — request the module |
| 403 | module_forbidden_by_profile | Your organisation cannot hold this module | No |
| 403 | profile_not_available | Deployment profile unknown, off, or wrong for your jurisdiction | No |
| 403 | tenant_restricted | Restricted for unpaid invoices. New positions blocked; closing still allowed | No — settle the invoice |
| 404 | not_found | No such resource for you | No |
| 409 | conflict | Idempotency key reused with a different payload, or a state conflict | No |
| 409 | tenant_suspended | Organisation suspended | No |
| 409 | exposure_cap_not_configured | No exposure cap configured | No — contact Cascade |
| 409 | restricted_symbol_blocked | Symbol hard-blocked | No |
| 422 | unprocessable_entity | Understood but semantically invalid — e.g. impossible market timings | No |
| 422 | exposure_cap_exceeded | Out of capacity | Yes, once headroom frees |
| 422 | restricted_symbol_flagged | Symbol flagged for review at authoring | No |
| 422 | spread_not_declared | Quote requested with no spread policy | No |
| 422 | index_not_quotable | No usable index to quote against | Later |
| 403 | trading_halted | Trading is halted | Later |
| — | intake_halted | Authoring stopped; see ruleId | Later |
| 429 | — | Too many requests | Yes, after the advertised delay |
| 500 | internal_error | A fault on our side | Yes, with backoff |
| 503 | unavailable | A dependency is unavailable | Yes, with backoff |
| 503 | load_shed | Declined under load, before being attempted | Yes — the same call will succeed |
Degrading well
| Signal | Do |
|---|---|
indexStatus: degraded | Show “price unavailable”. Never a number. |
mark.impaired | Mark your valuations impaired and say so. |
| Configuration values unreadable | Treat entitlements and cap as unknown, not empty. |
module.blocked events appearing | Something is calling a module you do not hold — fix the caller or request the module. |
| Reconciliation reads degraded and new positions failing | Assume a freeze; escalate. |
exposure_cap_exceeded | Out of capacity — retry when headroom frees. Distinct from exposure_cap_not_configured, which is a setup gap that retrying will never fix. |
Going-live checklist
What to confirm before you point a live key at production.
Correctness
- All money parsed as decimals, never floats
- All four indexStatus values handled; degraded never renders a number
- pricedAt used as the price time; ts only as the series x-axis
- seq gate per basketId; gaps treated as normal
- Movement labelled with changeSince / changeWindowFull
- History read against the response's from/to; bucketWidth compared against resolutionServed
- Closed-market gaps not interpolated
- Replay flags treated as success
Access
- Separate keys per concern; none over-scoped
- No key in any shipped client, log line or error report
- module_not_enabled handled distinctly from forbidden
- Live key issued only after approval
Events
- Signature verified over raw bytes and timestamp, constant time
- Handlers idempotent, deduplicating on event id
- module.blocked and reconciliation.break routed to a human
- Reconnect: Last-Event-ID on events, seq gate on marks
Operations
- x-correlation-id logged on every call
- Rate-limit headers observed; backoff on 429
- load_shed retried; unavailable alerted
- Nightly reconciliation scheduled, alerting on widening cumulativeDrift
- Close path independent of reporting endpoints
Glossary
| Term | Meaning |
|---|---|
| Template | A reusable market definition. Not tradable. |
| Event | A published, tradable instance of a template, with a trading window. |
| Outcome | One resolvable result of an event. One basket per outcome. |
| Basket | One outcome's tradable instrument. basketId is the key used by Modules 2 and 3. |
| Constituent | One leg of a basket: symbol, weight, direction. |
| Index | A basket's declared base-1,000 level. Not a price and not executable. |
| Mark | One published index observation. |
| Exposure / book | Your net notional per underlying across all your markets. |
| coverHeld | Notional the venue has actually covered for a symbol. |
| Exposure cap | The ceiling on your open and reserved notional, set by Cascade. |
| Netting | Combining your customers' offsetting instructions so only the residual reaches the venue. You set the mode from the dashboard — it is your firm's execution decision, so there is no Cascade-side write. |
| Break | A disagreement between your statement and Cascade's records. |
| Freeze | A block raised by a break; cleared by Cascade. |
| customerRef | Your opaque customer reference — the only customer-shaped field in the API. |
