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 →

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…UseWhy
Evaluating in the sandboxPortal loginIt is what signup issues, and it works on every pillar
A backend calling BaaS or PMOAuth client credentialsStandard and short-lived, and you issue the key yourself
Barred by policy from transmitting a secretHMAC request signingPortfolio Management only

Portal login

POST/api/v1/auth/login
request
curl -X POST https://interposehq.com/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email": "you@yourfirm.com", "password": "..."}'
response
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type":   "bearer",
  "user": {
    "user_id":  "pauth_01J8Z...",
    "email":    "you@yourfirm.com",
    "role":     "advisor",
    "firm_id":  "firm_01J8Z...",
    "rep_code": "SBX7R01",
    "is_active": true
  }
}
PropertyValue
AlgorithmHS256
Lifetime8 hours
Rate limit10 requests/minute per IP across all /api/v1/auth/*
Bad credentials401 — the same body for an unknown email and a wrong password
use it as an ordinary bearer token
curl -H "Authorization: Bearer $TOKEN" \
  https://interposehq.com/api/v1/pm/portfolios

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.

RoleSees
advisorOnly accounts, portfolios and CRM records under its own rep codes
branch_supervisorIts branch's rep codes
ops_adminPlatform-wide
clientOnly its own accounts
api_clientA 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.

POST/api/v1/baas/api-keys/self
GET/api/v1/baas/api-keys/self
GET/api/v1/baas/api-keys/self/permission-groups
DELETE/api/v1/baas/api-keys/self/{key_id}
request
curl -X POST https://interposehq.com/api/v1/baas/api-keys/self \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "settlement-worker", "permission_group": "read_only"}'
response — 201
{
  "key_id":            "key_01J8Z...",
  "name":              "settlement-worker",
  "permission_group":  "read_only",
  "allowed_rep_codes": ["SBX7R01"],
  "status":            "active",
  "key_secret":        "ik_live_9f3c..."
}

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.

PillarPermission groups you may grant yourself
BaaSread_only · trading · account_management
Portfolio Managementread_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.

POST/api/v1/baas/oauth/token
POST/api/v1/pm/oauth/token

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

request
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_...'
response
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type":   "Bearer",
  "expires_in":   3600
}

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

PropertyValue
Canonical stringMETHOD \n PATH \n TIMESTAMP_MS \n SHA256_HEX(body)
Path signedThe service-side path — /v1/..., not the public /api/v1/pm/...
HeadersX-VM-Key-Id · X-VM-Timestamp · X-VM-Signature
Timestamp unitUnix milliseconds
Replay window30 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.

POST/api/v1/baas/stream/tickets
POST/api/v1/pm/stream/tickets
response
{"ticket": "eyJhbGciOiJIUzI1NiIs...", "token_type": "ws_ticket", "expires_in": 60}

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 yet

Portal 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_only key 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_credentials grant 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.