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

# API keys and WebSocket

> The vault:migrate and positions:write scopes, how to get a key that carries them, what a key can do without your signature, and the per-vault WebSocket changes.

## Scopes

| Scope | Grants | Partner key | Self-service key permission |
| - | - | - | - |
| `markets:read`, `portfolio:read`, `orders:read` | Reads | ✓ | `read` |
| `orders:write` | Place and cancel orders | ✓ | `trade` |
| `vault:migrate` (new) | `POST /api/me/vault`, `POST /api/me/vault/upgrade`, the USDC move (`POST /api/me/vault/upgrade/transfer`, `…/{id}/signature`); reads `GET /api/me/vault` and the move status | ✓ (`single_wallet` keys only) | `vault` (new) |
| `positions:write` (new) | Split / merge / convert signatures and `op-submit`, redeem, auto-redeem, on any of your vaults; reads `GET /api/me/vault` | ✓ (`single_wallet` keys only) | `vault` (new) |
| `withdraw:write` | `POST /api/withdrawals/request`, `POST /api/me/withdrawals/{id}/cancel` | ✓ (new for partner keys) | `withdraw` |
| `vault:write` | Every vault route above, the deposit relay, and the withdrawal-security (2FA / whitelist) settings | ✓ (existing keys keep working) | — |

Keys issued before this release keep exactly their scopes. A key
without `vault:migrate` or `vault:write` gets `403 api_key_scope_missing`
on the upgrade.

## Getting a key with the new scopes

| Key | How |
| - | - |
| `single_wallet` partner key | Ask your integration manager for `vault:migrate`, `positions:write` and, if the bot withdraws, `withdraw:write` |
| `multi_wallet` partner key | Multi-wallet partner keys: migration is enabled for your partner account by your integration manager once your payout addresses are listed for generation-2 withdrawals. Multi-wallet keys use `vault:write` (`vault:migrate` and `positions:write` are single-wallet only). |
| Self-service key | The web app's API-keys page will offer the Vault permission. Mint a new key with it (which grants `['vault:migrate', 'positions:write']`); existing keys keep their permissions |

## What a key can do without your signature

| Action | Needs |
| - | - |
| Create the vault, upgrade it | The key alone. The upgrade is one-way: it makes the generation-2 vault active, so deposits go there. It moves no funds |
| Move USDC between your vaults | Your `WithdrawERC20` signature on the generation-1 vault. No 2FA code |
| Orders, split / merge / convert, redeem | Your signature as before: the order, the owner signature at `op-submit`, the `RedeemAuthorization` |
| Split / merge signature requests, split / merge unlock, auto-redeem preference | The key alone; none moves value out of a vault |
| Withdraw | Your `WithdrawERC20` signature, the whitelist if set, `totpCode` if 2FA is on |
| Change 2FA or the whitelist | A partner `vault:write` key, as before. Being rolled out: 2FA setup / disable and whitelist add / remove also carry your EIP-712 `WithdrawalSecurityChange` signature, and a whitelist change with 2FA on carries your current code; once enforced, an address the key adds to a whitelist that already has an entry waits 24 hours by default, and the key cannot remove the last usable address or the last address. Every change that takes effect (2FA turned on or off, an address added or removed) sends you a notice. See [Withdrawal 2FA](https://docs.testnet.dev.adipredictstreet.com/concepts/withdrawals/two-factor.md#owner-signature-on-changes-made-with-an-api-key) |

A leaked key with `vault:migrate` can migrate your account
irreversibly but cannot take funds. Give a bot only the scopes it uses.
Keys of a closed, blocked or banned account are refused on REST and on
the WebSocket.

## WebSocket

`/ws/user` authenticates as before. The `connected` greeting adds
`vaultAddresses`, every vault of the bound wallet:

```json theme={null}
{
  "type": "connected",
  "data": {
    "gateway":         "user",
    "walletAddress":   "0x…",
    "vaultAddress":    "0x…active at connect",
    "vaultAddresses":  ["0x…gen1", "0x…gen2"],
    "authMethod":      "api_key",
    "protocolVersion": 2
  }
}
```

`vault_positions` and `vault_activity` accept any address in
`vaultAddresses` as an id:

```json theme={null}
{ "id": "s1", "cmd": "subscribe", "params": { "subscriptions": [
  { "channel": "vault_activity",  "ids": ["0x…gen1", "0x…gen2"] },
  { "channel": "vault_positions", "ids": ["0x…gen1", "0x…gen2"] },
  { "channel": "user_activity" } ] } }
```

| Channel | Change |
| - | - |
| `user_activity` | New `vault_activated` (`{ walletAddress, vaultAddress, previousVaultAddress, generation, activatedAt }`) when the upgrade activates, and `vault_move_updated` on every USDC-move status |
| `vault_activity` | Frames name the vault in `data.vault`. Move legs carry `vaultMoveId` and `vaultMoveLeg` (`withdraw` on generation 1, `deposit` on generation 2, `refund`) |
| `vault_positions` | Frames name the vault in `data.vaultAddress` |

* Upgrading while connected: subscribe to the new vault on the same
  socket, no reconnect needed. `vaultAddress` stays the one from the
  handshake; use `vault_activated` to learn the active vault.
* Close codes: a closed, blocked or banned account is refused at handshake — and any open sockets for that wallet (both JWT sessions and API-key sockets) are closed immediately when a restriction or ban event fires, with a 30-second periodic re-check as a backstop — with
  `4401` and reason `account_closed`, `account_blocked` or
  `wallet_banned` (`account_unavailable` for a multi-wallet key's
  `X-User-Wallet`). Rotating a key closes its sockets with
  `4409 api_key_changed`; an expired key's sockets close when it
  expires.


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