Skip to main content
Every route is nested under an identity. {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
Show 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
The dashboard calls this for you; you only call it if you serve your own return page. Send what the brokerage returned: 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 takes env (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
A crypto position’s symbol is written without a slash (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.
A price finer than a micro-dollar is rounded half-up.

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
At most one of 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.