{identity} accepts an idn_… id or the identity’s name.
An identity may connect one brokerage account per env: paper (simulated money) and live. A
connection is addressed by (identity, env) — it has no id of its own. Amounts are integer
micro-USD (1 USD = 1000000); quantities are decimal strings ("0.0125"), because fractional shares
and coins need them. See Trading for the model and the agent tools. Reads need the agents:read
scope and writes need the billing:write scope; on a live account, connecting, completing, placing, canceling and closing
also need a live sk_live_ key.
Naive Managed Agents is not a broker, a dealer or an investment adviser, and holds no customer money. The account, its orders and its fills are the brokerage’s. Nothing Naive or an agent says about a symbol is investment advice.
On a deployment with no brokerage app configured, connecting and every read or write at the brokerage answer
501 feature_not_configured, and so does listing connections; disconnecting still works.The connection object
status is one of pending (an authorization was started), active, expired (the brokerage
revoked access — connect again) or failed, with status_reason saying why. Reconnecting an
active env keeps it active until the new authorization lands; a declined re-authorization leaves
it active and only records the reason. This object never carries a token: Naive keeps one sealed
access token per identity and env, and no response returns it.
Connect
POST /v1/identities/{identity}/trading/connect — scope billing:write
disclosure to the person before they open authorize_url. The link works once and expires
after 15 minutes. When they approve, the brokerage sends their browser to
GET /v1/trading/oauth/callback, which stores nothing: it forwards the brokerage’s answer to the
dashboard’s /trading/complete page. There, the signed-in person finishes the connection with
POST /v1/trading/oauth/complete, and lands on the identity’s page with ?trading=connected — or
?trading=failed, with the reason on the connection. Not offered over MCP: a model never
hands a person a link that binds their brokerage to an identity.
Complete a connection
POST /v1/trading/oauth/complete — scope billing:write
state, plus code — or error when the person declined. A state is accepted
once, and only from a key in the organization that started the connect, so a link that reaches
someone else can never put their brokerage account on your identity. An unknown, used or foreign
state is not_found. Answers the trading_connection: active with its account_number, or
failed with status_reason (an already active connection stays active and only records the
reason).
List connections
GET /v1/identities/{identity}/trading/connections — scope agents:read
{ "object": "list", "data": [ trading_connection, … ] } — at most one per env.
Disconnect
DELETE /v1/identities/{identity}/trading/connections/{env} — scope billing:write
Deletes the connection and its sealed token. Answers
{ "object": "trading_connection", "identity_id": "…", "env": "paper", "deleted": true }, or
404 not_found when that env is not connected. To also revoke Naive’s access at the brokerage, do so
in the brokerage’s own settings.
Choosing the env
Every read and write takesenv (a query parameter on reads and cancel, a body field on place and
close). Given, it is used. Omitted, the identity’s one active connection is used; with both
active the call answers 400 validation_failed with param: "env"; with none, 409 state_conflict — connect first.
Account
GET /v1/identities/{identity}/trading/account?env= — scope agents:read
status is the brokerage’s own account status, verbatim.
Positions
GET /v1/identities/{identity}/trading/positions?env= — scope agents:read
BTCUSD). asset_class is us_equity,
crypto or us_option.
The order object
id is the brokerage’s order id. status is the brokerage’s own (new, partially_filled,
filled, canceled, …), verbatim. An order Naive cannot represent — a kind placed elsewhere that
this API does not offer — is left out of lists, and reading it by id answers validation_failed.
List orders
GET /v1/identities/{identity}/trading/orders?env=&status=&limit= — scope agents:read
status is open (default), closed or all; limit 1–500 (default 50). Answers
{ "object": "list", "data": [ trading_order, … ] }, unpaged.
Read an order
GET /v1/identities/{identity}/trading/orders/{order_id}?env= — scope agents:read
Quotes
GET /v1/identities/{identity}/trading/quotes?symbols=&asset_class=&env= — scope agents:read
symbols is 1–100, comma-separated, of one asset_class: us_equity (default) or crypto. Crypto
symbols are slashed: BTC/USD.
Place an order
POST /v1/identities/{identity}/trading/orders — scope billing:write
Answers
201 with the trading_order. The request’s Idempotency-Key becomes the order’s
client_order_id unless the body names one, so a retry with the same key cannot place twice; a
request with neither carries no key. A call with this route is the caller’s own decision — it is not
an approval card; an agent’s order asks a person by default, and on
live always. Venue rules — a minimum notional, which
time_in_force a coin accepts — are the brokerage’s: its refusal comes back as validation_failed
or forbidden with its reason.
Cancel an order
DELETE /v1/identities/{identity}/trading/orders/{order_id}?env= — scope billing:write
Answers { "id": "…", "object": "trading_order", "deleted": true }. The brokerage cancels
asynchronously: read the order for its final status.
Close a position
POST /v1/identities/{identity}/trading/positions/{symbol}/close — scope billing:write
qty or percentage (a decimal string, greater than 0 and at most 100); neither closes
the whole position. Answers the closing trading_order. Use the position’s own symbol (BTCUSD).
Errors
All codes are the standard ones.Audit
Connect started, connected, disconnected, order placed, order canceled and position closed each write an audit log entry (trading_connection.connect_started,
trading_connection.connected, trading_connection.disconnected, trading_order.placed,
trading_order.canceled, trading_position.closed). Each names the env; an order entry also names the
symbol, side, type and size, and a close names the symbol and the amount. No entry carries a token, an
envelope or an authorization state.