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 →
Authentication
Scoped by rep code, not by a shared account.
Access is computed from the claims in your token on every request — the API does not trust a resource id you pass it. On the REST surface, two firms cannot see each other's data, and that isolation runs through one code path rather than a special case per endpoint.
That includes the streams. Server-Sent Events connections and WebSocket subscriptions are authorized with the same predicate as the REST route for the same account, so a stream carries your book and nobody else's. The handshake mechanics are under Events & streaming.
Interpose accepts three credentials. A portal login is a human signing in for an 8-hour token — what a sandbox signup issues, and it works on every pillar. A machine credential is a key pair you issue yourself from that login and your server exchanges for a one-hour token. Request signing is the option where the secret never crosses the wire. Coverage of the latter two is uneven by pillar, and the table below says which is which.
| You are… | Use | Why |
|---|---|---|
| Evaluating in the sandbox | Portal login | It is what signup issues, and it works on every pillar |
| A backend calling BaaS or PM | OAuth client credentials | Standard and short-lived, and you issue the key yourself |
| Barred by policy from transmitting a secret | HMAC request signing | Portfolio Management only |
Portal login
| Property | Value |
|---|---|
| Algorithm | HS256 |
| Lifetime | 8 hours |
| Rate limit | 10 requests/minute per IP across all /api/v1/auth/* |
| Bad credentials | 401 — the same body for an unknown email and a wrong password |
What the token carries
The payload includes role, firm_id, rep_code and allowed_rep_codes. Authorization is computed from those claims on every request — the API does not trust a resource id you pass it.
| Role | Sees |
|---|---|
| advisor | Only accounts, portfolios and CRM records under its own rep codes |
| branch_supervisor | Its branch's rep codes |
| ops_admin | Platform-wide |
| client | Only its own accounts |
| api_client | A machine credential — an API key exchanged for a token, or an HMAC-signed request. It resolves to the firm and rep codes on its own key, so it sees exactly what the login that issued it sees |
A sandbox tenant is an ordinary advisor. That is worth understanding if you are evaluating the security posture rather than the features: sandbox traffic is not a separate authorization branch that could quietly fail open, it rides the same rep-code scoping path every advisor login on the platform exercises daily.
API keys — issue your own
A portal login can mint machine credentials for its own firm, so a signup is all you need to get a server talking to the API. Both BaaS and Portfolio Management issue them, on the same four paths.
key_secret comes back exactly once
Only a fingerprint is stored, and no endpoint will show you the secret again. If you lose it, revoke the key and issue another.
Portfolio Management issues keys at /api/v1/pm/api-keys/self. The two pillars keep separate key stores, so a BaaS key is not a credential on PM — issue one per pillar you call.
A key can never be wider than the login that made it
firm_id and allowed_rep_codes are copied from your token. Sending either in the body is a 422 rather than a silent ignore — a caller who believes a scope field was honoured would assume the key is narrower than it is. Only an advisor or branch_supervisor login may issue, so a leaked key cannot be used to manufacture more.
| Pillar | Permission groups you may grant yourself |
|---|---|
| BaaS | read_only · trading · account_management |
| Portfolio Management | read_only · portfolio_management · advisory |
Omitting permission_group gives you read_only. Anything outside the list above returns a 422 naming the valid values; full_access is the operator tier and is not self-grantable. A firm may hold ten active keys at once and the eleventh returns 429, so rotate by issuing the new key before revoking the old one — revocation takes effect immediately. A key belonging to another firm answers 404 on delete, the same as one that does not exist, so this is not a way to probe which key ids are real.
Machine credentials — OAuth 2.0 client credentials
The standard server-to-server flow, RFC 6749 §4.4. Available on BaaS and Portfolio Management.
The token endpoint is form-encoded, not JSON
This follows RFC 6749, and it is the single most common way to get a 422 here — a JSON body is rejected with Field required on all three fields. In httpx or requests, that means data=, not json=.
The client_id is the key id (key_…); the client_secret is the secret shown once at issuance (ik_live_…). Swapping them produces a 401 that reads like a credential problem.
HMAC request signing — Portfolio Management only
The high-ceremony option: the secret never crosses the wire. Brokerage-as-a-Service does not accept signed requests on its REST routes — use a bearer token there. Two surfaces are the exception and do take a signed request: the stream-ticket endpoint below, and the Server-Sent Event streams under /v1/events/.
| Property | Value |
|---|---|
| Canonical string | METHOD \n PATH \n TIMESTAMP_MS \n SHA256_HEX(body) |
| Path signed | The service-side path — /v1/..., not the public /api/v1/pm/... |
| Headers | X-VM-Key-Id · X-VM-Timestamp · X-VM-Signature |
| Timestamp unit | Unix milliseconds |
| Replay window | 30 seconds |
Two details that will cost you an afternoon
The timestamp is in milliseconds. A signer written against time.time() in seconds fails every request.
You sign the service-side path. The proxy rewrites /api/v1/pm/portfolios to /v1/portfolios before the service sees it, so signing the public path produces a signature over a string the verifier never reconstructs.
Stream tickets
A WebSocket handshake carries no Authorization header, so the socket takes a single-use ticket you mint over REST with a credential that does — a bearer token or an HMAC-signed request.
The ticket lives 60 seconds, is spent on first use, and carries your entitlement claims frozen at mint time — so the socket authorizes subscriptions against exactly the identity REST would have used. A portal JWT passed in the socket URL is refused, which is the whole point: a token good for eight hours must not become a stream credential because somebody pasted it into a query string. Connecting and subscribing are covered under Events & streaming.
Current limits
what is not built yetPortal logins, self-serve API keys and request signing all work today. These are the edges worth knowing before you build on them.
A key's permission group is recorded, not enforced per route
The group you choose resolves to a scope list on the key record, and no route requires a scope today — so a
read_onlykey is not yet stopped from writing. The isolation that does hold is the one that matters most: a key sees only its own firm's rep codes, on the same code path every advisor login runs on. Treat the group as a statement of intent until scope enforcement ships.OAuth and signing coverage is uneven by pillar
Both pillars issue API keys and OAuth tokens, but only Portfolio Management accepts HMAC-signed requests on its REST routes; on BaaS the signature is accepted only by the stream-ticket endpoint. Only the
client_credentialsgrant is implemented — there is no authorization-code flow and no PKCE support.
Verifying inbound deliveries
Webhooks invert the direction — there, Interpose authenticates to you and you verify our signature. The scheme and its traps are documented under Events & streaming, alongside the more important fact that platform events are not dispatched to webhooks today.