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

> Connect a persona's brokerage account, read it, and place orders — client.trading.

A persona can connect **its own** brokerage account — `paper` or `live` — over OAuth. Once connected, the account can be read and traded from the API, and an agent whose session selects that persona gets trading tools whose orders each wait for a person's approval. Naive Managed Agents is not a broker or an investment adviser and holds no customer money; it keeps one sealed access token per persona and env, which no response ever returns. Nothing Naive or an agent says about a symbol is investment advice. See [Trading](https://vetta.sh/docs/identity/trading.md) for the model and the agent tools.

Every method takes the persona first (an `idn_` id or its name). `env` is optional on the reads and writes: with one active connection it is implied, with both connected it is required (`validation_failed` on `env`). Money is integer micro-USD; quantities are decimal strings such as `"0.0125"`. Writes need a key with the `billing:write` scope; reads take any key. Connecting a `live` account, and placing, canceling or closing on one, also needs a live `sk_live_` key (`forbidden` otherwise).

## connect

```ts theme={"system"}
client.trading.connect(identity: string, body: { env: "paper" | "live" }): Promise<TradingConnect>
```

`POST /v1/identities/{id}/trading/connect`. Answers `{ object: "trading_connect", authorize_url, env, disclosure }`. Show `disclosure` to the person **before** they open `authorize_url`. After they approve, the brokerage sends their browser to `GET /v1/trading/oauth/callback`, which forwards to the dashboard; there the signed-in person finishes with `client.trading.complete({ state, code })` (`POST /v1/trading/oauth/complete`) and lands on the persona's page with `?trading=connected` (or `?trading=failed`). Only a state minted in the caller's own organization completes. The link works once and expires after 15 minutes. Reconnecting an `active` env keeps it `active` until the new authorization lands.

```ts theme={"system"}
const { disclosure, authorize_url } = await client.trading.connect("ava", { env: "paper" });
console.log(disclosure);
console.log(authorize_url); // open in a browser
```

## connections

```ts theme={"system"}
client.trading.connections(identity: string): Promise<{ object: "list"; data: TradingConnection[] }>
```

`GET /v1/identities/{id}/trading/connections`. Both envs' rows: `status` is `pending`, `active`, `expired` (the brokerage revoked access — connect again) or `failed`, with `status_reason` saying why.

## disconnect

```ts theme={"system"}
client.trading.disconnect(identity: string, env: "paper" | "live"): Promise<{ object: "trading_connection"; identity_id: string; env: string; deleted: true }>
```

`DELETE /v1/identities/{id}/trading/connections/{env}`. Deletes the connection and its token.

## account / positions

```ts theme={"system"}
client.trading.account(identity: string, query?: { env?: "paper" | "live" }): Promise<TradingAccount>
client.trading.positions(identity: string, query?: { env?: "paper" | "live" }): Promise<{ object: "list"; data: TradingPosition[] }>
```

`GET /v1/identities/{id}/trading/account` answers `cash_micro_usd`, `buying_power_micro_usd` and `equity_micro_usd`. `GET /v1/identities/{id}/trading/positions` lists open positions; a crypto position's symbol is written without a slash (`BTCUSD`).

## orders / order

```ts theme={"system"}
client.trading.orders(identity: string, query?: { env?: "paper" | "live"; status?: "open" | "closed" | "all"; limit?: number }): Promise<{ object: "list"; data: TradingOrder[] }>
client.trading.order(identity: string, orderId: string, query?: { env?: "paper" | "live" }): Promise<TradingOrder>
```

`GET /v1/identities/{id}/trading/orders` (default `status: "open"`, `limit` 1–500, default 50) and `GET /v1/identities/{id}/trading/orders/{order_id}`. An order's `status` is the brokerage's own (`new`, `filled`, `canceled`, …).

## quotes

```ts theme={"system"}
client.trading.quotes(identity: string, query: { symbols: string[]; asset_class?: "us_equity" | "crypto"; env?: "paper" | "live" }): Promise<{ object: "list"; data: TradingQuote[] }>
```

`GET /v1/identities/{id}/trading/quotes`. The latest bid and ask for 1–100 symbols of one asset class (default `us_equity`). Crypto symbols are slashed: `BTC/USD`.

## placeOrder

```ts theme={"system"}
client.trading.placeOrder(identity: string, body: PlaceTradingOrder): Promise<TradingOrder>
```

`POST /v1/identities/{id}/trading/orders`, answering `201`. `type` (`market`, `limit`, `stop`, `stop_limit`) and `time_in_force` (`day`, `gtc`, `ioc`, `fok`, `opg`, `cls`) are required — there is no default. Give exactly one of `qty` or `notional_micro_usd`; `limit_price_micro_usd` is required on `limit` and `stop_limit` orders and refused on the others, and `stop_price_micro_usd` likewise on `stop` and `stop_limit`. The request's `Idempotency-Key` becomes the order's `client_order_id` unless the body names one. The client sends a new key on every call, so to make a retry safe, pass your own `client_order_id`: the brokerage refuses a second order with the same one. Venue rules — a minimum notional, which time-in-force a coin accepts — are the brokerage's: a refusal comes back as `validation_failed` or `forbidden` with its reason.

```ts theme={"system"}
const order = await client.trading.placeOrder("ava", {
  env: "paper",
  symbol: "AAPL",
  side: "buy",
  type: "limit",
  time_in_force: "day",
  qty: "1",
  limit_price_micro_usd: 180_250_000, // $180.25
});
```

## cancelOrder

```ts theme={"system"}
client.trading.cancelOrder(identity: string, orderId: string, query?: { env?: "paper" | "live" }): Promise<{ id: string; object: "trading_order"; deleted: true }>
```

`DELETE /v1/identities/{id}/trading/orders/{order_id}`.

## closePosition

```ts theme={"system"}
client.trading.closePosition(identity: string, symbol: string, body?: { env?: "paper" | "live"; qty?: string; percentage?: string }): Promise<TradingOrder>
```

`POST /v1/identities/{id}/trading/positions/{symbol}/close`. At most one of `qty` or `percentage` (greater than 0, at most 100); neither closes the whole position. Answers the closing order.

## Errors

| Code | When |
| - | - |
| `feature_not_configured` (501) | This deployment has no brokerage app. |
| `state_conflict` (409) | No active connection for that persona and env — connect first; or the brokerage revoked access, which marks the connection `expired`. |
| `validation_failed` (400) | A malformed body, both envs connected with no `env`, or the brokerage refused the order. |
| `forbidden` (403) | The key lacks `billing:write`, a `live` write was made without an `sk_live_` key, or the brokerage refused on an account rule. The connection stays `active`. |
| `rate_limited` (429) / `provider_error` (502) | The brokerage is busy or did not answer; retry later. |


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