# API key and Subaccount policies

Grant API keys and Subaccounts exactly the actions and Pairs they need with PolicyService, and understand how the two policies combine.

A policy is a reusable set of permissions. Polyester uses two kinds:

- An **API key policy** limits what one API key can do. A key without a policy can only create and list deposit addresses.
- A **Subaccount policy** is the ceiling for a Subaccount. No member role and no API key can exceed it.

Both are managed through `PolicyService` with an interactive session; every policy read and write accepts only a session token. API keys cannot create, change, or attach policies.

> **A new API key cannot read or trade**
>
> API key creation does not accept scopes or a policy. Attach an API key policy before the key can read or trade. Until then every request from the key is refused, except deposit-address creation and listing, which no policy controls.

## How a request is checked

For an API key request, Polyester applies these checks in order. Each one can only remove permissions:

1. The key's scope: Main Account, or its bound Subaccount. See [Subaccounts and roles](https://testnet.polyester.com/docs/developer-docs/account/subaccounts-and-roles#target-a-subaccount-in-api-calls).
2. The key's API key policy: the requested action and, for trading, the market.
3. For a Subaccount, the Subaccount policy: the same action and market, plus its limits.
4. The endpoint's own rules: balances, destination whitelists, MFA, rate limits, and request validation.

The effective permission is the intersection of the two policies. Attaching a broad API key policy to a key on a restricted Subaccount does not widen the Subaccount. Main Account keys have no Subaccount policy, so only the API key policy applies.

Interactive members follow the same order with their role in place of the API key policy.

## Policy actions

Both policy kinds use the `PolicyAction` enum. A write action also grants the matching read.

| Action                    | Allows                                                                                                          |
| ------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `TRADE_SPOT`              | Every spot order mutation, cancel-all, cancel-all-after, and trigger placement or change. Includes `READ_SPOT`. |
| `READ_SPOT`               | Spot orders, trades, subscriptions, cancel-all dry-run, and effective fee rates. Never a mutation.              |
| `READ_BALANCES`           | Balances, balance history, and equity.                                                                          |
| `READ_INTERNAL_TRANSFERS` | Ledger transfer history (`ListTransfers`).                                                                      |
| `INTERNAL_TRANSFER`       | Trading-to-Trading internal transfers and Trading-to-Funding withdrawals. Includes `READ_INTERNAL_TRANSFERS`.   |
| `EXTERNAL_WITHDRAW`       | Trading withdrawals to an external Network. Destination whitelists are checked separately.                      |
| `READ_ADDRESS_BOOK`       | Saved, recent, and whitelisted destinations.                                                                    |
| `MANAGE_ADDRESS_BOOK`     | Address-book writes for sessions. On an API key policy it grants reads only, because writes need a session.     |
| `READ_AUTO_WITHDRAW`      | The auto-withdraw rule and its runs.                                                                            |
| `MANAGE_AUTO_WITHDRAW`    | Changing the auto-withdraw rule. Also needs `EXTERNAL_WITHDRAW`. API keys can never change a rule.              |
| `SWAP`                    | Quoting, creating, and reading Swaps of the account's own Trading balance.                                      |

Deposit-address creation and listing are outside policy scope. No policy action controls them, and an attached policy never blocks them. See [Deposits](https://testnet.polyester.com/docs/developer-docs/funds-transfers/deposits).

### Market scope

`spotMarketScope` is `ALL` or `ALLOWLIST`. `ALL` allows every current and future market. `ALLOWLIST` allows only the Pairs listed in `spotMarkets`, by `symbolId` from the spot configuration. An `ALLOWLIST` with no markets is invalid.

## Subaccount policies

A Subaccount policy carries the actions and market scope above, plus enforced limits:

| Field              | Effect                                                                                                                                        |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxOrderNotional` | Largest single-order notional, in quote microunits (one unit is 0.000001 USDT). `0` means no cap. Exceeding it returns `POLICY_MAX_NOTIONAL`. |
| `maxOpenOrders`    | Most resting orders at one time. `0` means no cap. Reaching it returns `POLICY_MAX_OPEN_ORDERS`.                                              |
| `tradingHalted`    | Rejects new orders and exposure-increasing changes with `POLICY_TRADING_HALTED`. Cancels and pauses still work.                               |
| `locked`           | Write-protects the policy. Only the owner can lock, unlock, or change a locked policy.                                                        |

`reviewAt` and `expiresAt` are governance metadata only. They do not disable the policy.

Every Subaccount policy keeps a mandatory read-only floor: `READ_BALANCES`, `READ_SPOT`, `READ_INTERNAL_TRANSFERS`, `READ_ADDRESS_BOOK`, and `READ_AUTO_WITHDRAW`. With no policy attached, a Subaccount uses exactly that floor, so it cannot trade or move funds.

### Manage Subaccount policies

Each operation lists its method, then who can call it.

- **List and read** · [`ListSubaccountPolicies`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/ListSubaccountPolicies), [`GetSubaccountPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/GetSubaccountPolicy)\
  Owner; current members for the attached policy.
- **Create** · [`CreateSubaccountPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/CreateSubaccountPolicy)\
  Owner; an admin only when attaching in the same request.
- **Update** · [`UpdateSubaccountPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/UpdateSubaccountPolicy)\
  Owner; admins only for unlocked policies.
- **Delete an unused policy** · [`DeleteSubaccountPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/DeleteSubaccountPolicy)\
  Owner only.
- **Attach or replace** · [`SetSubaccountPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/SetSubaccountPolicy)\
  Owner; admins unless the current policy is locked.
- **Clear** · `SetSubaccountPolicy` with `policyId` `0`\
  Owner only.

## API key policies

An API key policy has a name, description, actions, market scope, and an `isTemplate` flag for reuse. Read them with [`ListApiPolicies`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/ListApiPolicies) and [`GetApiPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/GetApiPolicy). Create one with [`CreateApiPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/CreateApiPolicy); set `assignToKeyId` to attach it to a key in the same request, which uses one step-up proof. Attach or replace a policy later with [`SetApiKeyPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/SetApiKeyPolicy). Clearing a key's policy (`policyId` `0`) is owner-only and leaves the key with no permissions beyond deposit addresses.

A policy that is still attached cannot be deleted with [`DeleteApiPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/DeleteApiPolicy); the request fails with `AUTH_POLICY_IN_USE`. Updates with [`UpdateApiPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/UpdateApiPolicy) take an `updateMask` and `expectedRevision`. A stale revision fails before any change.

### Right after a grant

A policy change can take a moment to reach every service. A request sent in that moment, which a grant you just made allows, is not refused with a permission error. Orders, triggers, withdrawals, and internal transfers answer a retryable `Unavailable` (REST `503`) with the message "policy is not mirrored yet; retry": `UNAVAILABLE` on orders and triggers, and `SERVICE_UNAVAILABLE` on withdrawals and internal transfers. Nothing was placed, reserved, or moved. Retry with backoff and the same idempotency identity. A refusal that the current policy confirms stays a permission error, such as `POLICY_SPOT_TRADE_DENY`; do not retry it unchanged.

## API key lifecycle

Creating, updating, disabling, rotating, and revoking keys are session-only operations, and each mutation needs a fresh step-up. Attach the policy with `SetApiKeyPolicy` after you create a key; until then the key can only create and list deposit addresses. Every step, from registration to rotation without downtime, is in [API key lifecycle](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-lifecycle).

### IP allowlists

Restrict where a key can be used with `ipWhitelist`, a list of up to 32 unique IPv4 or IPv6 CIDRs. An empty list means no restriction. Requests from outside the list fail authentication with REST code `IP_NOT_ALLOWED` (ConnectRPC `AUTH_IP_NOT_ALLOWED`). See [API key IP allowlists](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-ip-allowlists).

## Common refusals

| Code                      | Meaning                                              |
| ------------------------- | ---------------------------------------------------- |
| `API_KEY_POLICY_REQUIRED` | The key has no policy attached.                      |
| `API_KEY_ACTION_DENY`     | The key's policy does not allow the action.          |
| `API_KEY_MARKET_DENY`     | The key's policy does not allow this market.         |
| `SUBACCOUNT_POLICY_DENY`  | The Subaccount policy does not allow the action.     |
| `POLICY_SPOT_TRADE_DENY`  | Spot trading is not allowed by the effective policy. |
| `POLICY_MARKET_DENY`      | The Subaccount policy does not allow this market.    |
| `ROLE_SPOT_TRADE_DENY`    | The member's role cannot trade.                      |

The exact code depends on the endpoint. See [Error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes) for the full catalog, and the [API reference](https://testnet.polyester.com/docs/api-docs) for field schemas.
