> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Trading

> A brokerage account an identity connects over OAuth, read and traded through — paper or live.

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](https://vetta.sh/docs/identity/trading.md) 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.

> [!NOTE]
>
> 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.

> [!NOTE]
>
> 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

```json theme={"system"}
{
  "object": "trading_connection",
  "identity_id": "idn_01j9x2k3m4n5p6q7r8s9t0v1w2",
  "env": "paper",
  "status": "active",
  "account_number": "PA3XXXXXXX",
  "scope": "account:write trading data",
  "status_reason": null,
  "created_at": "2026-10-07T10:00:00Z",
  "updated_at": "2026-10-07T10:00:04Z"
}
```

`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`

```json theme={"system"}
{ "env": "paper" }
```

```json theme={"system"}
{
  "object": "trading_connect",
  "authorize_url": "https://…/oauth/authorize?…",
  "env": "paper",
  "disclosure": "By allowing Naïve to access your brokerage account, you are granting Naïve access to your account information and authorization to place transactions in your account at your direction. …"
}
```

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](https://vetta.sh/docs/api/mcp.md): 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`

```json theme={"system"}
{ "state": "…", "code": "…" }
```

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`

```json theme={"system"}
{
  "object": "trading_account",
  "env": "paper",
  "account_number": "PA3XXXXXXX",
  "status": "ACTIVE",
  "cash_micro_usd": 100000000000,
  "buying_power_micro_usd": 200000000000,
  "equity_micro_usd": 100000000000
}
```

`status` is the brokerage's own account status, verbatim.

## Positions

`GET /v1/identities/{identity}/trading/positions?env=` — scope `agents:read`

```json theme={"system"}
{
  "object": "list",
  "data": [
    {
      "object": "trading_position",
      "symbol": "AAPL",
      "asset_class": "us_equity",
      "side": "long",
      "qty": "2",
      "avg_entry_price_micro_usd": 180000000,
      "current_price_micro_usd": 190060000,
      "market_value_micro_usd": 380120000,
      "unrealized_pl_micro_usd": 20120000
    }
  ]
}
```

A crypto position's symbol is written without a slash (`BTCUSD`). `asset_class` is `us_equity`,
`crypto` or `us_option`.

## The order object

```json theme={"system"}
{
  "object": "trading_order",
  "id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
  "client_order_id": "req_2c8f…",
  "symbol": "AAPL",
  "asset_class": "us_equity",
  "side": "buy",
  "type": "limit",
  "time_in_force": "day",
  "qty": "1",
  "notional_micro_usd": null,
  "filled_qty": "0",
  "filled_avg_price_micro_usd": null,
  "limit_price_micro_usd": 180250000,
  "stop_price_micro_usd": null,
  "extended_hours": false,
  "status": "new",
  "created_at": "2026-10-07T14:30:00Z",
  "filled_at": null,
  "canceled_at": null
}
```

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

```json theme={"system"}
{
  "object": "list",
  "data": [
    {
      "object": "trading_quote",
      "symbol": "AAPL",
      "asset_class": "us_equity",
      "bid_price_micro_usd": 190050000,
      "ask_price_micro_usd": 190070000,
      "bid_size": "100",
      "ask_size": "200",
      "quoted_at": "2026-10-07T14:30:00Z"
    }
  ]
}
```

A price finer than a micro-dollar is rounded half-up.

## Place an order

`POST /v1/identities/{identity}/trading/orders` — scope `billing:write`

```json theme={"system"}
{
  "env": "paper",
  "symbol": "AAPL",
  "side": "buy",
  "type": "limit",
  "time_in_force": "day",
  "qty": "1",
  "limit_price_micro_usd": 180250000
}
```

| Field | Rule |
| - | - |
| `symbol` | Required. Option symbols are accepted as they are. |
| `side` | `buy` or `sell`. |
| `type` | `market`, `limit`, `stop` or `stop_limit`. Required — there is no default. |
| `time_in_force` | `day`, `gtc`, `ioc`, `fok`, `opg` or `cls`. Required. |
| `qty` / `notional_micro_usd` | Exactly one. |
| `limit_price_micro_usd` | Required on `limit` and `stop_limit`, refused on the others. |
| `stop_price_micro_usd` | Required on `stop` and `stop_limit`, refused on the others. |
| `extended_hours` | Optional; allow a fill outside regular hours. |
| `client_order_id` | Optional, up to 128 characters. |

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](https://vetta.sh/docs/identity/trading.md#agent-tools) 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`

```json theme={"system"}
{ "env": "paper", "percentage": "50" }
```

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](https://vetta.sh/docs/api/errors.md).

| Code | When |
| - | - |
| `feature_not_configured` (501) | This deployment has no brokerage app. |
| `state_conflict` (409) | No `active` connection for that identity and env — connect first; the brokerage revoked access ("reconnect required"), which marks the connection `expired`; or the connection changed (a disconnect or a newer connect) while an authorization was being completed — connect again. |
| `validation_failed` (400) | A malformed body, both envs connected with no `env`, or the brokerage refused the request (its reason, shortened). |
| `forbidden` (403) | The key lacks `billing:write`; a `live` connect, completion, order, cancel or close was made without a live `sk_live_` key; or the brokerage refused on an account rule. The connection stays `active`. |
| `not_found` (404) | No such identity in this organization, no such order at the brokerage, or an authorization `state` that is unknown, already used, or from another organization. |
| `rate_limited` (429) / `provider_error` (502) | The brokerage is busy or did not answer, or the stored credential could not be used here (nothing was sent, and the connection is left as it is); retry later. |

## Audit

Connect started, connected, disconnected, order placed, order canceled and position closed each
write an [audit log](https://vetta.sh/docs/api/audit-logs.md) 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`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.