InterposeInvest

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 →

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. Target is something we are building toward and have not measured. By arrangement 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.

CapabilityStatusNote
REST across PM, BaaS and AuthLiveOpenAPI 3.1 behind it
Rebalancing, drift, performance, tax lots, billingLiveProduction code paths, not a demo mode
Real market dataLiveReal securities, real delayed prices
Server-Sent EventsLiveFirm-scoped, no handshake — the simpler of the two real-time transports
WebSocket streamsLiveReachable through the public proxy using a single-use 60-second ticket
Self-serve API keysLiveCreated with the portal login a signup issues; the secret is returned once
OAuth client credentials · HMAC signingLiveExchange a key for a one-hour bearer token, or sign each request
SDK install from interposehq.comLiveDirect-URL pip and npm installs, with a machine-readable index
SDK publication to npm and PyPITargetRegistry publication needs credentials we do not hold yet — install by URL meanwhile
Kafka topic consumptionBy arrangementSelf-hosted broker; arranged during onboarding
WebhooksRegistration onlyYou 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.

curl and any plain HTTP client
https://interposehq.com/api/v1/{pillar}/{resource}

GET https://interposehq.com/api/v1/baas/accounts
GET https://interposehq.com/api/v1/pm/portfolios
the official SDKs — note the missing version segment
https://interposehq.com/api/{pillar}

BaasClient(base_url="https://interposehq.com/api/baas")

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.

CredentialLifetimeUse it when
Portal login (HS256 bearer)8 hoursEvaluating, or driving the API as a person. It is what a sandbox signup issues.
OAuth 2.0 client credentials1 hourA backend calling on its own behalf. Keys are self-serve.
HMAC-SHA256 request signing30-second windowPolicy 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.

create a key, then exchange it for a token
# Self-grantable groups — BaaS: read_only · trading · account_management
#                        PM:   read_only · portfolio_management · advisory
curl -X POST https://interposehq.com/api/v1/baas/api-keys/self \
  -H "Authorization: Bearer $PORTAL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "reporting-job", "permission_group": "read_only"}'

# -> {"key_id": "key_01J8Z...", "key_secret": "ik_live_..."}   secret shown once

# The token endpoint is form-encoded, per RFC 6749 — data=, not json=
curl -X POST https://interposehq.com/api/v1/baas/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=key_01J8Z...' \
  -d 'client_secret=ik_live_...'

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.

headers
X-VM-Key-Id:    key_01J8Z...
X-VM-Timestamp: 1715441400000          # unix MILLISECONDS
X-VM-Signature: hex(HMAC-SHA256(secret, canonical_string))
canonical string
{METHOD}\n{PATH}\n{TIMESTAMP_MS}\n{SHA256_HEX(body)}

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.

the envelope — Server-Sent Events and Kafka
{
  "specversion":     "1.0",
  "id":              "evt_01HV4Y2K3M5N8P9QR2ST4UVWXY",
  "source":          "com.interpose.baas",
  "type":            "com.interpose.baas.order.filled",
  "datacontenttype": "application/json",
  "time":            "2026-05-11T14:23:00.000000000Z",
  "issandbox":       true,
  "data":            { }
}

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.

ticket, then connect
POST /api/v1/baas/stream/tickets       # bearer token or a signed request
# -> {"ticket": "...", "token_type": "ws_ticket", "expires_in": 60}

wss://interposehq.com/api/v1/baas/stream?ticket=<ticket>
wss://interposehq.com/api/v1/pm/pm/stream?ticket=<ticket>

# then, on the open socket
-> {"action": "subscribe", "account_id": "acct_01HV4Y..."}
<- {"type": "subscribed",  "account_id": "acct_01HV4Y..."}

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.

TopicPublished byFires when
vm.baas.orders.submittedBaaSAn order is accepted and routed
vm.baas.orders.filledBaaSAn order fills, whole or partial
vm.baas.orders.cancelledBaaSAn order is cancelled
vm.baas.positions.updatedBaaSA position changes
vm.pm.rebalancing.initiatedPMA rebalancing run starts
vm.pm.rebalancing.completedPMA rebalancing run finishes
vm.pm.rebalancing.orders.createdPMRebalance 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.

StatusMeaning
400Bad request — malformed JSON or a missing required field
401Unauthenticated — invalid or expired token, or a bad signature
403Authenticated, but the resource is outside your firm and rep codes
404Resource not found
409Conflict — cancelling an already-filled order, for example
422Validation error or a compliance rejection — inspect detail
429An application-level cap — the ten-key ceiling, the sandbox account cap. No Retry-After is sent. Edge throttling returns 503 instead
5xxServer 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.

PrefixEntityExample
usr_Userusr_01HV4Y2K3M5N8P9QR2ST4UVWXY
acct_Accountacct_01HV4Y2K3M5N8P9QR2ST4UVWXY
ord_Orderord_01HV4Y2K3M5N8P9QR2ST4UVWXY
txn_Transactiontxn_01HV4Y2K3M5N8P9QR2ST4UVWXY
ptf_Portfolioptf_01HV4Y2K3M5N8P9QR2ST4UVWXY
reb_Rebalancing runreb_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.

ValuePrecisionExample
Fiat monetary amount4 decimal places"152.3400"
Share quantity8 decimal places"10.00000000"
FX rate6 decimal places"1.265432"
Performance return10 decimal places stored, 2 displayed"0.0452318920"
TimestampISO 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.

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