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 →

From nothing to a real response.

Signing up provisions a real tenant — your own firm, branch, rep code and advisor login — not a shared demo account. Your data is isolated by the same rep-code scoping that runs for every firm on the platform, and the tenant arrives with a book already in it, so your first call returns rows rather than an empty list.

Three steps, about ten minutes: provision a tenant, exchange the login for a token, call the API. A fourth is there when you are ready to point a server at it — machine credentials are self-serve from the same login. If you decide to go ahead, the firm you explored becomes your live one — models, configuration and fee schedules intact.

1. Provision a tenant

POST/api/v1/auth/sandbox/signup
request
curl -X POST https://interposehq.com/api/v1/auth/sandbox/signup \
  -H 'Content-Type: application/json' \
  -d '{
        "email":   "you@yourfirm.com",
        "name":    "Your Name",
        "company": "Your Firm LLC"
      }'
FieldRequiredNotes
emailRequiredDisposable and temp-mail domains are rejected with a 422. Free-mail providers are accepted.
nameRequiredBecomes the advisor name on your rep code.
companyOptionalYour firm's display name. Defaults to name.

Every well-formed address gets the same 201, and what happens next arrives by email. A new address gets a confirmation link; an address that already has a sandbox or an Interpose login gets sign-in and password-reset links instead; a suspended sandbox is asked to contact support. The response deliberately does not say which — otherwise the form would tell anyone whether an address belongs to an Interpose user.

signup — 201, for every address
{
  "status":  "PENDING_VERIFICATION",
  "email":   "you@yourfirm.com",
  "message": "Check your email. We've sent the next step to this address — if it can start a sandbox, that's a link to activate it."
}

Click the link, or post its token to /api/v1/auth/sandbox/verify. That is when the tenant is provisioned, and verify returns the credentials. A used link answers 404; a link for an address another Interpose login has since claimed answers 409 and provisions nothing. (A local stack with no mail provider skips the email and returns the credentials from signup directly; that mode is off in production.)

verify — 201
{
  "signup_id":       "sbx_01J8Z...",
  "status":          "ACTIVE",
  "firm_code":       "SBX7",
  "rep_code":        "SBX7R01",
  "expires_at":      "2026-09-21T18:04:11.402913+00:00",
  "portal_url":      "https://app.interposehq.com",
  "portal_email":    "you@yourfirm.com",
  "portal_password": "8Kd2m-QpvR7x",
  "docs_url":        "https://interposehq.com/developers/sandbox"
}

The password is shown once and is not recoverable

Only a bcrypt hash is stored. Signing up again with the same address never issues new credentials, deliberately — otherwise anyone who knew your email address could take over your tenant. You get the same 201, and the email tells you how to sign in or reset your password.

Errors you may hit

StatusMeaningWhat to do
422Malformed email, or a blocked disposable domainUse a real, deliverable address
429Too many signup requests from your networkWait a few minutes
503Either the 25-tenant ceiling (or signup temporarily unavailable), or the edge rate limiter. A JSON body is the service; an HTML body is the limiterBack off if HTML; email support if JSON

None of these depends on whether the address is already registered. At most five signup emails go to one address per hour; further requests still answer 201 but send nothing.

2. Exchange the login for a token

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": "8Kd2m-QpvR7x"}'

The response carries an access_token — an HS256 JWT valid for 8 hours — alongside a user object with your role, firm and rep code. The token carries those claims, and every subsequent read is scoped from them rather than from any resource id you pass.

Cache the token

All /api/v1/auth/* paths are limited to 10 requests per minute per IP. Logging in per request will rate-limit you within seconds — and the proxy answers with a 503, not a 429. An unknown email and a wrong password return the identical 401 body, deliberately.

3. Make your first call

Your tenant arrives with a book already in it: 20 clients and 43 accounts on native custody. 16 are the advisory book — 32 accounts across six account types and 4 portfolios (3 backed by firm models); 3 are self-directed clients with one account each, funded far smaller; 1 is a managed client whose account runs in a digital portfolio of its own; one is a EUR-base account holding foreign cash; and 6 are AGGREGATED-custody accounts at Schwab, Fidelity and Pershing (or the ones you pick at signup), assigned into the same portfolios and fed through file-import custodian connections — the multi-custodian overlay. Advisory deposits are drawn per tenant from $72,000 to $5,000,000 — deliberately not a flat figure, so AUM tiering and fee-schedule boundaries have something to bite on — and the cash is invested by a real rebalancing run, so you get positions, orders and tax lots rather than an all-cash book. Seeding runs behind the signup response, not inside it. If you call within a few seconds of signing up you may catch a partially built book; poll until the count settles. The authoritative counts are served at GET /v1/sandbox/profile — no auth needed.

list your accounts
TOKEN='eyJhbGciOiJIUzI1NiIs...'

curl -H "Authorization: Bearer $TOKEN" \
  'https://interposehq.com/api/v1/baas/accounts?page=1&page_size=25'
response (abridged — the full object carries about 30 fields)
{
  "items": [
    {
      "account_id":     "acct_01HV4Y2K3M5N8P9QR2ST4UVWXY",
      "account_type":   "INDIVIDUAL",
      "account_name":   "Amara Okonkwo — Individual",
      "status":         "ACTIVE",
      "cash_balance":   "18450.7300",
      "buying_power":   "18450.7300",
      "base_currency":  "USD",
      "custodian":      "NATIVE"
    }
  ],
  "total": 24,
  "page": 1,
  "page_size": 25
}

Two field names catch people out immediately: it is account_type, not type, and an account's currency is base_currency — currency is the field on transactional objects like orders, not on the account. Note "18450.7300": a string, four decimal places. Every monetary value on the platform is a string, and that is not stylistic.

A call that needs no token at all

Market data is public, which makes it the fastest way to prove your networking works:

no Authorization header required
curl https://interposehq.com/api/v1/baas/market/quotes/AAPL

Portfolios and drift

Drift is computed per portfolio against its model. On a freshly seeded book it comes back near zero on every line, because the seeder invested the cash by running the real rebalancer against those same models. To watch drift open up, move a model's weights or trade against a portfolio and read it again.

GET/api/v1/pm/portfolios
GET/api/v1/pm/portfolios/{portfolio_id}/drift

Placing an order

Note the typed security identifier — there is no bare symbol field anywhere on the trading surface, and the field is type, not order_type:

request
curl -X POST https://interposehq.com/api/v1/baas/orders \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "account_id": "acct_01HV4Y2K3M5N8P9QR2ST4UVWXY",
        "security":   {"type": "ticker", "value": "AAPL"},
        "side":       "BUY",
        "type":       "MARKET",
        "quantity":   "10.00000000"
      }'

The account must be ACTIVE. A PENDING account returns a 422 whose detail is an object naming the failed check — a shape worth handling, because it is how every pre-trade compliance rule reports itself:

422 — a business-rule rejection
{
  "detail": {
    "message": "Order rejected by compliance checks.",
    "failed_checks": [
      { "check":  "account_active",
        "reason": "Account acct_01HV4Y... is PENDING; must be ACTIVE to accept orders." }
    ]
  }
}

In your sandbox this order routes to a local FIX 4.2 simulator. A sandbox account cannot dispatch to an external custodian — the gate lives in the order router and keys off the account's own flag, not an environment variable a deploy could flip.

4. Issue a key for your server

The portal token works everywhere, but it expires in eight hours and belongs to a person. For a server, mint a key from that login and let it fetch its own tokens. The key is scoped to the firm and rep code of the login that created it, so it can never see more than you can — supplying firm_id or allowed_rep_codes in the body is a 422, not a silent ignore.

POST/api/v1/baas/api-keys/self
issue a key
curl -X POST https://interposehq.com/api/v1/baas/api-keys/self \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "evaluation-worker", "permission_group": "read_only"}'
exchange it — form-encoded, 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_...'

key_secret comes back exactly once and is not recoverable. Portfolio Management issues keys on the same path under /api/v1/pm/api-keys/self, and wherever you mint it the key authenticates on every pillar — issue one, not one per pillar. The permission groups, the ten-key ceiling per firm and the revoke path are under Authentication.

Current limits

what is not built yet

The path above works end to end today. Two things to know before you build on it.

  • Pick the permission group deliberately — it is checked on every request

    A self-serve key is scoped to your firm and rep codes, and that isolation is real. The permission_group you choose is checked per route on top of it, so a read_only key really is refused a write, with a 403 naming the scope it lacked. Choose the narrowest group that covers your integration. See Authentication.

  • The book has three years of history, and the history is synthetic

    The seeder funds 32 accounts and invests them, then backdates roughly three years of trading through the same fill path a live order takes — so lots carry genuine holding periods and the tax engines classify them the way they will classify a real client's. What it is not is your history: the clients, trade dates and sizes are invented, so treat performance and attribution as proof the calculation works rather than as a track record.