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

# REST API

> Standard HTTP/JSON access for backends, scripts, custom clients, and API tooling

Use the REST API when you want direct HTTP/JSON access from backends, scripts, custom clients, mobile apps, or API tooling. SDKs are available when you want Monaco auth, signing, and request formatting handled for you.

<Note>
  REST SDKs are available for TypeScript, React, and Rust. See the [TypeScript SDK](/sdk/typescript/intro), [React SDK](/sdk/react/intro), or [Rust REST SDK](/sdk/rust-rest).
</Note>

## Endpoint

```text theme={null}
https://staging.apimonaco.xyz/api
```

## Base URLs

Use the staging base URL for private alpha and testnet integrations:

```text theme={null}
https://staging.apimonaco.xyz
```

## Request Requirements

Every request must include a non-empty `User-Agent` header. Requests that send an empty or absent `User-Agent` are rejected at the edge with `403 Forbidden` before reaching the API.

<Warning>
  Official SDKs set a `User-Agent` automatically. Direct HTTP clients — curl, shell scripts, and generated or custom clients — must set one explicitly, or requests will be rejected.
</Warning>

```bash theme={null}
curl -H 'User-Agent: my-app/1.0' -H 'Accept: application/json' \
  'https://staging.apimonaco.xyz/api/v1/market/pairs?isActive=true&pageSize=100'
```

## Routes

The generated route pages in this section are the source of truth for request parameters, request bodies, responses, and authentication headers. Use the left navigation to browse by endpoint group, or start with the [health check route](/api-reference/health/health-check-endpoint).

## OpenAPI

Every route page in this section is generated from Monaco's OpenAPI specification, so the documented parameters, request bodies, responses, and authentication headers always match the live API.

Public market-data routes can be called without authentication. Account, order, withdrawal, and margin routes require a wallet-authorized ed25519 session key.

## Authentication Flow

Monaco authenticates with a wallet-authorized ed25519 session key. The wallet signs once to authorize a session key; every authenticated request is then signed with that key.

1. **Generate a session key** -> create an ed25519 keypair locally.
2. **Create challenge** -> request a nonce, passing the session public key.
3. **Sign message** -> sign the challenge message with the wallet.
4. **Verify signature** -> Monaco registers the session public key and opens a session.
5. **Sign requests** -> sign each subsequent request with the session private key.

There are no access or refresh tokens.

## Authenticating Requests

Every authenticated request is signed with the session private key. Two protocols are accepted over the same key: the legacy `X-Monaco-*` headers below, and opt-in [RFC 9421 HTTP message signatures](/reference/http-message-signatures). Send one or the other: a request carrying `Signature` or `Signature-Input` alongside any `X-Monaco-*` header is rejected with `401`. `Content-Digest` is not a credential and may accompany a legacy-signed request on its own.

For legacy signing, set three headers:

| Header | Value |
| - | - |
| `X-Monaco-PublicKey` | 64-char lowercase-hex session public key |
| `X-Monaco-Timestamp` | Unix timestamp in milliseconds, within 30 seconds of server time |
| `X-Monaco-Signature` | Hex ed25519 signature over the signing string |

The signing string is built from the request:

```text theme={null}
METHOD\npath?query\ntimestamp_ms\nSHA256_hex(body)
```

* `METHOD` — uppercase HTTP method, such as `GET` or `POST`.
* `path?query` — request path including query string, exactly as sent.
* `timestamp_ms` — the same value sent in `X-Monaco-Timestamp`.
* `SHA256_hex(body)` — hex SHA-256 of the raw request body, or of the empty byte string when there is no body.

For RFC 9421 signing, send `Signature-Input`, `Signature`, and `Content-Digest` instead. Monaco verifies a bounded profile — the label `monaco`, the components `@method`, `@path`, `@query`, `content-digest` in that order, `created` in Unix **seconds** within 30 seconds of server time, and `alg="ed25519"` — and never falls back to legacy verification when it fails. The [HTTP message signatures](/reference/http-message-signatures) reference has the complete profile and signature base.

Because the body hash is signed, a captured request cannot be reused with a modified body, and the timestamp bounds how long it remains valid. Freshness is an anti-stale control, not an anti-replay one: an identical signed request can be accepted again inside the window, and Monaco keeps no nonce or replay cache. Where an endpoint takes an `idempotencyKey` — order creation and batch creation do — treat it as required rather than optional on any retry, and reuse the original key and payload. An endpoint without one carries no exactly-once guarantee, so do not blindly retry a write against it. SDKs handle signing automatically. Use direct REST only when you need a custom client.

## Network Configuration

The API base URL determines which network you're targeting. Network configuration (vault contract, chain settings) is handled server-side for security.

* **Staging (Testnet)**: `https://staging.apimonaco.xyz` - Sei Testnet (chain ID 1328)

## Enum Value Formats

Monaco API uses consistent uppercase formatting for all enum values. Use these exact values in your requests:

### Market Types & Trading Modes

* `SPOT` - Spot trading (uppercase)

### Order Types

* `LIMIT` - Limit order (uppercase)
* `MARKET` - Market order (uppercase)

### Order Sides

* `BUY` - Buy order (uppercase)
* `SELL` - Sell order (uppercase)

### Order Status

* `SUBMITTED` - Order submitted to matching engine
* `PARTIALLY_FILLED` - Partially executed
* `FILLED` - Fully executed (match complete, pending settlement)
* `SETTLED` - Settlement initiated
* `SETTLED_ON_CHAIN` - Fully settled on-chain (terminal state)
* `CANCELLED` - Order cancelled
* `EXPIRED` - Order expired (GTC orders expire after 90 days by default)
* `REJECTED` - Order rejected

**Note**: All enum values use uppercase format with underscores for consistency across the API.

## Authentication Endpoints

### 1. Create Authentication Challenge

Creates a unique challenge that must be signed by the user's wallet.

```http theme={null}
POST /api/v1/auth/challenge
```

**Request Body:**

```json theme={null}
{
  "address": "0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5",
  "clientId": "688a21440e304c06b935f743dae88278",
  "sessionPublicKey": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"
}
```

**Response:**

For a margin pair with a recorded open-interest sample, the response also includes the paired quantity and quote-asset notional:

```json theme={null}
{
  "nonce": "abc123def456",
  "message": "Please sign this message to authenticate with Monaco Protocol: abc123def456",
  "expiresAt": 1703875200
}
```

**Request Fields:**

* `address` (string, required) - User's Ethereum wallet address
* `clientId` (string, optional) - Your application's public client ID
* `sessionPublicKey` (string, required) - 64-char lowercase-hex ed25519 session public key generated locally. The returned `message` embeds this key so the wallet signature authorizes it.

**Response Fields:**

* `nonce` (string) - Unique challenge identifier
* `message` (string) - Message to sign with wallet
* `expiresAt` (number) - Challenge expiration timestamp (Unix)

### 2. Verify Signature

Verifies the signed challenge, registers the session public key, and opens a session.

```http theme={null}
POST /api/v1/auth/verify
```

**Request Body:**

```json theme={null}
{
  "address": "0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5",
  "signature": "0x1234567890abcdef...",
  "nonce": "abc123def456",
  "clientId": "688a21440e304c06b935f743dae88278",
  "sessionPublicKey": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"
}
```

**Response:**

```json theme={null}
{
  "expiresAt": 1703878800,
  "user": {
    "id": "user-123",
    "address": "0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5",
    "username": "testuser"
  }
}
```

**Request Fields:**

* `address` (string, required) - Ethereum wallet address that signed the message
* `signature` (string, required) - Hex-encoded signature from wallet
* `nonce` (string, required) - Nonce from the challenge request
* `clientId` (string, optional) - Your application's public client ID
* `sessionPublicKey` (string, required) - Same 64-char hex session public key sent to `/auth/challenge`; the server binds it to the new session
* `referralCode` (string, optional) - TraderCode captured at sign-up; recorded only on the wallet's first sign-in to this application, and silently ignored if unknown

**Response Fields:**

* `expiresAt` (number) - Session expiration timestamp (Unix)
* `user` (object) - Authenticated user information (id, address, username)

No tokens are returned. Sign subsequent requests with the session private key matching `sessionPublicKey` — see [Authenticating Requests](#authenticating-requests).

A deactivated signing account is refused with `403` and `code` `ACCOUNT_DEACTIVATED` in the error envelope.

### 3. Refresh Session

Refreshes the current session's expiry. For a delegated session, the exact owner/agent delegation must still be active, and the returned expiry cannot exceed the delegation expiry; it may move earlier if the owner shortened the delegation. The request carries no body and is authenticated with the standard signing headers.

```http theme={null}
POST /api/v1/auth/refresh
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "expiresAt": 1703882400
}
```

### 4. Revoke Session

Revokes the current session. The request carries no body and is authenticated with the standard signing headers. The user will need to authenticate again.

```http theme={null}
POST /api/v1/auth/revoke
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "message": "Session revoked successfully"
}
```

**Note**: This endpoint revokes the session identified by the signing key on the request.

## Account Endpoints

All account endpoints require a signed request — see [Authenticating Requests](#authenticating-requests).

### 5. Get User Profile

Retrieves the current user's core profile metadata. Does not include balances, movements, or orders — use the dedicated endpoints for those.

```http theme={null}
GET /api/v1/accounts/me
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "id": "user-123",
  "address": "0x742d35Cc6634C0532925a3b8D1B9d7c2bd34e8Dc",
  "username": null,
  "accountType": "master",
  "canWithdraw": true,
  "createdAt": "2024-01-15T10:30:00Z"
}
```

**Fields:**

* `id` (string) - User unique identifier
* `address` (string) - User wallet address
* `username` (string, optional) - User's display name
* `accountType` (string) - Account type: "master" or "sub"
* `canWithdraw` (boolean) - Whether user can withdraw
* `createdAt` (string) - Account creation timestamp (ISO 8601, UTC)

### 6. Get User Balances

Retrieves the current user's token balances with pagination support.

```http theme={null}
GET /api/v1/accounts/balances?pageSize=20
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Query Parameters:**

* `pageSize` (number, optional) - Items per page, 1 to 1000 (default 20); a larger value is rejected
* `pageToken` (string, optional) - Cursor from a previous response's `nextPageToken`; omit or send `""` to start the walk. There is no `page`: sending one is a `400`

**Response:**

```json theme={null}
{
  "balances": [
    {
      "assetId": "123e4567-e89b-12d3-a456-426614174000",
      "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
      "symbol": "USDC",
      "decimals": 6,
      "availableBalance": "1000.50",
      "lockedBalance": "50.25",
      "totalBalance": "1050.75",
      "availableBalanceRaw": "1000500000",
      "lockedBalanceRaw": "50250000"
    }
  ],
  "pageSize": 20,
  "total": 5,
  "totalPages": 1,
  "nextPageToken": ""
}
```

**Balance Fields:**

* `assetId` (string) - Asset UUID
* `token` (string) - Token contract address
* `symbol` (string | null) - Token symbol (if available)
* `decimals` (number) - Token decimals
* `availableBalance` (string) - Available balance for trading (normalized)
* `lockedBalance` (string) - Balance locked in open orders (normalized)
* `totalBalance` (string) - Total balance (available + locked)
* `availableBalanceRaw` (string) - Available balance in raw format (smallest unit)
* `lockedBalanceRaw` (string) - Locked balance in raw format (smallest unit)

### 7. Get User Balance by Asset

Retrieves the current user's balance for a specific asset. Returns zero balances if the user has no balance for a valid asset.

```http theme={null}
GET /api/v1/accounts/balances/{assetId}
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Path Parameters:**

* `assetId` (string) - Asset UUID (get from trading pair's `baseAssetId` or `quoteAssetId`)

**Response:**

```json theme={null}
{
  "assetId": "123e4567-e89b-12d3-a456-426614174000",
  "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
  "symbol": "USDC",
  "decimals": 6,
  "availableBalance": "1000.50",
  "lockedBalance": "50.25",
  "totalBalance": "1050.75",
  "availableBalanceRaw": "1000500000",
  "lockedBalanceRaw": "50250000"
}
```

**Error Responses:**

* `401 Unauthorized` - Authentication required
* `404 Not Found` - Asset not found

### 8. Get User Movements

Retrieves the current user's ledger movements (transaction history) with pagination and filtering support.

```http theme={null}
GET /api/v1/accounts/movements?pageSize=20&entryType=CREDIT&transactionType=DEPOSIT
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Query Parameters:**

* `pageSize` (number, optional) - Items per page, 1 to 1000 (default: 20)
* `pageToken` (string, optional) - Pagination cursor. Omit it or send `""` to start the walk; otherwise pass the previous response's `nextPageToken`. Page-number pagination was removed, so a request carrying `page` is rejected with `400`
* `orderBy` (string, optional) - Sort direction for `createdAt`: `ASC` or `DESC` (default `DESC`)
* `entryType` (string, optional) - Filter by entry type
* `transactionType` (string, optional) - Filter by transaction type
* `assetId` (UUID, optional) - Filter by asset ID

**Response:**

```json theme={null}
{
  "movements": [
    {
      "id": "mov-123",
      "amount": "100.50",
      "balanceBefore": "900.00",
      "balanceAfter": "1000.50",
      "entryType": "CREDIT",
      "transactionType": "DEPOSIT",
      "description": "USDC deposit",
      "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
      "blockNumber": 18500000,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "pageSize": 20,
  "total": 45,
  "totalPages": 3,
  "nextPageToken": "",
  "totalCapped": false
}
```

`total` and `totalPages` are bounded by the server count ceiling; `totalCapped` is `true` when more than that matched, so they are a lower bound. Walk the listing by following `nextPageToken` until it comes back empty — each page over-fetches one row, so an empty token is definitive. Do not drive the loop off `total`: a user past the ceiling would stop early.

**Movement Fields:**

* `entryType` (string) - Entry type
* `transactionType` (string) - Transaction type

### 9. List Sub-Accounts with Balances

Lists all sub-accounts with their token balances for a master account.

```http theme={null}
GET /api/v1/accounts/sub-accounts
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "subAccounts": [
    {
      "id": "sub-123",
      "address": "0x...",
      "username": null,
      "canWithdraw": true,
      "createdAt": "2024-01-15T10:30:00Z",
      "balances": [
        {
          "token": "0x...",
          "symbol": "USDC",
          "decimals": 6,
          "availableBalance": "500.00",
          "lockedBalance": "0.00",
          "totalBalance": "500.00",
          "availableBalanceRaw": "500000000",
          "lockedBalanceRaw": "0"
        }
      ]
    }
  ],
  "total": 1
}
```

**Error Responses:**

* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Only master accounts can view sub-accounts

## Sub-Account Limits Endpoints

<Warning>
  Sub-account spending limits are not enforced:

  * Create and update return `400 Bad Request` with the message `Sub-account spending limits are not enforced and can no longer be configured`.
  * Get and delete act only on limits created before that change.
  * All four endpoints are scheduled for removal.
</Warning>

Endpoints for managing trading limits on sub-accounts. Only master accounts can manage limits for their sub-accounts.

### 10. Create Sub-Account Limit

Creates a new limit for a sub-account.

```http theme={null}
POST /api/v1/accounts/sub-accounts/limits
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body:**

```json theme={null}
{
  "subAccountId": "123e4567-e89b-12d3-a456-426614174000",
  "assetId": "123e4567-e89b-12d3-a456-426614174001",
  "maxAmount": "10000.00",
  "dailyLimit": "1000.00"
}
```

**Request Fields:**

* `subAccountId` (string, required) - Sub-account UUID
* `assetId` (string, required) - Asset UUID (get from trading pair's `baseAssetId` or `quoteAssetId`)
* `maxAmount` (string, required) - Maximum amount allowed for the sub-account
* `dailyLimit` (string, optional) - Daily limit amount

**Response:**

```json theme={null}
{
  "id": "limit-123",
  "subAccountId": "123e4567-e89b-12d3-a456-426614174000",
  "masterAccountId": "master-456",
  "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
  "maxAmount": "10000.00",
  "dailyLimit": "1000.00",
  "usedToday": "0.00",
  "lastResetAt": null,
  "isActive": true,
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:00Z"
}
```

**Response Fields:**

* `token` (string) - Token contract address (resolved from assetId)
* `maxAmount` (string) - Maximum amount allowed
* `dailyLimit` (string, optional) - Daily limit amount
* `usedToday` (string) - Amount used today
* `lastResetAt` (string, optional) - Last daily reset timestamp

**Error Responses:**

* `400 Bad Request` - Invalid request or limit already exists
* `403 Forbidden` - Only master accounts can set sub-account limits
* `404 Not Found` - Sub-account relationship not found or asset not found

### 11. Get Sub-Account Limits

Gets all limits for a sub-account.

```http theme={null}
GET /api/v1/accounts/sub-accounts/{subAccountId}/limits
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Path Parameters:**

* `subAccountId` (string) - Sub-account UUID

**Response:**

```json theme={null}
[
  {
    "id": "limit-123",
    "subAccountId": "123e4567-e89b-12d3-a456-426614174000",
    "masterAccountId": "master-456",
    "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
    "maxAmount": "10000.00",
    "dailyLimit": "1000.00",
    "usedToday": "250.00",
    "lastResetAt": "2024-01-15T00:00:00Z",
    "isActive": true,
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T10:30:00Z"
  }
]
```

### 12. Update Sub-Account Limit

Updates an existing limit for a sub-account.

```http theme={null}
PUT /api/v1/accounts/sub-accounts/{subAccountId}/limits/{assetId}
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Path Parameters:**

* `subAccountId` (string) - Sub-account UUID
* `assetId` (string) - Asset UUID (get from trading pair's `baseAssetId` or `quoteAssetId`)

**Request Body:**

```json theme={null}
{
  "maxAmount": "15000.00",
  "dailyLimit": "2000.00",
  "isActive": true
}
```

**Response:**

```json theme={null}
{
  "id": "limit-123",
  "subAccountId": "123e4567-e89b-12d3-a456-426614174000",
  "masterAccountId": "master-456",
  "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
  "maxAmount": "15000.00",
  "dailyLimit": "2000.00",
  "usedToday": "250.00",
  "lastResetAt": "2024-01-15T00:00:00Z",
  "isActive": true,
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T11:00:00Z"
}
```

### 13. Delete Sub-Account Limit

Deletes a limit for a sub-account.

```http theme={null}
DELETE /api/v1/accounts/sub-accounts/{subAccountId}/limits/{assetId}
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Path Parameters:**

* `subAccountId` (string) - Sub-account UUID
* `assetId` (string) - Asset UUID (get from trading pair's `baseAssetId` or `quoteAssetId`)

**Response:**

```http theme={null}
204 No Content
```

## Application Configuration Endpoint

### 14. Get Application Configuration

Returns the configuration for the authenticated application.

```http theme={null}
GET /api/v1/applications/config
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "id": "app-123",
  "name": "My Trading App",
  "allowedOrigins": ["https://myapp.com"],
  "webhookUrl": "https://myapp.com/webhook",
  "vaultContractAddress": "0x1234567890abcdef1234567890abcdef12345678"
}
```

## Application Reporting Endpoints

These endpoints are public: they need no session or key. Each one selects its application with the required `clientId` query parameter — the same public client ID your frontend logs in with. An unknown or inactive client ID returns `404`; a missing one returns `400`.

They are rate limited per client IP: 60 requests per minute sustained, with a burst of 120. Over the limit the API returns `429` with a `Retry-After` header.

The four listings use cursor pagination. Omit `pageToken` (or send `""`) to start, then pass each response's `nextPageToken` until it comes back empty. `pageSize` accepts 1 to 1000 (default 20). There is no `page` parameter; sending one returns `400`. The filters must match the request that produced the token.

### 15. List Application Users

Returns the application's users, ordered by user id ascending.

```http theme={null}
GET /api/v1/applications/users?clientId=YOUR_CLIENT_ID&pageSize=20
```

**Query Parameters:**

* `clientId` (string, required) - The application's public client ID
* `pageSize` (number, optional) - Items per page, 1 to 1000 (default 20); a larger value is rejected
* `pageToken` (string, optional) - Cursor from a previous response's `nextPageToken`; omit or send `""` to start the walk. There is no `page`: sending one is a `400`
* `isActive` (boolean, optional) - Filter by active status
* `accountType` (string, optional) - Filter by account type: "master" or "sub"
* `address` (string, optional) - Filter by wallet address (partial match)

**Response:**

```json theme={null}
{
  "users": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "address": "0x742d35Cc6634C0532925a3b8D1B9d7c2bd34e8Dc",
      "accountType": "master",
      "isActive": true,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "pageSize": 20,
  "total": 150,
  "totalPages": 8,
  "nextPageToken": "eyJ2IjoxLCJ0cyI6MCwiaWQiOiIuLi4ifQ",
  "totalCapped": false
}
```

`total` and `totalPages` are bounded by a server count ceiling; `totalCapped` is `true` when more users matched than that.

### 16. List Application Balances

Returns the balances of the application's users, ordered by balance id ascending.

```http theme={null}
GET /api/v1/applications/balances?clientId=YOUR_CLIENT_ID&pageSize=20
```

**Query Parameters:**

* `clientId` (string, required) - The application's public client ID
* `pageSize` (number, optional) - Items per page, 1 to 1000 (default 20); a larger value is rejected
* `pageToken` (string, optional) - Cursor from a previous response's `nextPageToken`; omit or send `""` to start the walk. There is no `page`: sending one is a `400`
* `userId` (string, optional) - Filter by user ID
* `assetId` (string, optional) - Filter by asset UUID

**Response:**

```json theme={null}
{
  "balances": [
    {
      "userId": "123e4567-e89b-12d3-a456-426614174000",
      "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
      "symbol": "USDC",
      "availableBalance": "1000.50",
      "lockedBalance": "50.25",
      "totalBalance": "1050.75"
    }
  ],
  "pageSize": 20,
  "total": 500,
  "totalPages": 25,
  "nextPageToken": "eyJ2IjoxLCJ0cyI6MCwiaWQiOiIuLi4ifQ",
  "totalCapped": false
}
```

### 17. List Application Orders

Returns the orders placed by the application's users, newest first, across live and archived history.

```http theme={null}
GET /api/v1/applications/orders?clientId=YOUR_CLIENT_ID&pageSize=20
```

**Query Parameters:**

* `clientId` (string, required) - The application's public client ID
* `pageSize` (number, optional) - Items per page, 1 to 1000 (default 20); a larger value is rejected
* `pageToken` (string, optional) - Cursor from a previous response's `nextPageToken`; omit or send `""` to start the walk. There is no `page`: sending one is a `400`
* `status` (string, optional) - Filter by order status
* `tradingPairId` (string, optional) - Filter by trading pair UUID
* `userId` (string, optional) - Filter by user UUID
* `side` (string, optional) - Filter by order side: "BUY" or "SELL"
* `orderType` (string, optional) - Filter by order type, e.g. "LIMIT" or "MARKET"

**Response:**

```json theme={null}
{
  "orders": [
    {
      "id": "ord_1234567890abcdef",
      "userId": "123e4567-e89b-12d3-a456-426614174000",
      "tradingPairId": "123e4567-e89b-12d3-a456-426614174000",
      "orderType": "LIMIT",
      "side": "BUY",
      "price": "2000.50",
      "quantity": "1.5",
      "filledQuantity": "0.5",
      "status": "PARTIALLY_FILLED",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "pageSize": 20,
  "nextPageToken": "eyJ2IjoxLCJ0cyI6MCwiaWQiOiIuLi4ifQ"
}
```

There is no `total` or `totalPages`: the orders relation is too large to count behind a filter. Walk the listing by following `nextPageToken` until it comes back empty.

### 18. List Application Movements

Returns the ledger movements (deposits, withdrawals, trades, etc.) of the application's users, newest first.

```http theme={null}
GET /api/v1/applications/movements?clientId=YOUR_CLIENT_ID&pageSize=20
```

**Query Parameters:**

* `clientId` (string, required) - The application's public client ID
* `pageSize` (number, optional) - Items per page, 1 to 1000 (default 20); a larger value is rejected
* `pageToken` (string, optional) - Cursor from a previous response's `nextPageToken`; omit or send `""` to start the walk. There is no `page`: sending one is a `400`
* `userId` (string, optional) - Filter by user ID
* `entryType` (string, optional) - Filter by entry type
* `transactionType` (string, optional) - Filter by transaction type
* `assetId` (string, optional) - Filter by asset UUID

**Response:**

```json theme={null}
{
  "movements": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "userId": "123e4567-e89b-12d3-a456-426614174000",
      "amount": "100.50",
      "balanceBefore": "900.00",
      "balanceAfter": "1000.50",
      "entryType": "CREDIT",
      "transactionType": "DEPOSIT",
      "description": "USDC deposit",
      "token": "0x23e459c24c4925ba89205c400dceca7abc11723d",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "pageSize": 20,
  "nextPageToken": "eyJ2IjoxLCJ0cyI6MCwiaWQiOiIuLi4ifQ"
}
```

There is no `total` or `totalPages`: the ledger relation is too large to count behind a filter. Walk the listing by following `nextPageToken` until it comes back empty.

### 19. Get Application Stats

Returns aggregate volume and fee stats for an application. Stats are scoped to trades where the application's users were the **taker**.

```http theme={null}
GET /api/v1/applications/stats?clientId=YOUR_CLIENT_ID
```

**Query Parameters:**

* `clientId` (string, required) - The application's public client ID
* `since` (string, optional) - Only include trades at or after this timestamp (ISO 8601)

**Response:**

```json theme={null}
{
  "volume": "12345.67",
  "makerFee": "12.34",
  "takerFee": "34.56",
  "applicationTakerFee": "5.67",
  "tradeCount": 42
}
```

* `volume` (string) - Total quote volume (normalized) for trades where this application's users were the taker
* `makerFee` (string) - Total maker fees collected (normalized, quote token units)
* `takerFee` (string) - Total taker fees collected (normalized, quote token units)
* `applicationTakerFee` (string) - Application revenue share from taker fees (normalized, quote token units)
* `tradeCount` (number) - Total number of trades

## Withdrawal Endpoint

### 20. Initiate Withdrawal

Allocates a withdrawal and returns ready-to-submit vault calldata. The server signs the withdrawal with its `WITHDRAWAL_SIGNER`; you submit the returned `calldata` to the vault contract (`tx.data`) to move funds on-chain — no further server-side signing is needed. Requires the `Withdraw` permission on the session.

```http theme={null}
POST /api/v1/withdrawals
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body:**

```json theme={null}
{
  "assetId": "123e4567-e89b-12d3-a456-426614174000",
  "amount": "100500000",
  "destination": "0x742d35cc6634c0532925a3b8d1b9d7c2bd34e8dc"
}
```

**Request Fields:**

* `assetId` (string, required) - Asset UUID to withdraw (get from a trading pair's `baseAssetId` or `quoteAssetId`)
* `amount` (string, required) - Raw token amount in the smallest unit as an integer string (e.g., `"100500000"` for 100.5 USDC with 6 decimals). Multiply the human-readable amount by 10\*\*decimals.
* `destination` (string, required) - `0x`-prefixed 20-byte address that will receive the withdrawal

**Response:**

```json theme={null}
{
  "withdrawalIndex": 42,
  "vaultAddress": "0x6a86da986797d59a839d136db490292cd560c131",
  "calldata": "0x..."
}
```

**Response Fields:**

* `withdrawalIndex` (number) - Allocated withdrawal index; matches `executeSignedWithdrawal.index` on-chain
* `vaultAddress` (string) - `0x`-prefixed lowercase vault contract address to send the calldata to (returned so you don't have to query `/applications/config` separately)
* `calldata` (string) - `0x`-prefixed ABI-encoded calldata for `executeSignedWithdrawal(...)`, already signed by the server. Submit it as `tx.data` to `vaultAddress`.

#### Look Up a Withdrawal

Fetch a previously-allocated withdrawal by index. Public lookup — no authentication required. Returns the same shape as the initiate response.

```http theme={null}
GET /api/v1/withdrawals/{withdrawalIndex}
```

## Fee Simulation Endpoint

### 21. Simulate Order Fees

Simulates the fees for an order before placing it. Useful for displaying accurate fee estimates to users.

```http theme={null}
GET /api/v1/fees/simulate?tradingPairId={uuid}&side=BUY&price=40000&quantity=0.5
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Query Parameters:**

* `tradingPairId` (string, required) - Trading pair UUID
* `side` (string, required) - Order side: "BUY" or "SELL"
* `price` (string, required) - Price per unit
* `quantity` (string, required) - Quantity to trade

**Response:**

```json theme={null}
{
  "notional": "20000.00",
  "monacoTakerFee": "20.00",
  "monacoMakerRebate": "-5.00",
  "applicationTakerFee": "10.00",
  "totalTakerFees": "30.00",
  "takerTotalPayment": "20030.00",
  "makerTotalReceipt": "19995.00",
  "buyOrderLockAmount": "20030.00",
  "monacoTakerFeeBpsExact": "6.5",
  "monacoMakerRebateBpsExact": "-1",
  "monacoTakerFeeBps": 10,
  "monacoMakerRebateBps": -2,
  "applicationTakerFeeBps": 5,
  "applicationName": "Monaco Trading Frontend"
}
```

**Response Fields:**

* `notional` (string) - Total trade value (price × quantity)
* `monacoTakerFee` (string) - Monaco protocol taker fee
* `monacoMakerRebate` (string) - Monaco protocol maker rebate (negative = rebate)
* `applicationTakerFee` (string) - Application-specific taker fee
* `totalTakerFees` (string) - Total fees for taker orders (monaco + application)
* `takerTotalPayment` (string) - Total amount taker pays (notional + fees)
* `makerTotalReceipt` (string) - Total amount maker receives (notional + rebate)
* `buyOrderLockAmount` (string, optional) - Amount to lock for BUY orders (null for SELL)
* `monacoTakerFeeBpsExact` (string) - Tier-resolved Monaco taker rate as a decimal bps string (e.g. `"6.5"`) — the exact rate the ledger charges this caller, equal to `monacoTakerFee / notional`
* `monacoMakerRebateBpsExact` (string) - Tier-resolved Monaco maker rebate as a decimal bps string (negative = rebate, e.g. `"-1"`)
* `monacoTakerFeeBps` (number) - **Deprecated.** Whole-bps taker rate; an integer cannot carry fractional tiered rates, so it echoes the flat trading-pair column. Use `monacoTakerFeeBpsExact` or `GET /api/v1/fees/tier`
* `monacoMakerRebateBps` (number) - **Deprecated.** Whole-bps maker rebate; same integer limitation. Use `monacoMakerRebateBpsExact` or `GET /api/v1/fees/tier`
* `applicationTakerFeeBps` (number) - Application fee rate in basis points
* `applicationName` (string, optional) - Application name

## Trading Endpoints

All trading endpoints require a signed request — see [Authenticating Requests](#authenticating-requests).

### 22. Place Order

Creates a new limit or market order on the exchange. This endpoint handles both order types based on the `orderType` field.

```http theme={null}
POST /api/v1/orders
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body (Limit Order):**

```json theme={null}
{
  "tradingPairId": "123e4567-e89b-12d3-a456-426614174000",
  "orderType": "LIMIT",
  "side": "BUY",
  "price": "2000.50",
  "quantity": "1.5",
  "tradingMode": "SPOT",
  "expirationDate": "2024-12-31T23:59:59Z"
}
```

**Note**: `expirationDate` is an optional field for limit orders (default: 90 days for GTC orders).

**Request Body (Market Order):**

```json theme={null}
{
  "tradingPairId": "123e4567-e89b-12d3-a456-426614174000",
  "orderType": "MARKET",
  "side": "SELL",
  "price": null,
  "quantity": "0.5",
  "tradingMode": "SPOT",
  "slippageToleranceBps": 100
}
```

**Note**: Market orders execute inside a mandatory server-side price band: 1,000 bps by default, or a perp market's own band (see [Perp price band](/trading-mechanics/perps#perp-price-band)). `slippageToleranceBps` is optional and only *tightens* that band (e.g., 100 = 1%); it cannot widen it. A market order may partially fill and cancel the remainder, or be rejected when no liquidity sits inside the band.

**Response:**

```json theme={null}
{
  "orderId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "SUCCESS",
  "message": "Order processed with 2 trades",
  "matchResult": {
    "tradesCount": 2,
    "totalFilled": "0.2",
    "remainingQuantity": "0.3",
    "averageFillPrice": "35100.00",
    "status": "PARTIALLY_FILLED",
    "actualSlippageBps": 15,
    "maxSlippageBps": 50,
    "executionPriceRange": {
      "bestPrice": "35000.00",
      "worstPrice": "35150.00"
    }
  }
}
```

**Request Fields:**

* `tradingPairId` (string, required) - Trading pair UUID (get from `/api/v1/market/pairs`)
* `orderType` (string, required) - "LIMIT" or "MARKET"
* `side` (string, required) - "BUY" or "SELL"
* `quantity` (string, required) - Order quantity as decimal string
* `price` (string, optional) - Order price (required for LIMIT orders, ignored for MARKET)
* `tradingMode` (string, optional) - Trading mode: "SPOT" (default) or "MARGIN"
* `timeInForce` (string, optional) - LIMIT orders only: "GTC" (default), "IOC", or "FOK". Rejected on MARKET orders
* `expirationDate` (string, optional) - Custom expiration in ISO 8601 format (default: 90 days for GTC)
* `slippageToleranceBps` (number, optional) - For market orders: tightens the market's mandatory price band, in basis points (e.g., 100 = 1%). Cannot widen the band
* `useMasterBalance` (boolean, optional) - For sub-accounts: use master account's balance

**Response Fields:**

* `orderId` (string) - Unique order identifier (UUID)
* `status` (string) - "SUCCESS" or "FAILED"
* `message` (string) - Operation message
* `matchResult` (object, optional) - Immediate execution results if order matched:
  * `tradesCount` (number) - Number of trades executed
  * `totalFilled` (string) - Total quantity filled
  * `remainingQuantity` (string) - Remaining unfilled quantity
  * `averageFillPrice` (string, optional) - Average fill price across all trades
  * `status` (string) - Order status after matching (e.g., "SUBMITTED", "PARTIALLY\_FILLED", "FILLED")
  * `actualSlippageBps` (number, optional) - Actual slippage in basis points (positive = worse price)
  * `maxSlippageBps` (number, optional) - Maximum slippage allowed when order was submitted
  * `executionPriceRange` (object, optional) - Price range: `bestPrice`, `worstPrice`

### 23. Cancel Order

Cancels an existing open order by its ID.

```http theme={null}
POST /api/v1/orders/cancel
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body:**

```json theme={null}
{
  "orderId": "123e4567-e89b-12d3-a456-426614174000"
}
```

**Response:**

```json theme={null}
{
  "orderId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "SUCCESS",
  "message": "Order cancelled successfully"
}
```

**Request Fields:**

* `orderId` (string) - ID of the order to cancel

**Response Fields:**

* `orderId` (string) - ID of the cancelled order
* `status` (string) - "SUCCESS" or "FAILED"
* `message` (string) - Response message

**Error Responses:**

* `400 Bad Request` - Invalid order ID, order already filled/cancelled/expired, or order not found
* `401 Unauthorized` - Authentication required or order doesn't belong to user
* `500 Internal Server Error` - Server error during cancellation

### 24. Batch Cancel Orders

Cancels multiple orders in a single request (best-effort per order). Supports canceling specific orders or all active orders for the user.

```http theme={null}
POST /api/v1/orders/batch-cancel?cancel_all=false
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Query Parameters:**

* `cancel_all` (boolean, optional) - If true, cancels all active orders for the user (ignores `orderIds` in body). Default: false

**Request Body (when cancel\_all=false):**

```json theme={null}
{
  "orderIds": [
    "123e4567-e89b-12d3-a456-426614174000",
    "987e6543-e21b-12d3-a456-426614174001"
  ]
}
```

**Request Body (when cancel\_all=true):**

```json theme={null}
{
  "orderIds": []
}
```

**Response:**

```json theme={null}
{
  "success": true,
  "totalRequested": 3,
  "totalCancelled": 2,
  "totalFailed": 1,
  "results": [
    {
      "orderId": "123e4567-e89b-12d3-a456-426614174000",
      "success": true,
      "cancelledAt": "2024-01-15T10:35:00Z",
      "error": null
    },
    {
      "orderId": "987e6543-e21b-12d3-a456-426614174001",
      "success": false,
      "cancelledAt": null,
      "error": {
        "code": "ORDER_NOT_FOUND",
        "message": "Order not found or already cancelled"
      }
    }
  ]
}
```

**Response Fields:**

* `success` (boolean) - True if all orders cancelled successfully
* `totalRequested` (number) - Number of orders requested to cancel
* `totalCancelled` (number) - Number successfully cancelled
* `totalFailed` (number) - Number that failed to cancel
* `results` (array) - Per-order results:
  * `orderId` (string) - Order ID
  * `success` (boolean) - Whether this specific order was cancelled
  * `cancelledAt` (string, optional) - Cancellation timestamp (ISO 8601, UTC)
  * `error` (object, optional) - Error details if failed (code, message)

**Error Responses:**

* `400 Bad Request` - No order IDs provided when cancel\_all is false, or invalid order IDs
* `401 Unauthorized` - Authentication required
* `500 Internal Server Error` - Server error during cancellation

**Note:** This is a best-effort operation. Some cancellations may fail while others succeed; failures don't roll back successful cancellations.

### 25. Replace Order

Replaces an existing order by canceling it and creating a new order atomically. Returns a new order ID.

```http theme={null}
PUT /api/v1/orders/{orderId}
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body:**

```json theme={null}
{
  "price": "2100.00",
  "quantity": "2.0"
}
```

**Response:**

```json theme={null}
{
  "orderId": "987e6543-e21b-12d3-a456-426614174000",
  "status": "SUCCESS",
  "message": "Order replaced successfully",
  "updatedFields": {
    "price": "2100.00",
    "quantity": "2.0"
  },
  "originalOrderId": "123e4567-e89b-12d3-a456-426614174000",
  "matchResult": null
}
```

**Request Fields:**

* `price` (string, optional) - New order price (omit to keep original price)
* `quantity` (string, optional) - New **total** order quantity. The replacement rests this total minus the already-filled amount, so it must exceed the filled amount. Omit it to keep the order's total; the replacement then rests the unfilled remainder. Margin reduce-only orders follow the same rule; what the replacement rests must not exceed the live position. See [Replace Orders](/developers/trade#replace-orders)

**Response Fields:**

* `orderId` (string) - **New** order ID after replacement
* `status` (string) - "SUCCESS" or "FAILED"
* `message` (string) - Operation message
* `updatedFields` (object) - The replacement's `price` and `quantity`, set whether or not the request changed them. `price` is the requested price, or the original's when omitted. `quantity` is the replacement's **total**, not its resting size: with `quantity` omitted it is the original order's total, so a 60/100-filled order reports `"100"` and rests 40 (releases before v1.0.44 reported `"40"`). This holds for margin reduce-only orders too. Read the order's `remainingQuantity` for the resting size
* `originalOrderId` (string) - Original order ID that was replaced
* `matchResult` (object, nullable) - Match result if order executed immediately

### 26. Get Orders

Retrieves a paginated list of the authenticated user's book orders (`LIMIT` and `MARKET`, resting and historical) with optional filtering; filter `status` to `SUBMITTED,PARTIALLY_FILLED` for the resting working set. TWAP parents and conditional (TP/SL) orders are not rows of this listing: use `GET /api/v1/orders/twap` and `GET /api/v1/orders/conditional`. Nor are a TWAP's slices — the child orders it places, which carry `parentOrderId` — resting or finished, and they are not counted in `total`: read them with `GET /api/v1/orders/twap/{twapOrderId}/slices`.

```http theme={null}
GET /api/v1/orders?pageSize=20&pageToken=&status=FILLED,PARTIALLY_FILLED&tradingPairId=123e4567-e89b-12d3-a456-426614174000
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Query Parameters:**

* `pageSize` (number, optional) - Items per page (default: 20); up to 1000
* `status` (string, optional) - Filter by order status. Supports comma-separated list for multiple statuses (e.g., "FILLED,PARTIALLY\_FILLED"). Valid values: "SUBMITTED", "PARTIALLY\_FILLED", "FILLED", "SETTLED", "CANCELLED", "EXPIRED", "REJECTED"
* `tradingPairId` (string, optional) - Filter by trading pair UUID
* `pageToken` (string, optional) - Pagination cursor. This listing merges hot and archived rows. Omit it or send an empty string to start a walk over full history, then send each response's `nextPageToken` to continue; the walk is exhausted when a page returns no rows and an empty `nextPageToken`. `total` / `totalPages` are populated, bounded by a server count ceiling (exact up to it, then reported as the ceiling with `totalCapped: true`; see the response below)

**Response:**

```json theme={null}
{
  "orders": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "tradingPairId": "456e7890-e12b-12d3-a456-426614174000",
      "orderType": "LIMIT",
      "side": "BUY",
      "price": "2000.50",
      "quantity": "1.5",
      "filledQuantity": "0.5",
      "averageFillPrice": "2000.45",
      "status": "PARTIALLY_FILLED",
      "tradingMode": "SPOT",
      "timeInForce": "GTC",
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:35:00Z",
      "expirationDate": "2024-04-15T10:30:00Z"
    }
  ],
  "total": 45,
  "pageSize": 20,
  "totalPages": 3,
  "totalCapped": false,
  "nextPageToken": "<opaque>"
}
```

**Order Fields:**

* `id` (string) - Order unique identifier (UUID)
* `tradingPairId` (string) - Trading pair ID (UUID)
* `orderType` (string) - "LIMIT" or "MARKET"
* `conditionalOrderId` (string, optional) - Present only on an order the engine placed for a take-profit or stop-loss trigger: the conditional order that fired it. Absent on orders the client placed, on TWAP slice children, and on triggered closes written before the field existed (there is no backfill). Served by this endpoint and `GET /api/v1/orders/{orderId}`; no WebSocket frame carries it
* `origin` (string, optional) - What placed the order: "USER" (the account itself, including its TP/SL triggers and TWAP slices), "LIQUIDATION" (the liquidation engine closing the account's position), "ADL" (an auto-deleveraging close), "COPY" (the copy trading engine, for one of the account's follows) or "SYSTEM". Use it to label a row in order history: a liquidation is a reduce-only IOC limit order, so `orderType`, `timeInForce` and `reduceOnly` alone read the same as an order the user placed. Present on every book order; absent on a "TWAP" or "CONDITIONAL" row. The same labels as `origin` on the order WebSocket events
* `side` (string) - "BUY" or "SELL"
* `price` (string, optional) - Order price (null for market orders)
* `quantity` (string) - Order quantity
* `filledQuantity` (string) - Quantity filled so far
* `averageFillPrice` (string, optional) - Average fill price (null if not filled yet)
* `status` (string) - Order status (see Order Status enum)
* `tradingMode` (string) - "SPOT" or "MARGIN"
* `timeInForce` (string, optional) - "GTC", "IOC", or "FOK". Absent on MARKET orders, which have none
* `createdAt` (string) - Order creation timestamp (ISO 8601, UTC)
* `updatedAt` (string, optional) - Last update timestamp (ISO 8601, UTC)
* `expirationDate` (string, optional) - Order expiration date
* `applicationTakerFee` (string, optional) - Application (builder) taker fee paid, in quote token units, summed over the fills where the order was the taker; a replacement also includes the fills of the order it replaced; 0 on a liquidation order
* `monacoTakerFee` (string, optional) - Monaco protocol taker fee paid, in quote token units, summed over the fills where the order was the taker; a replacement also includes the fills of the order it replaced; a liquidation order carries the liquidation fee it was charged instead
* `monacoMakerRebate` (string, optional) - Monaco maker fee, in quote token units, summed over the fills where the order was the maker (negative = rebate); a replacement also includes the fills of the order it replaced
* `totalTakerFees` (string, optional) - Total taker fees (monaco + application)
* `takerTotalPayment` (string, optional) - Total amount taker paid: the filled notional as the ledger booked it (each fill rounded down to the quote token's smallest unit) plus `totalTakerFees`; a replacement also includes the fills of the order it replaced
* `makerTotalReceipt` (string, optional) - Total amount maker received: the notional of its maker fills as the ledger booked it minus `monacoMakerRebate` (a rebate adds); a replacement also includes the fills of the order it replaced

### 27. Get Order by ID

Retrieves detailed information about a specific order by its ID.

```http theme={null}
GET /api/v1/orders/{orderId}
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Path Parameters:**

* `orderId` (string) - Order UUID

**Response:**

```json theme={null}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "tradingPairId": "456e7890-e12b-12d3-a456-426614174000",
  "orderType": "LIMIT",
  "side": "BUY",
  "price": "2000.50",
  "quantity": "1.5",
  "filledQuantity": "1.5",
  "averageFillPrice": "2000.45",
  "status": "FILLED",
  "tradingMode": "SPOT",
  "timeInForce": "GTC",
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:35:00Z",
  "expirationDate": "2024-04-15T10:30:00Z"
}
```

**Error Responses:**

* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Order doesn't belong to user
* `404 Not Found` - Order not found

## Market Data Endpoints

Public endpoints for accessing trading pairs and market data. Authentication is not required.

### 28. Get Trading Pairs

Retrieves a paginated list of all available trading pairs with optional filtering.

```http theme={null}
GET /api/v1/market/pairs?page=1&pageSize=20&marketType=SPOT&isActive=true
```

**Query Parameters:**

* `page` (number, optional) - Page number, starts from 1 (default: 1)
* `pageSize` (number, optional) - Items per page, max 100 (default: 20)
* `marketType` (string, optional) - Filter by market type: "SPOT"
* `baseToken` (string, optional) - Filter by base token symbol (e.g., "ETH")
* `quoteToken` (string, optional) - Filter by quote token symbol (e.g., "USD")
* `isActive` (boolean, optional) - Filter by active status. An operator-managed market is public only while its launch's durable runtime projection reads `live` — not merely its launch phase — so a pending, warming or halted managed launch is hidden and `isActive=false` does not surface it. A pair with no managed launch keeps its ordinary visibility.

**Response:**

```json theme={null}
{
  "tradingPairs": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "symbol": "ETH/USDC",
      "baseToken": "ETH",
      "baseAssetName": "Ethereum",
      "quoteToken": "USDC",
      "quoteAssetName": "USDC",
      "baseTokenContract": "0x...",
      "quoteTokenContract": "0x...",
      "baseAssetId": "123e4567-e89b-12d3-a456-426614174001",
      "quoteAssetId": "123e4567-e89b-12d3-a456-426614174002",
      "baseDecimals": 18,
      "quoteDecimals": 6,
      "baseIconUrl": "https://assets.monaco.xyz/tokens/eth.svg",
      "quoteIconUrl": "https://assets.monaco.xyz/tokens/usdc.svg",
      "minOrderSize": "0.01",
      "maxOrderSize": "1000.0",
      "tickSize": "0.01",
      "makerFeeBps": -1,
      "takerFeeBps": 5,
      "marketType": "SPOT",
      "isActive": true,
      "isolatedOnly": false
    }
  ],
  "page": 1,
  "pageSize": 20,
  "total": 15,
  "totalPages": 1
}
```

**Fields:**

* `id` (string) - Trading pair UUID (use as `tradingPairId` in order requests)
* `symbol` (string) - Trading pair symbol (e.g., "ETH/USDC")
* `baseToken` (string) - Base token symbol
* `baseAssetName` (string) - Human-readable base asset display name (e.g., "Robinhood Markets" for HOOD) — useful for matching markets by company/asset name in search
* `quoteToken` (string) - Quote token symbol
* `quoteAssetName` (string) - Human-readable quote asset display name
* `baseTokenContract` (string) - Base token contract address
* `quoteTokenContract` (string) - Quote token contract address
* `baseAssetId` (string) - Base asset UUID (use for withdrawal requests)
* `quoteAssetId` (string) - Quote asset UUID (use for withdrawal requests)
* `baseDecimals` (number) - Base token decimals
* `quoteDecimals` (number) - Quote token decimals
* `baseIconUrl` (string) - Base token icon URL
* `quoteIconUrl` (string) - Quote token icon URL
* `minOrderSize` (string) - Minimum order size
* `maxOrderSize` (string) - Maximum order size
* `tickSize` (string) - Minimum price increment
* `makerFeeBps` (number) - **Deprecated.** Flat pair-level maker fee in basis points (negative = rebate); this is not the per-user tiered rate. Use `GET /api/v1/fees/tier` for the caller's real rate
* `takerFeeBps` (number) - **Deprecated.** Flat pair-level taker fee in basis points; this is not the per-user tiered rate. Use `GET /api/v1/fees/tier` for the caller's real rate
* `marketType` (string) - "SPOT" or "MARGIN"
* `isActive` (boolean) - Whether pair is active for trading
* `isolatedOnly` (boolean) - Isolated-only market: cross margin may only reduce a position. A `CROSS` order that is not reduce-only is rejected with `400` and code `CROSS_MARGIN_NOT_ALLOWED`; open with `marginMode: "ISOLATED"`

### 29. Get Trading Pair by ID

Retrieves detailed information about a specific trading pair by its UUID.

```http theme={null}
GET /api/v1/market/pairs/{pair_id}
```

**Path Parameters:**

* `tradingPairId` (string) - Trading pair UUID (e.g., "123e4567-e89b-12d3-a456-426614174000")

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "symbol": "ETH/USDC",
    "baseToken": "ETH",
    "quoteToken": "USDC",
    "baseTokenContract": "0x...",
    "quoteTokenContract": "0x...",
    "baseAssetId": "123e4567-e89b-12d3-a456-426614174001",
    "quoteAssetId": "123e4567-e89b-12d3-a456-426614174002",
    "baseDecimals": 18,
    "quoteDecimals": 6,
    "minOrderSize": "0.01",
    "maxOrderSize": "1000.0",
    "tickSize": "0.01",
    "makerFeeBps": -1,
    "takerFeeBps": 5,
    "marketType": "SPOT",
    "isActive": true,
    "isolatedOnly": false
  }
}
```

**Error Responses:**

* `404 Not Found` - Trading pair not found

### 30. Get Market Metadata

Retrieves current price, 24-hour statistics, and market information for a trading pair.

```http theme={null}
GET /api/v1/market/pairs/{pair_id}/metadata
```

**Path Parameters:**

* `tradingPairId` (string) - Trading pair UUID

**Response:**

```json theme={null}
{
  "symbol": "BTC/USDC",
  "baseIconUrl": "https://assets.monaco.xyz/tokens/btc.svg",
  "quoteIconUrl": "https://assets.monaco.xyz/tokens/usdc.svg",
  "lastPrice": "95432.50",
  "lastPriceTimestamp": 1736742000000,
  "high24h": "96500.00",
  "low24h": "94200.00",
  "volume24h": "1234.5678",
  "priceChange24h": "1232.50",
  "priceChangePercent24h": "1.31",
  "marketInitializationTimestamp": 1735000000000
}
```

**Response Fields:**

* `symbol` (string) - Trading pair symbol (e.g., "BTC/USDC")
* `baseIconUrl` (string) - Base token icon URL
* `quoteIconUrl` (string) - Quote token icon URL
* `lastPrice` (string, optional) - Current price from latest candle (null if no data)
* `lastPriceTimestamp` (number, optional) - Timestamp of last price in milliseconds (null if no data)
* `high24h` (string, optional) - 24-hour high price (null if less than 24h of data)
* `low24h` (string, optional) - 24-hour low price (null if less than 24h of data)
* `volume24h` (string, optional) - 24-hour trading volume in base asset (null if less than 24h of data)
* `quoteVolume24h` (string, optional) - 24-hour trading volume in quote token / USDC (null if less than 24h of data)
* `priceChange24h` (string, optional) - Price change in last 24 hours (null if less than 24h of data)
* `priceChangePercent24h` (string, optional) - Percentage change in last 24 hours (null if less than 24h of data)
* `marketInitializationTimestamp` (number, optional) - Timestamp when market first started trading (first candle)

**Error Responses:**

* `404 Not Found` - Trading pair not found or inactive
* `500 Internal Server Error` - Failed to fetch market data

### 31. Get Historical Candlestick Data

Retrieves historical OHLCV (Open, High, Low, Close, Volume) candlestick data for a trading pair.

```http theme={null}
GET /api/v1/market/pairs/charts/candlestick/{tradingPairId}/{interval}?startTime=1704067200000&endTime=1704153600000&limit=350
```

**Path Parameters:**

* `tradingPairId` (string) - Trading pair UUID (get from `/api/v1/market/pairs`)
* `interval` (string) - Candlestick interval: "1m", "5m", "15m", "1h", "4h", or "1d"

**Query Parameters:**

* `startTime` (number, optional) - Start timestamp in Unix milliseconds (for forward pagination)
* `endTime` (number, optional) - End timestamp in Unix milliseconds (for backward pagination)
* `limit` (number, optional) - Maximum candlesticks to return (default: 350, max: 500)
* `priceType` (string, optional) - Price series: `trade` (default) or `mark` (margin pairs only)

**Response:**

```json theme={null}
{
  "data": [
    {
      "T": 1704067200000,
      "t": 1704067260000,
      "o": "2250.50",
      "h": "2255.00",
      "l": "2248.00",
      "c": "2252.75",
      "v": "125.5",
      "s": "ETH",
      "i": "1m",
      "n": 45,
      "openInterestBase": "2.5",
      "openInterestNotional": "6250"
    }
  ]
}
```

**Candlestick Fields:**

* `T` (number) - Candle start timestamp (Unix milliseconds)
* `t` (number) - Candle end timestamp (Unix milliseconds)
* `o` (string) - Open price
* `h` (string) - High price
* `l` (string) - Low price
* `c` (string) - Close price
* `v` (string) - Volume (base token)
* `s` (string) - Symbol
* `i` (string) - Interval
* `n` (number) - Number of trades in this candle
* `openInterestBase` (string or null, optional) - One-sided LONG open interest in base-asset units, using the latest sample at or before the candle end
* `openInterestNotional` (string or null, optional) - One-sided LONG quote-asset notional paired with that same sample's price; present only when the paired base quantity and price are available. It is not recalculated from the candle close, and the quote asset may not be USD.

Both open-interest fields are unavailable for candles without a recorded sample (including historical periods before collection) or a paired price. `openInterestBase` remains available when the quantity is known but the paired price is not.

**Supported Intervals:**

* `1m` - 1 minute
* `5m` - 5 minutes
* `15m` - 15 minutes
* `1h` - 1 hour
* `4h` - 4 hours
* `1d` - 1 day

**Error Responses:**

* `400 Bad Request` - Invalid interval or time range
* `404 Not Found` - Trading pair not found or inactive

## Orderbook Endpoints

### 32. Get Orderbook Snapshot

Gets the complete orderbook snapshot for a trading pair showing all bids and asks with their price levels. This is a public endpoint that does not require authentication.

```http theme={null}
GET /api/v1/orderbook/{tradingPairId}?levels=20&tradingMode=Spot&magnitude=0.01&denomination=base
```

**Path Parameters:**

* `tradingPairId` (string) - Trading pair UUID

**Query Parameters:**

* `levels` (number, optional) - Number of price levels to return, max 100 (default: all levels)
* `tradingMode` (string, optional) - Trading mode: "Spot" or "Margin" (default: "Spot")
* `magnitude` (string, optional) - Price grouping magnitude for aggregation. Valid values: "0.0001", "0.001", "0.01", "0.1", "1", "10", "100", "1000", "10000". If not specified, returns ungrouped levels
* `denomination` (string, optional) - Quantity denomination: "base" (default) or "quote". When "quote", quantities are multiplied by price

**Response:**

```json theme={null}
{
  "eventType": "orderbook_snapshot",
  "tradingPairId": "00000000-0000-0000-0000-000000000001",
  "pair": "BTC/USDC",
  "tradingMode": "Spot",
  "timestamp": "2024-01-15T10:30:00Z",
  "sequenceNumber": 12345,
  "baseToken": "BTC",
  "quoteToken": "USDC",
  "baseDecimals": 8,
  "quoteDecimals": 6,
  "data": {
    "bids": [
      {"price": "34900.00", "quantity": "1.2", "orderCount": 2}
    ],
    "asks": [
      {"price": "35100.00", "quantity": "0.8", "orderCount": 1}
    ],
    "bestBid": "34900.00",
    "bestAsk": "35100.00",
    "bidVolume": "15.5",
    "askVolume": "12.3",
    "priceChange": null
  }
}
```

**Response Fields:**

* `eventType` (string) - Event type ("orderbook\_snapshot" for HTTP)
* `tradingPairId` (string) - Trading pair UUID
* `pair` (string) - Trading pair symbol (e.g., "BTC/USDC")
* `tradingMode` (string) - "Spot" or "Margin"
* `timestamp` (string) - Snapshot timestamp (ISO 8601, UTC)
* `sequenceNumber` (number) - Sequence number for ordering updates
* `baseToken` (string) - Base token symbol
* `quoteToken` (string) - Quote token symbol
* `baseDecimals` (number) - Base token decimals
* `quoteDecimals` (number) - Quote token decimals
* `data` (object) - Orderbook data:
  * `bids` (array) - Buy orders sorted by price (highest first)
  * `asks` (array) - Sell orders sorted by price (lowest first)
  * `bestBid` (string) - Current best bid price
  * `bestAsk` (string) - Current best ask price
  * `bidVolume` (string) - Total bid volume
  * `askVolume` (string) - Total ask volume
  * `priceChange` (object, optional) - Price change information

**Error Responses:**

* `400 Bad Request` - Invalid magnitude or denomination value
* `404 Not Found` - Trading pair not found
* `500 Internal Server Error` - Matching engine not initialized

## Delegated Agents Endpoints

Delegated agents let a master account authorize an external agent wallet to trade on its behalf under a fine-grained policy. The owner registers an agent and policy; the agent then authenticates with its own session key and exchanges it for an owner-scoped delegated session. The management endpoints require a signed request — see [Authenticating Requests](#authenticating-requests).

### 33. Upsert Delegated Agent

Registers or updates a delegated agent. Master accounts only. The delegation is keyed on `agentAddress`, so calling again with the same address updates the existing delegation and its policy. An owner holds at most 10 active, unexpired agents; registering another past that is rejected until one is revoked or expires. Updating an agent that is already active does not count against the limit.

```http theme={null}
POST /api/v1/delegated-agents
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body:**

```json theme={null}
{
  "agentAddress": "0x742d35cc6634c0532925a3b8d4060f31e2c3d8b5",
  "name": "market-maker-bot",
  "expiresAt": "2026-12-31T23:59:59Z",
  "allowedActions": ["CREATE_ORDER", "CANCEL_ORDER"],
  "allowedTradingPairIds": [],
  "allowedMarginAccountIds": [],
  "allowedOrderTypes": ["LIMIT"],
  "allowedTimeInForce": ["GTC"],
  "maxLeverage": "10",
  "maxOrderNotional": "50000",
  "ipAllowlist": ["203.0.113.0/24"]
}
```

**Request Fields:**

* `agentAddress` (string, required) - Agent wallet address (EVM, 42 chars including `0x`)
* `name` (string, optional) - Human-friendly label for the agent
* `expiresAt` (string, optional) - Delegation expiry timestamp (ISO 8601). Omit for the default of 14 days from now; at most 180 days ahead. Ends delegated reads, trading, and session refresh
* `allowedActions` (array, required) - Actions the agent may perform: `CREATE_ORDER`, `CANCEL_ORDER`, `REPLACE_ORDER`, `READ`, `MANAGE_RISK_BUCKETS`. Any other value is rejected. Every delegated session may read the owner's data, so `[]` or `["READ"]` is a read-only key that every order-management route refuses. `MANAGE_RISK_BUCKETS` lets the agent fund risk buckets in a margin account listed in `allowedMarginAccountIds`, through `POST /api/v1/margin/risk-buckets/collateral/transfer-in` only
* `allowedTradingPairIds` (array, required) - Trading pair UUIDs the agent may trade. Use `[]` to leave markets unrestricted when `allowedMarginAccountIds` is listed
* `allowedMarginAccountIds` (array, optional) - Margin account UUIDs the agent is bound to. Omitted, `null`, or `[]` leaves the account unrestricted. Each listed scope must match: with both listed, an order needs a listed market **and** a listed account. Both scopes empty deny all orders
* `allowedOrderTypes` (array, optional) - Permitted order types (`LIMIT`, `MARKET`). Omitted, `null`, or `[]` means unrestricted order types
* `allowedTimeInForce` (array, optional) - Permitted time-in-force values (`GTC`, `IOC`, `FOK`). Omitted, `null`, or `[]` means unrestricted time in force
* `maxLeverage` (string, optional) - Maximum leverage the agent may use (decimal string). Omit for no limit
* `maxOrderNotional` (string, optional) - Maximum order notional (price × quantity, decimal string). Omit for no limit
* `maxOpenOrders` (legacy field, unsupported) - Omit it. Any configured value is rejected because this cap was never enforced
* `ipAllowlist` (array, optional) - Up to 20 IP addresses or CIDR ranges (IPv4 or IPv6) the agent may create its delegated session from. Omitted, `null`, or `[]` allows any address

Omitting either required array returns `400 Invalid JSON payload`. Empty lists have the field-specific meanings above; they do not all grant unrestricted access. Upsert replaces the entire policy, so omitted optional fields do not preserve previous restrictions, and an omitted `expiresAt` sets the expiry to 14 days from the update.

**Response:**

```json theme={null}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "ownerUserId": "456e7890-e12b-12d3-a456-426614174001",
  "agentAddress": "0x742d35cc6634c0532925a3b8d4060f31e2c3d8b5",
  "name": "market-maker-bot",
  "isActive": true,
  "expiresAt": "2026-12-31T23:59:59Z",
  "revokedAt": null,
  "allowedActions": ["CREATE_ORDER", "CANCEL_ORDER"],
  "allowedTradingPairIds": [],
  "allowedMarginAccountIds": [],
  "allowedOrderTypes": ["LIMIT"],
  "allowedTimeInForce": ["GTC"],
  "maxLeverage": "10",
  "maxOrderNotional": "50000",
  "maxOpenOrders": null,
  "ipAllowlist": ["203.0.113.0/24"]
}
```

**Response Fields:**

* `id` (string) - Delegation UUID
* `ownerUserId` (string) - Owner account UUID the agent acts on behalf of
* `agentAddress` (string) - Agent wallet address (EVM)
* `name` (string, optional) - Human-friendly label for the agent
* `isActive` (boolean) - Whether the delegation is active
* `expiresAt` (string, optional) - Delegation expiry timestamp (ISO 8601), if set
* `revokedAt` (string, optional) - Revocation timestamp (ISO 8601), if revoked
* `allowedActions`, `allowedTradingPairIds`, `allowedMarginAccountIds`, `allowedOrderTypes`, `allowedTimeInForce` (arrays) - The policy as stored
* `maxLeverage`, `maxOrderNotional` (string, optional) - Policy limits, if set
* `maxOpenOrders` (number or null) - Legacy data only; no concurrent open-order cap is enforced. New or updated policies return null
* `ipAllowlist` (array) - The IP allowlist, normalized to CIDR form (a bare address is stored as `/32` or `/128`). Empty allows any address

**Error Responses:**

* `400 Bad Request` - Missing required JSON fields, an invalid `agentAddress`, an `expiresAt` that is not ISO 8601 or is more than 180 days ahead, an unknown action, more than 20 `ipAllowlist` entries or an entry that is not an IP address or CIDR range, other invalid policy values, or unsupported `maxOpenOrders`
* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Only master accounts can manage delegated agents

### 34. List Delegated Agents

Lists every agent the calling account owns, each with its policy. Master accounts only.

```http theme={null}
GET /api/v1/delegated-agents
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "agents": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "ownerUserId": "456e7890-e12b-12d3-a456-426614174001",
      "agentAddress": "0x742d35cc6634c0532925a3b8d4060f31e2c3d8b5",
      "name": "market-maker-bot",
      "isActive": true,
      "expiresAt": "2026-12-31T23:59:59Z",
      "revokedAt": null,
      "allowedActions": ["CREATE_ORDER", "CANCEL_ORDER"],
      "allowedTradingPairIds": [],
      "allowedMarginAccountIds": [],
      "allowedOrderTypes": ["LIMIT"],
      "allowedTimeInForce": ["GTC"],
      "maxLeverage": "10",
      "maxOrderNotional": "50000",
      "maxOpenOrders": null
    }
  ]
}
```

**Response Fields:**

* `agents` (array) - The calling owner's delegated agents (each object as in [Upsert Delegated Agent](#33-upsert-delegated-agent))

**Error Responses:**

* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Only master accounts can list delegated agents

### 35. List Delegating Owners

Reverse lookup keyed on the caller's own wallet address: an agent that has authenticated with its own session key discovers which owners it may act on behalf of, and the `ownerUserId` to pass to [Create Delegated Session](#37-create-delegated-session). Returns only active (non-revoked, non-expired) delegations.

```http theme={null}
GET /api/v1/delegated-agents/owners
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Response:**

```json theme={null}
{
  "owners": [
    {
      "ownerUserId": "123e4567-e89b-12d3-a456-426614174000",
      "delegationId": "456e7890-e12b-12d3-a456-426614174001",
      "name": "market-maker-bot",
      "isActive": true,
      "expiresAt": "2026-12-31T23:59:59Z"
    }
  ]
}
```

**Response Fields:**

* `owners` (array) - Owners that have an active delegation to the calling agent:
  * `ownerUserId` (string) - Owner account UUID to pass to Create Delegated Session
  * `delegationId` (string) - Delegation UUID for the (owner, agent) pair
  * `name` (string, optional) - Human-friendly label the owner gave this agent, if any
  * `isActive` (boolean) - Whether the delegation is active
  * `expiresAt` (string, optional) - Delegation expiry timestamp (ISO 8601), if set

**Error Responses:**

* `401 Unauthorized` - Authentication required

### 36. Revoke Delegated Agent

Revokes the delegation identified by `delegatedAgentId`. Master accounts only. Existing delegated sessions are revoked, so later REST and gRPC requests fail and open WebSockets disconnect on periodic revalidation. Orders already accepted remain open.

```http theme={null}
DELETE /api/v1/delegated-agents/{delegatedAgentId}
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
```

**Path Parameters:**

* `delegatedAgentId` (string) - Delegation UUID to revoke

**Response:**

```json theme={null}
{
  "status": "REVOKED"
}
```

**Error Responses:**

* `400 Bad Request` - Invalid `delegatedAgentId`
* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Only master accounts can revoke delegated agents
* `404 Not Found` - Delegated agent not found

### 37. Create Delegated Session

Exchanges the agent's own session for an owner-scoped delegated session. The agent authenticates with its own session key, then calls this with the `ownerUserId` it wants to act for (discover it via [List Delegating Owners](#35-list-delegating-owners)) and a freshly generated session public key. The new session acts as the owner but records the agent's address for policy enforcement. Requires an active delegation for the (owner, agent) pair and, when the delegation has an `ipAllowlist`, a caller address inside one of its ranges.

```http theme={null}
POST /api/v1/delegated-agents/sessions
```

**Headers:**

```
X-Monaco-PublicKey: YOUR_SESSION_PUBLIC_KEY
X-Monaco-Timestamp: UNIX_MILLISECONDS
X-Monaco-Signature: ED25519_SIGNATURE
Content-Type: application/json
```

**Request Body:**

```json theme={null}
{
  "ownerUserId": "123e4567-e89b-12d3-a456-426614174000",
  "sessionPublicKey": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"
}
```

**Request Fields:**

* `ownerUserId` (string, required) - Owner account UUID to act on behalf of (discover via List Delegating Owners)
* `sessionPublicKey` (string, required) - 64-char lowercase-hex ed25519 public key the agent generated for this delegated session. Subsequent requests acting on the owner's behalf are signed with the matching private key

**Response:**

```json theme={null}
{
  "expiresAt": 1735689599,
  "delegationId": "456e7890-e12b-12d3-a456-426614174001",
  "ownerUserId": "123e4567-e89b-12d3-a456-426614174000",
  "agentAddress": "0x742d35cc6634c0532925a3b8d4060f31e2c3d8b5"
}
```

**Response Fields:**

* `expiresAt` (number) - Session expiry as a Unix timestamp (seconds); it cannot exceed the delegation expiry
* `delegationId` (string) - Delegation UUID for the active (owner, agent) pair
* `ownerUserId` (string) - Owner account UUID the session acts on behalf of
* `agentAddress` (string) - Agent wallet address recorded on the session for policy enforcement

**Error Responses:**

* `400 Bad Request` - Invalid request
* `401 Unauthorized` - Authentication required
* `403 Forbidden` - No active delegation for owner, or the delegation's IP allowlist does not include the caller's address

## Error Handling

All endpoints return standard HTTP status codes:

* **200** - Success
* **400** - Bad Request (invalid parameters)
* **401** - Unauthorized (missing, invalid, or expired session signature)
* **403** - Forbidden (authenticated, but not allowed to access the resource)
* **500** - Internal Server Error
* **503** - Service Unavailable (retryable — including an authenticated request whose session check the server could not complete)

**Error Response Format:**

```json theme={null}
{
  "error": "Authentication failed",
  "message": "Invalid signature provided"
}
```

When a rejection originates in the matching engine, the envelope additionally carries a stable machine-readable `code` (for example `POST_ONLY_WOULD_CROSS` for a post-only order that would cross the book). The field is omitted entirely when no engine code applies — match on `code` when present instead of parsing `message`:

```json theme={null}
{
  "error": "BAD_REQUEST",
  "message": "Bad request: post-only order would cross: 550e8400-e29b-41d4-a716-446655440000",
  "code": "POST_ONLY_WOULD_CROSS",
  "statusCode": 400
}
```

### Handling Session Expiry (401 Errors)

When a session expires or is revoked, authenticated endpoints return `401 Unauthorized`. Re-run the challenge/verify flow to start a new session. A clock more than 30 seconds out of sync with the server also produces `401` — keep the request timestamp accurate. The TypeScript SDK corrects for this itself, from each response's `Date` header; a raw client should do the same or keep its clock synced.

A `401` always means the credentials themselves were rejected: a bad signature, a timestamp outside the skew window, or a session that was not found, has expired or been revoked, or whose delegation is inactive. When the server cannot complete the session check at all — a database or other dependency is unavailable — it answers `503` with error `SERVICE_UNAVAILABLE` and the message `Authentication temporarily unavailable; retry` instead. That is not a verdict on the session: keep it and retry with bounded exponential backoff and jitter, capping the attempts, rather than re-running challenge/verify.

## Code Examples

### cURL

**Complete Authentication Flow:**

```bash theme={null}
# 0. Generate an ed25519 session keypair locally (implementation varies)

# 1. Create challenge — pass the session public key
curl -X POST https://staging.apimonaco.xyz/api/v1/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5",
    "clientId": "688a21440e304c06b935f743dae88278",
    "sessionPublicKey": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"
  }'

# 2. Sign the returned message with your wallet (implementation varies)

# 3. Verify signature — registers the session public key, opens a session
curl -X POST https://staging.apimonaco.xyz/api/v1/auth/verify \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5",
    "signature": "0x1234567890abcdef...",
    "nonce": "abc123def456",
    "clientId": "688a21440e304c06b935f743dae88278",
    "sessionPublicKey": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"
  }'

# 4. Sign every subsequent request with the session private key
#    (see #authenticating-requests)
```

### JavaScript (Node.js)

```javascript theme={null}
const { bytesToHex, utf8ToBytes } = require('@noble/hashes/utils');
const { sha256 } = require('@noble/hashes/sha256');
const ed = require('@noble/ed25519');

class MonacoAPI {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.session = null; // { publicKey, privateKey } as hex
  }

  // Generate the ed25519 session keypair (hex-encoded)
  static generateSession() {
    const privateKey = ed.utils.randomPrivateKey();
    return {
      privateKey: bytesToHex(privateKey),
      publicKey: bytesToHex(ed.getPublicKey(privateKey)),
    };
  }

  async createChallenge(address, sessionPublicKey, clientId = null) {
    const payload = { address, sessionPublicKey: sessionPublicKey };
    if (clientId) payload.clientId = clientId;

    const response = await fetch(`${this.baseUrl}/api/v1/auth/challenge`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    return response.json();
  }

  // After the wallet signs the challenge message, register the session
  async verifySignature(address, signature, nonce, session, clientId = null) {
    const payload = {
      address,
      signature,
      nonce,
      sessionPublicKey: session.publicKey,
    };
    if (clientId) payload.clientId = clientId;

    const response = await fetch(`${this.baseUrl}/api/v1/auth/verify`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);

    this.session = session; // sign all later requests with this keypair
    return response.json();
  }

  // Build the X-Monaco-* signing headers for an authenticated request
  signedHeaders(method, pathWithQuery, body = '') {
    const timestampMs = Date.now();
    const bodyHash = bytesToHex(sha256(body ? utf8ToBytes(body) : new Uint8Array(0)));
    const signingString = `${method}\n${pathWithQuery}\n${timestampMs}\n${bodyHash}`;
    const signature = bytesToHex(ed.sign(utf8ToBytes(signingString), this.session.privateKey));
    return {
      'X-Monaco-PublicKey': this.session.publicKey,
      'X-Monaco-Timestamp': String(timestampMs),
      'X-Monaco-Signature': signature,
    };
  }

  // Example authenticated GET
  async getBalances() {
    const path = '/api/v1/accounts/balances';
    const response = await fetch(`${this.baseUrl}${path}`, {
      headers: this.signedHeaders('GET', path),
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    return response.json();
  }
}
```

## Getting Credentials

Contact the Monaco Protocol team to obtain your application's `clientId`. It identifies your application for user login and for the public application reporting endpoints.

## Migration from SDK

If you're migrating from the TypeScript SDK, note these key differences:

1. **Field Names**: API uses `snake_case` (e.g., `clientId`) while SDK uses `camelCase` (e.g., `clientId`)
2. **Manual Request Signing**: You must generate a session keypair and sign every request with it (no token to store)
3. **Wallet Signing**: You must implement wallet message signing separately
4. **Error Handling**: Parse HTTP status codes instead of SDK error classes

## Withdrawal Usage Examples

These examples assume the `MonacoAPI` class above (with `signedHeaders()`) and an authenticated `api` whose `api.session` is set. The session must hold the `Withdraw` permission.

Deposits are on-chain: send tokens directly to the vault contract and the indexer credits your balance — there is no deposit API. Withdrawals go through the API below, which returns signed vault calldata for you to submit on-chain.

### JavaScript Example

```javascript theme={null}
async function initiateWithdrawal(api, assetId, amountRaw, destination) {
  const path = '/api/v1/withdrawals';
  const body = JSON.stringify({ assetId: assetId, amount: amountRaw, destination });

  const response = await fetch(`${api.baseUrl}${path}`, {
    method: 'POST',
    headers: {
      ...api.signedHeaders('POST', path, body),
      'Content-Type': 'application/json',
    },
    body,
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  return response.json();
}

// Usage - For 100.5 USDC (6 decimals): 100.5 * 10**6 = 100500000
const withdrawal = await initiateWithdrawal(
  api,
  '123e4567-e89b-12d3-a456-426614174000', // Asset UUID from trading pair
  '100500000',                             // Raw format: 100.5 USDC with 6 decimals
  '0x742d35cc6634c0532925a3b8d1b9d7c2bd34e8dc' // Destination address
);

console.log(`Withdrawal index: ${withdrawal.withdrawalIndex}`);
// Submit the signed calldata to the vault contract to complete the withdrawal on-chain:
//   await walletClient.sendTransaction({ to: withdrawal.vaultAddress, data: withdrawal.calldata });
```

## Trading Usage Examples

### JavaScript Example

```javascript theme={null}
async function placeOrder(api, order) {
  const path = '/api/v1/orders';
  const body = JSON.stringify(order);
  const response = await fetch(`${api.baseUrl}${path}`, {
    method: 'POST',
    headers: {
      ...api.signedHeaders('POST', path, body),
      'Content-Type': 'application/json',
    },
    body,
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  return response.json();
}

async function replaceOrder(api, orderId, updates) {
  const path = `/api/v1/orders/${orderId}`;
  const body = JSON.stringify(updates);
  const response = await fetch(`${api.baseUrl}${path}`, {
    method: 'PUT',
    headers: {
      ...api.signedHeaders('PUT', path, body),
      'Content-Type': 'application/json',
    },
    body,
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  return response.json();
}

// Usage - tradingPairId is a UUID from /api/v1/market/pairs
const tradingPairId = '123e4567-e89b-12d3-a456-426614174000';

const limitOrder = await placeOrder(api, {
  tradingPairId: tradingPairId,
  orderType: 'LIMIT',
  side: 'BUY',
  price: '2000.50',
  quantity: '1.5',
  tradingMode: 'SPOT',
});
console.log(`Limit Order ID: ${limitOrder.orderId}`);

const marketOrder = await placeOrder(api, {
  tradingPairId: tradingPairId,
  orderType: 'MARKET',
  side: 'SELL',
  price: null,
  quantity: '0.5',
  tradingMode: 'SPOT',
  slippageToleranceBps: 100, // optional: 1% max slippage
});
console.log(`Market Order ID: ${marketOrder.orderId}`);

const replaceResult = await replaceOrder(api, limitOrder.orderId, {
  price: '1950.00',
  quantity: '1.5',
});
console.log(`New Order ID: ${replaceResult.orderId}`);
console.log(`Original Order ID: ${replaceResult.originalOrderId}`);
```

## Security Considerations

1. **Handle Session Expiry** - Detect `401` responses and re-run the challenge/verify flow
2. **Validate Responses** - Always validate API responses before using data
3. **Use HTTPS** - All API requests must use HTTPS in production
4. **Revoke Sessions** - Implement logout by calling `POST /api/v1/auth/revoke`, and never persist the session private key insecurely


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