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

# Delegated Agents

> Authorize a separate signer wallet to trade on behalf of an owner account

```typescript theme={null}
import { MonacoSDK } from "@0xmonaco/core";

// Owner — register and manage agents
await sdk.delegatedAgents.upsertDelegatedAgent(request);
await sdk.delegatedAgents.listDelegatedAgents();
await sdk.delegatedAgents.revokeDelegatedAgent(agentId);

// Agent — discover the owners that delegate to it, then adopt an owner-scoped session
await sdk.delegatedAgents.listDelegatedOwners();
await sdk.loginAsDelegatedOwner(ownerUserId);
```

The owner retains custody of all funds, collateral movement, and withdrawal rights. The agent
may only perform the trading actions defined in its policy. For a conceptual overview see the
[Delegated Agents](/developers/delegated-agents).

***

## Owner

<ResponseField name="upsertDelegatedAgent" type="(request: UpsertDelegatedAgentRequest) => Promise<DelegatedAgent>">
  Create or update the policy for an agent wallet. If an agent with the same `agentAddress`
  already exists for this owner, its policy is replaced in full. An owner holds at most 10
  active, unexpired agents; registering another past that is rejected until one is revoked or
  expires.

  **Parameters:**

  * `request`: `UpsertDelegatedAgentRequest`
    * `agentAddress`: string — EVM wallet address of the agent
    * `name?`: string — human-readable label for the agent
    * `allowedActions`: `DelegatedAgentAction[]` — required; subset of `"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 an empty list or `["READ"]` is a read-only key that cannot manage orders. `"MANAGE_RISK_BUCKETS"` lets the agent call `transferCollateralToRiskBucket` for a margin account listed in `allowedMarginAccountIds`
    * `allowedTradingPairIds`: string\[] — required; trading pair UUIDs the agent may trade. Send `[]` to leave markets unrestricted for an account-bound agent
    * `allowedMarginAccountIds?`: string\[] — margin accounts the agent is bound to. Each listed scope must match: with both listed, an order needs a listed market **and** a listed account. Omit or send `[]` for a market-only agent, which trades its markets in every margin account; both scopes empty deny all orders
    * `allowedOrderTypes?`: `Array<"LIMIT" | "MARKET">` — omit or send `[]` for unrestricted order types
    * `allowedTimeInForce?`: `Array<"GTC" | "IOC" | "FOK">` — omit or send `[]` for unrestricted time in force
    * `maxLeverage?`: string — maximum leverage the agent may request, e.g. `"10"`
    * `maxOrderNotional?`: string — per-order notional cap in quote asset units, e.g. `"100000"`
    * `maxOpenOrders?`: number — unsupported legacy field; omit it. Requests setting it are rejected because this cap was never enforced
    * `expiresAt?`: string — ISO 8601 expiry; ends delegated reads, trading, and session refresh. Omit for the default of 14 days from now (also on an update); at most 180 days ahead
    * `ipAllowlist?`: string\[] — up to 20 IP addresses or CIDR ranges the agent may create its delegated session from. Omit or send `[]` to allow any address

  **Returns:** `DelegatedAgent`:

  * `id`: string — UUID, store for revocation
  * `ownerUserId`: string
  * `agentAddress`: string
  * `name?`: string
  * `isActive`: boolean
  * `expiresAt?`: string — ISO 8601
  * `revokedAt?`: string — ISO 8601, set once the agent is revoked
  * `allowedActions`: string\[]
  * `allowedTradingPairIds`: string\[]
  * `allowedMarginAccountIds`: string\[]
  * `allowedOrderTypes?`: string\[]
  * `allowedTimeInForce?`: string\[]
  * `maxLeverage?`: string
  * `maxOrderNotional?`: string
  * `maxOpenOrders?`: number — legacy data only; not an enforced limit
  * `ipAllowlist?`: string\[] — normalized CIDR ranges (a bare address reads back as `/32` or `/128`); empty allows any address

  Direct REST callers must include `allowedActions` and `allowedTradingPairIds`, even when empty. The other four arrays also accept JSON `null` over REST. TypeScript callers should omit optional arrays or send `[]`.

  **Example:**

  ```typescript theme={null}
  const agent = await sdk.delegatedAgents.upsertDelegatedAgent({
    agentAddress: "0xYourAgentWalletAddress",
    allowedActions: ["CREATE_ORDER", "CANCEL_ORDER", "REPLACE_ORDER"],
    allowedTradingPairIds: [btcPerpTradingPairId],
    allowedOrderTypes: ["LIMIT"],
    allowedTimeInForce: ["GTC", "IOC"],
    maxLeverage: "10",
    maxOrderNotional: "100000",
    expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
    ipAllowlist: ["203.0.113.0/24"],
  });

  console.log(agent.id); // store this for revocation
  ```
</ResponseField>

<ResponseField name="listDelegatedAgents" type="() => Promise<ListDelegatedAgentsResponse>">
  Return all delegated agents registered by the authenticated owner.

  **Returns:** `ListDelegatedAgentsResponse`:

  * `agents`: `DelegatedAgent[]`

  **Example:**

  ```typescript theme={null}
  const { agents } = await sdk.delegatedAgents.listDelegatedAgents();
  const active = agents.filter((a) => a.isActive);
  ```
</ResponseField>

<ResponseField name="revokeDelegatedAgent" type="(agentId: string) => Promise<{ status: 'REVOKED' }>">
  Revoke an agent policy and its existing delegated sessions. Subsequent signed requests,
  including reads and session refresh, fail authentication. Connected WebSockets lose access
  at their next periodic session revalidation (currently every 60 seconds). Orders already
  submitted are unaffected; arrange owner-side cancellation if needed.

  **Parameters:**

  * `agentId`: string — the `id` returned by `upsertDelegatedAgent`

  **Returns:**

  * `status`: `"REVOKED"`

  **Example:**

  ```typescript theme={null}
  await sdk.delegatedAgents.revokeDelegatedAgent(agent.id);
  ```
</ResponseField>

***

## Agent

<ResponseField name="listDelegatedOwners" type="() => Promise<ListDelegatedOwnersResponse>">
  Reverse lookup keyed on the authenticated agent's own wallet address. Returns the owners that
  currently have an active delegation to this agent — non-revoked and non-expired only. Use this
  to discover the `ownerUserId` to pass into `loginAsDelegatedOwner`, so the agent does not
  need the owner's ID supplied out of band.

  **Returns:** `ListDelegatedOwnersResponse`:

  * `owners`: `DelegatedAgentOwner[]`:
    * `ownerUserId`: string — pass this into `loginAsDelegatedOwner`
    * `delegationId`: string — UUID of the delegation record
    * `name?`: string — the label the owner gave this agent
    * `isActive`: boolean
    * `expiresAt?`: string — ISO 8601 expiry of the delegation

  **Example:**

  ```typescript theme={null}
  // The agent authenticates with its own wallet first (session-key login)
  await sdk.login(agentClientId);

  const { owners } = await sdk.delegatedAgents.listDelegatedOwners();
  const ownerUserId = owners[0].ownerUserId;
  ```
</ResponseField>

<ResponseField name="loginAsDelegatedOwner" type="(ownerUserId: string) => Promise<AuthState>">
  Adopt an owner-scoped delegated session and route all subsequent SDK calls through it. This is
  the method agents should use to act on an owner — it generates a fresh ed25519 session keypair,
  registers it with `createDelegatedSession`, and installs the resulting owner-scoped session as
  the SDK's active auth state. Every read and trading call after it operates on the owner account,
  with the agent wallet address kept as the session's actor for policy enforcement.

  The agent must already be authenticated with its own wallet (`sdk.login`). The owner keeps
  custody, collateral, positions, PnL, risk, and withdrawal control. The delegated session cannot
  withdraw, and trading mutations are re-checked against the active policy every time.

  **Parameters:**

  * `ownerUserId`: string — the `ownerUserId` of the account owner who registered this agent,
    as returned by `listDelegatedOwners`

  **Returns:** `AuthState` — the adopted owner-scoped session:

  * `user.id`: string — the owner's user ID (the identity subsequent calls act on)
  * `user.address`: string — the agent's wallet address (the preserved actor)
  * `expiresAt`: number — Unix timestamp (seconds), no later than the grant's expiry. Revocation or an earlier policy expiry ends access sooner
  * `sessionPublicKey` / `sessionPrivateKey`: the freshly generated owner-scoped session keypair

  **Example:**

  ```typescript theme={null}
  // 1. The agent authenticates with its own wallet (session-key login)
  await sdk.login(agentClientId);

  // 2. Discover an owner that delegates to this agent
  const { owners } = await sdk.delegatedAgents.listDelegatedOwners();
  const ownerUserId = owners[0].ownerUserId;

  // 3. Adopt the owner-scoped session — subsequent calls act on the owner
  const auth = await sdk.loginAsDelegatedOwner(ownerUserId);
  // auth.user.id is the owner; auth.user.address is the agent (the session's actor);
  // the session expires at auth.expiresAt; earlier revocation also ends access.

  // 4. Place orders against the owner's perps accounts — signed with the owner session
  await sdk.trading.placeLimitOrder(btcPerpTradingPairId, "BUY", "0.5", "65000", {
    tradingMode: "MARGIN",
    leverage: "5",
    strategyKey: "btc-mm-a",
  });
  ```

  The `strategyKey` field causes Monaco to resolve the owner's parent perps account and
  target the isolated bucket for the `(tradingPair, strategyKey)` combination. Omit it to use the
  owner's default bucket for the pair. See [`placeLimitOrder`](/sdk/typescript/trades)
  for full order placement options.
</ResponseField>

<ResponseField name="createDelegatedSession" type="(request: CreateDelegatedSessionRequest) => Promise<CreateDelegatedSessionResponse>">
  Low-level primitive that exchanges the agent's own authenticated session for an owner-scoped
  delegated session record. Prefer `loginAsDelegatedOwner` (above), which calls
  this and then installs the resulting keypair as the SDK's active session. Calling
  `createDelegatedSession` on its own registers a session but does **not** switch the SDK to it, so
  subsequent calls keep signing with the agent's own session and act on the agent account, not the
  owner.

  The agent must first authenticate with its own wallet via the standard session-key login. When
  the delegation has an `ipAllowlist`, the call must come from an address inside one of its
  ranges. A delegated session acts against the owner account while preserving the agent wallet address as the
  actor. The owner keeps custody, collateral, positions, PnL, risk, and withdrawal control. The
  delegated session cannot withdraw, and trading mutations are checked against the active policy
  every time they are submitted.

  **Parameters:**

  * `request`: `CreateDelegatedSessionRequest`
    * `ownerUserId`: string — the `ownerUserId` of the account owner who registered this agent,
      as returned by `listDelegatedOwners`
    * `sessionPublicKey`: string — the lowercase-hex (64-char) ed25519 session public key the
      agent generates for this delegated session; subsequent requests acting on the owner's
      behalf are signed with the matching private key

  **Returns:** `CreateDelegatedSessionResponse`:

  * `expiresAt`: number — Unix timestamp when the delegated session expires, bounded by the grant's expiry
  * `delegationId`: string — UUID of the active delegation record
  * `ownerUserId`: string — the owner's user ID
  * `agentAddress`: string — the agent's wallet address
</ResponseField>

***

## Policy Enforcement

Delegated sessions require an active, unexpired grant for every signed request and periodic
WebSocket session revalidation. Trading actions also reload and enforce the detailed policy
before create, cancel, replace, and close-position actions. Trading scopes do not filter owner
account reads while the grant remains active.

`sdk.refreshAuth()` refreshes a still-valid delegated session without changing its keypair,
but cannot extend it beyond the grant expiry. It cannot revive an expired session or renew
the owner's authorization. Keep the agent's ordinary login separate from the adopted
delegated session. To replace an expired session, use that agent login to create a fresh
delegated session; an expired grant first needs owner reauthorization.

Expiry is not permanent key revocation. If the owner renews a grant, a session whose own
expiry has not passed may become usable again. Successful `revokeDelegatedAgent()` also
revokes existing session rows, and renewing the grant does not revive those revoked sessions.

Close-position calls are checked like order creation because they submit a close or reduce order.
For delegated batch close-all flows, scope the request to an explicit trading pair so the policy
check cannot accidentally cover every open market.

## Errors

* `400` — a required JSON array is missing, a policy value is invalid (including an unknown action,
  an `expiresAt` more than 180 days ahead, or an `ipAllowlist` with more than 20 entries or an entry
  that is not an IP address or CIDR range), or unsupported `maxOpenOrders` is set
* `401` — a delegated session is expired/revoked, or its backing grant is missing, inactive,
  expired, or revoked; applies to account reads, refresh, and signed trading requests
* `403` — agent wallet is not registered to the given owner, the policy is expired, or the
  requested action is outside the agent's `allowedActions`
* `403` — `createDelegatedSession` called from a non-agent wallet (the agent must authenticate
  with its own address first), or from an address outside the delegation's `ipAllowlist`
* `409` — `createDelegatedSession` called with a `sessionPublicKey` that is already registered
  (for example, reused from a prior session). Generate a fresh ed25519 session keypair per session
* `404` — `revokeDelegatedAgent` called with an `agentId` that does not belong to the
  authenticated owner


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