Sandbox environment. Interpose is not a registered broker-dealer and holds no FINRA approval. Every API documented here is available in a simulated evaluation environment only — no client assets are held, no real trades are executed, and no custody is provided. The calculation engines are the production code paths; fills, custody, settlement and KYC decisioning are simulated. What is real and what is simulated →
Developers
Build on the engines, not around them.
Interpose Invest is a front end over a Portfolio Management and brokerage platform that is API-first underneath. Everything the advisor portal and the client app do, they do by calling these APIs — so anything you see in the product, you can drive yourself, embed in your own application, or white-label as your own.
This section covers the two pillars that matter for a wealth or investing integration: Portfolio Management — portfolios, models, drift, rebalancing, performance, tax lots, billing — and Brokerage-as-a-Service — accounts, orders, positions, transactions and market data. Authentication sits alongside both.
What is live today
The markers below mean exactly one thing each, here and on every page in this section. Live exists and has test coverage today. is something we are building toward and have not measured. works, but needs a conversation with us to set up. Registration only means the configuration half exists and the delivery half does not. Not available is not reachable from your infrastructure today, whatever the specifications say.
| Capability | Status | Note |
|---|---|---|
| REST across PM, BaaS and Auth | Live | OpenAPI 3.1 behind it |
| Rebalancing, drift, performance, tax lots, billing | Live | Production code paths, not a demo mode |
| Real market data | Live | Real securities, real delayed prices |
| Server-Sent Events | Live | Firm-scoped, no handshake — the simpler of the two real-time transports |
| WebSocket streams | Live | Reachable through the public proxy using a single-use 60-second ticket |
| Self-serve API keys | Live | Created with the portal login a signup issues; the secret is returned once |
| OAuth client credentials · HMAC signing | Live | Exchange a key for a one-hour bearer token, or sign each request |
| SDK install from interposehq.com | Live | Direct-URL pip and npm installs, with a machine-readable index |
| SDK publication to npm and PyPI | Registry publication needs credentials we do not hold yet — install by URL meanwhile | |
| Kafka topic consumption | Self-hosted broker; arranged during onboarding | |
| Webhooks | Registration only | You get a subscription and a secret; nothing is dispatched yet |
Where a capability is not reachable from your infrastructure, you will find it under Current limits on the page it belongs to, rather than discovering it in an afternoon.
Base URLs
There is one environment, and it is a simulated evaluation environment. Interpose operates no production environment and will not before FINRA approval.
Never mix the two shapes
The SDKs append their own /v1; the curl path already contains one. Supplying both produces /api/v1/baas/v1/accounts, which returns 404. If you see a 404 on a path containing /v1/v1/, this is why.
Authentication
Three credentials, and all of them resolve to the same thing: the firm and rep codes the credential belongs to. Authorization is computed from those claims on every request — the API does not trust a resource id you pass it. Full detail, including which pillar accepts which, is on the authentication page.
| Credential | Lifetime | Use it when |
|---|---|---|
| Portal login (HS256 bearer) | 8 hours | Evaluating, or driving the API as a person. It is what a sandbox signup issues. |
| OAuth 2.0 client credentials | 1 hour | A backend calling on its own behalf. Keys are self-serve. |
| HMAC-SHA256 request signing | 30-second window | Policy forbids transmitting a secret. Nothing to refresh. |
Machine credentials are self-serve
Mint a key with the portal login your signup issued. The key is always scoped to the firm and rep codes of the login that created it — supplying firm_id or allowed_rep_codes in the body is a 422, not a silent ignore, so a key can never be broadened past the person who asked for it. Ten active keys per firm; full_access stays an operator-issued group.
Request signing
Three headers on every request, with the timestamp in unix milliseconds. Requests more than 30 seconds old are rejected, so sign at send time rather than caching a signature.
PATH is the service-side path — /v1/portfolios, not the public /api/v1/pm/portfolios. The proxy strips the pillar segment before the service sees it, so signing the public path produces a signature over a string the verifier never reconstructs.
Event streams
Every state change emits a CloudEvents 1.0 envelope on Kafka. Three transports carry it to you: Server-Sent Events, WebSocket, and direct Kafka consumption by arrangement. Two of them carry that envelope verbatim; the WebSocket sends its own lighter frame shape instead, which matters if you were planning to dedupe on the envelope id. Both HTTP transports are firm-scoped — naming an account you are not entitled to is a 403. The full treatment, including the frame shapes and the webhook signature scheme, is on Events & streaming.
On SSE and Kafka, dedupe on id: delivery is at-least-once and a reconnect can replay. WebSocket frames carry no envelope id, so if you need deduplication that is a reason to choose SSE. SSE is the simpler transport in general — an ordinary GET with a bearer token, no handshake, and it passes through the corporate proxies that block WebSockets. WebSocket costs one extra round trip for the ticket and gives you a duplex connection you can subscribe and re-subscribe on.
The WebSocket handshake
Ask for a ticket over normal HTTP, then connect with it. Tickets are single-use and live 60 seconds. The reason for a ticket rather than ?token= is narrow and worth stating: the server logs handshake query strings, and a portal token lives eight hours, so a logged token is a session handover. A 60-second single-use ticket in a log is spent.
The doubled pm/pm is not a typo
Portfolio Management declares its route at /v1/pm/stream, and the proxy rewrites /api/v1/pm/(.*) to /v1/$1. The two compose to /api/v1/pm/pm/stream. BaaS has no such doubling. If you normalise the path on the way out of your config loader, this is the one that breaks.
If you would rather not make two calls, offer the subprotocols interpose.v1 and interpose.token.<ticket> on the upgrade instead. A rejected handshake is an HTTP 403 with no close frame and no close code — the connection never got far enough to have one, so do not write a handler that waits for a code. Close codes exist only after a successful accept: 4403 is a subscription to something your credential cannot see, 4002 a malformed or missing subscribe frame.
Kafka topics
Direct consumption is arranged during onboarding rather than self-serve. The topic prefix is vm. for historical reasons while the CloudEvents type field uses com.interpose.* — two namespaces, both correct.
| Topic | Published by | Fires when |
|---|---|---|
| vm.baas.orders.submitted | BaaS | An order is accepted and routed |
| vm.baas.orders.filled | BaaS | An order fills, whole or partial |
| vm.baas.orders.cancelled | BaaS | An order is cancelled |
| vm.baas.positions.updated | BaaS | A position changes |
| vm.pm.rebalancing.initiated | PM | A rebalancing run starts |
| vm.pm.rebalancing.completed | PM | A rebalancing run finishes |
| vm.pm.rebalancing.orders.created | PM | Rebalance orders are handed to BaaS |
Errors
Every error returns JSON with a detail field. It has three shapes — a string, a validation list, or a compliance object carrying failed_checks — and 422 is used for both schema failures and business-rule rejections, so branch on the shape rather than on the status. The three shapes are set out on API conventions.
| Status | Meaning |
|---|---|
| 400 | Bad request — malformed JSON or a missing required field |
| 401 | Unauthenticated — invalid or expired token, or a bad signature |
| 403 | Authenticated, but the resource is outside your firm and rep codes |
| 404 | Resource not found |
| 409 | Conflict — cancelling an already-filled order, for example |
| 422 | Validation error or a compliance rejection — inspect detail |
| 429 | An application-level cap — the ten-key ceiling, the sandbox account cap. No Retry-After is sent. Edge throttling returns 503 instead |
| 5xx | Server error — send us the request and we will look: support@interposehq.com |
IDs, types and precision
Entity ids are ULIDs with a type prefix — lexicographically sortable, so sorting by id sorts by creation time, and safe to put in a URL.
| Prefix | Entity | Example |
|---|---|---|
| usr_ | User | usr_01HV4Y2K3M5N8P9QR2ST4UVWXY |
| acct_ | Account | acct_01HV4Y2K3M5N8P9QR2ST4UVWXY |
| ord_ | Order | ord_01HV4Y2K3M5N8P9QR2ST4UVWXY |
| txn_ | Transaction | txn_01HV4Y2K3M5N8P9QR2ST4UVWXY |
| ptf_ | Portfolio | ptf_01HV4Y2K3M5N8P9QR2ST4UVWXY |
| reb_ | Rebalancing run | reb_01HV4Y2K3M5N8P9QR2ST4UVWXY |
Money and quantities are strings, never JSON numbers. Parse them into a decimal type — Decimal, BigDecimal, whatever your language calls it — and not into a float. A cent lost to binary floating point is a reportable break, not a rounding curiosity.
| Value | Precision | Example |
|---|---|---|
| Fiat monetary amount | 4 decimal places | "152.3400" |
| Share quantity | 8 decimal places | "10.00000000" |
| FX rate | 6 decimal places | "1.265432" |
| Performance return | 10 decimal places stored, 2 displayed | "0.0452318920" |
| Timestamp | ISO 8601, nanoseconds, UTC | "2026-05-11T14:23:00.000000000Z" |
Every monetary amount carries an explicit currency field, and returns are decimal fractions rather than percentages — "0.0452318920" is 4.52%, not 0.045%. Security identifiers are typed objects rather than bare strings. Both, and the pagination shapes, are on API conventions.
On specification coverage
The published OpenAPI specifications are hand-maintained and describe roughly 180 operations. Portfolio Management and BaaS alone expose around 800. Where a specification is silent, the endpoint may still exist — ask us rather than assuming it does not. The endpoint reference here lists only routes that have been confirmed against the running services.
The things your CTO will not ask about
This section is written for whoever is doing the integration, but the questions that decide whether it happens are usually somebody else's. Short answers, so nobody has to email us to get them.
| Question | Answer |
|---|---|
| What does it cost? | Scoped with each firm against the shape of your book — how many custodians you run and how much of the stack you consolidate. The sandbox is free and needs no conversation first. Ask us. |
| How do I get my data out? | Export is available at any time, not only on termination, and the commitment is contractual — see Terms. Source-code escrow is available as part of a founding-partner agreement. |
| Can you move my existing book? | Yes, and we run it with you rather than handing you an endpoint. Bring the position and lot export you already download from your custodian. Unattended self-serve import is not something to build on yet. |
| Who is on the other end? | A small team, pre-FINRA. You get the person who designed the thing you are asking about. That is the trade against a vendor with a support tier. |
Where to go next
For the full four-pillar platform — Post-Trade and Retirement Plan Administration alongside these two — platform.interposehq.com/docs carries the complete reference.