# API reference

The REST API behind Norr's MCP tools. Every /v1 route needs the vault's agent token as `Authorization: Bearer <token>`; a token is scoped to one vault and no route can withdraw, change rules or choose a destination. The MCP endpoint (`/mcp` on the MCP host) exposes the same operations as tools. /health, /attestation and /info are public.

The machine-readable spec is [openapi.json](/openapi.json) (OpenAPI 3.1.0); this page is generated from it.

## Servers

- `https://api.norr.finance`: Hosted service. TLS terminates inside the attested CVM (check the certificate with scripts/verify-domain.mts). Runs on demand during the pilot.
- `https://85e1d6a557dae81985c9e30e28270f91bee5c0e9-8787.dstack-pha-prod5.phala.network`: The same CVM through Phala's gateway.
- `http://localhost:8787`: Local mock mode (pnpm dev:mock): fixed prices, no funds.

## Authentication

Send the vault's agent token on every `/v1` request: `Authorization: Bearer <agent token>`. Create one in the dashboard (Connect). A token works for one vault only.

## Assets

What can be traded, and what is known about it.

### GET /v1/assets

**List tradable assets.** Crypto by symbol and chain; tokenized stocks and ETFs by ticker, with every issuer that offers it and the smallest order that has been quoting. MCP tool: list_assets.

| Query parameter | Type | |
|---|---|---|
| `class` | `crypto`, `equity` | Only crypto, or only tokenized stocks and ETFs. |

Response `200` (`AssetList`):

| Field | Type | |
|---|---|---|
| `crypto` | object[] |  |
| `equities` | object[] |  |
| `equityNotice` | string |  |
| `equityMinimumNote` | string |  |
| `notice` | string |  |

Other responses: `400`, `401`.

### GET /v1/assets/check

**Risk or eligibility verdict for one asset.** Crypto: Scam Shield's risk level with reasons. Stocks: whether this vault may trade them, and the notice. Also any active NEAR Intents security incident touching the asset. MCP tool: check_asset.

| Query parameter | Type | |
|---|---|---|
| `asset` | string | required. A symbol (SOL), a ticker (NVDA: routed to the best-priced issuer) or a NEAR Intents asset id. |

Response `200` (`AssetCheck`):

| Field | Type | |
|---|---|---|
| `found` | boolean | required |
| `message` | string |  |
| `class` | `crypto`, `equity` |  |
| `ticker` | string |  |
| `versions` | object[] |  |
| `eligible` | boolean |  |
| `notice` | string |  |

Other responses: `400`, `401`.

### GET /v1/market

**Market data for one asset.** Listed price and, for crypto, the read-only signals Scam Shield scores (liquidity, market cap, age) with its risk level. For stocks: price per issuer and the quoting minimum. Facts, not advice. MCP tool: get_market_data.

| Query parameter | Type | |
|---|---|---|
| `asset` | string | required. A symbol (SOL), a ticker (NVDA: routed to the best-priced issuer) or a NEAR Intents asset id. |

Response `200` (`MarketData`):

| Field | Type | |
|---|---|---|
| `found` | boolean | required |
| `message` | string |  |
| `class` | `crypto`, `equity` |  |
| `ticker` | string |  |
| `source` | string |  |
| `versions` | object[] |  |

Other responses: `400`, `401`.

## Trading

Price a trade (dry run) or request one.

### POST /v1/quote

**Price a trade without trading.** Dry run: no funds move. Runs the same checks as a trade, so a trade that would be blocked returns the same blocked decision. MCP tool: get_quote.

Request body (`TradeInput`):

| Field | Type | |
|---|---|---|
| `side` | `buy`, `sell` | required |
| `asset` | string | required |
| `amountUsd` | number | required |
| `reason` | string | The agent's reasoning, sealed to the owner's key; the service cannot read it back. |

Response `200` (`Quote or Blocked`):

One of: Quote

One of: Blocked

Other responses: `400`, `401`.

### POST /v1/trade

**Request a trade.** Checks the rules and the asset's risk, quotes, executes through NEAR Intents (in the confidential layer when the vault's funds are there), and verifies the result. Funds always stay in the vault. A blocked answer names the rule; adjust the request instead of resending it. MCP tool: trade.

Request body (`TradeInput`):

| Field | Type | |
|---|---|---|
| `side` | `buy`, `sell` | required |
| `asset` | string | required |
| `amountUsd` | number | required |
| `reason` | string | The agent's reasoning, sealed to the owner's key; the service cannot read it back. |

Response `200` (`Decision`):

One of: Executed

One of: Blocked

One of: PendingApproval

One of: Failed

Other responses: `400`, `401`.

## Vault

Holdings, rules, results and history.

### GET /v1/portfolio

**Holdings in USD.** Every holding with its layer (public or confidential), valued in USD, and the split between crypto and tokenized stocks. MCP tool: get_portfolio.

Response `200` (`Portfolio`):

| Field | Type | |
|---|---|---|
| `holdings` | object[] | required |
| `totalUsd` | number | required |
| `byClass` | object | required |

Other responses: `401`, `503`.

### GET /v1/policy

**The rules the agent must stay within.** Planning view of the vault's rules, including what is left of today's spending cap. MCP tool: get_policy.

Response `200` (`PolicyView`):

| Field | Type | |
|---|---|---|
| `maxTradeUsd` | number | required |
| `dailySpendCapUsd` | number | required |
| `spentTodayUsd` | number | required |
| `spendRemainingTodayUsd` | number | required |
| `dailyLossCapUsd` | number | required |
| `maxEquityPct` | number | required |
| `allowedCrypto` | "any_safe" or string[] | required |
| `allowedEquities` | string[] | required |
| `cooldownSec` | number | required |
| `allowMediumRisk` | boolean | required |
| `equitiesEnabled` | boolean | required |
| `privacy` | `public`, `prefer`, `require` | required |
| `paused` | boolean | required |

Other responses: `401`.

### GET /v1/pnl

**Profit and loss since the first trade.** Per asset and in total: realized (sells at average cost) and unrealized (what is still held from Norr's buys, at today's listed price). Deposits have no known cost and are not counted. MCP tool: get_pnl.

Response `200` (`Pnl`):

| Field | Type | |
|---|---|---|
| `since` | string or null | required |
| `sinceMs` | number or null |  |
| `scope` | string | required |
| `assets` | object[] | required |
| `totals` | object | required |
| `unpriced` | string[] | required |

Other responses: `401`.

### GET /v1/activity

**Recent requests and outcomes.** The last 20 trade requests, newest first. Swaps waiting for the owner's approval are settled here once approved. MCP tool: get_activity.

Response `200` (`Activity`):

| Field | Type | |
|---|---|---|
| `activity` | object[] | required |

Other responses: `401`.

## Public

Health and attestation; no token.

### GET /health

**Is the service up.**

No token needed.

Response `200` (`object`):

| Field | Type | |
|---|---|---|
| `ok` | boolean | required |
| `mock` | boolean | required |
| `pricing` | `ready`, `retrying` | required |
| `mockOwner` | string | Mock mode only. |

### GET /attestation

**A fresh TDX quote bound to your nonce.** The quote's report data is your nonce, padded with zeros. Check it with the dashboard's Verify screen or scripts/verify-attestation.mts.

No token needed.

| Query parameter | Type | |
|---|---|---|
| `nonce` | string | required. 1 to 64 bytes of hex. |

Response `200` (`object`):

| Field | Type | |
|---|---|---|
| `attested` | true | required |
| `nonce` | string | required |
| `quote` | string | required. Hex TDX quote. |
| `eventLog` |  |  |
| `commit` | string or null |  |

Other responses: `400`, `429`, `503`.

### GET /info

**The measured app configuration.**

No token needed.

Response `200` (`object`):

| Field | Type | |
|---|---|---|
| `attested` | true | required |
| `appId` | string |  |
| `composeHash` | string |  |
| `appCompose` | string |  |
| `commit` | string or null |  |

Other responses: `503`.

## Decisions

What `POST /v1/trade` answers (`Decision`), by `status`:

**Executed**

| Field | Type | |
|---|---|---|
| `status` | "executed" | required |
| `txRef` | string | required |
| `filled` | object | required |
| `venue` | `public`, `confidential` |  |
| `verification` | `independent`, `custody-reported` |  |
| `warnings` | string[] |  |

**Blocked**

| Field | Type | |
|---|---|---|
| `status` | "blocked" | required |
| `code` | BlockCode | required |
| `message` | string | required |
| `suggestedUsd` | number |  |

**PendingApproval**

| Field | Type | |
|---|---|---|
| `status` | "pending_approval" | required |
| `requestId` | string | required |
| `message` | string | required |

**Failed**

| Field | Type | |
|---|---|---|
| `status` | `failed`, `refunded` | required |
| `message` | string | required |

Block codes and what they mean: [MCP tools](/docs/mcp-tools/#block-codes).
