> For the complete documentation index, see [llms.txt](https://sodex.com/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sodex.com/documentation/for-developers/developers/trading/api-keys-and-nonces.md).

# API Keys and Nonces

The docs overload the word "key" in several ways. This table pins the meanings down once; later sections assume these definitions.

| Term                          | What it is                                                                                                                                                                                                                                                                                                                                      | Example                                                   |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| **Master wallet**             | The EVM wallet that owns the Sodex account. Deposits and `addAPIKey` / `revokeAPIKey` must be signed by this wallet.                                                                                                                                                                                                                            | EVM address `0xAbC...123`                                 |
| **Master wallet private key** | The 32-byte ECDSA private key of the master wallet. Holding it grants full control of the account. Reserve it for account-level actions such as API key registration, revocation, and builder-fee approval; use a dedicated API key for routine trading.                                                                                        | `0x1234...cdef` (32 bytes, hex)                           |
| **API key**                   | A named, revocable signing credential attached to the master account (or a sub-account) via `addAPIKey`. Each master account can hold up to **5** API keys. API keys are for **signing trading actions** — they cannot query account data. Using an API key (rather than the master wallet) for day-to-day trading is the recommended workflow. | a row with `name="api-key-01"`, `publicKey=0x3d45...8256` |
| **API key name**              | The human-readable string that identifies one API key. Must match `^[0-9a-zA-Z_-]{1,36}$` and cannot be `default`. Passed in the `X-API-Key` HTTP header (despite the header's name, the value is the key *name*, not a public key or private key).                                                                                             | `"api-key-01"`                                            |
| **API key public key**        | The EVM address registered for that API key. Stored on-chain when you call `addAPIKey` and also echoed in query responses.                                                                                                                                                                                                                      | `0x3d4595c8742d0a58173a9963c05755b59a8f8256`              |
| **API key private key**       | The 32-byte ECDSA private key whose public address matches the API key's public key. Held by the client; used to sign every request that presents that API key's name in `X-API-Key`.                                                                                                                                                           | `0xabcd...7890` (32 bytes, hex)                           |

**Which key signs what**

| Action                                                                                                         | Who signs                                                | Which private key                 |
| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------- |
| `addAPIKey` / `addPermissionedAPIKey` / `approveBuilderFee` / `revokeAPIKey`                                   | Master wallet                                            | Master wallet's private key       |
| All other trading actions (e.g. `newOrder`, `cancelOrder`, `newTwapOrder`, `cancelTwapOrder`, `transferAsset`) | A registered API key (recommended), or the master wallet | The selected signer's private key |

> **Recommended workflow:** use the master wallet's private key **only** for account-level actions such as registering or revoking API keys and approving builder fees. For all normal trading requests, sign with a dedicated API key's private key. This lets you keep the master wallet offline and rotate signing credentials without moving funds.

Direct master-wallet signing is supported but not recommended for routine trading. Omit `X-API-Key` when using the master wallet; when the header names an API key, the signature must match that key.

**Header naming caveat** — the HTTP header `X-API-Key` carries the **name** of the key, not the key value. The corresponding private key is used to produce `X-API-Sign`; the private key itself is never sent over the wire.

## API keys

A master account can approve or revoke API keys to sign on behalf of the master account or any of the sub-accounts. Each master account can have at most **5** API keys.

Sodex currently supports EVM addresses as API key public keys. The client holds the API key's **private key** and uses it to sign each request; the server verifies the signature against the API key's registered public key.

API keys are only used to sign. To query account data associated with a master or sub-account, pass the actual `accountID` of that account — API keys are not lookup identifiers. See [Get account ID](/documentation/for-developers/api-reference/trading-api.md#get-account-id) for how to retrieve your account ID.

## Register a trading API key

Use two separate wallets: the **master wallet** authorizes registration, and the **API key wallet** signs subsequent orders. The registered `publicKey` is the API key wallet's EVM address, not its private key. Keep both private keys local and never put them in source control or logs.

Create a dedicated EVM key pair using your wallet or key-management tooling and store its private key securely as `SODEX_API_KEY_PRIVATE_KEY`. Choose a key name matching `^[0-9a-zA-Z_-]{1,36}$`; `default` is reserved. If you already have a registered, unexpired trading key, verify it using the query below and skip registration.

Activate and fund your testnet trading account before running this example. It submits a real registration on Testnet (chain ID `138565`).

This example registers an ordinary trading key without `builder` or `permissions`. It expires after 24 hours. Install the dependencies, save the code as `register-api-key.ts`, and configure the environment:

```bash
npm install viem tsx
export BASE_URL='https://testnet-gw.sodex.dev'
export USER_ADDRESS='YOUR_MASTER_WALLET_ADDRESS'
curl -sS "$BASE_URL/api/v1/perps/accounts/$USER_ADDRESS/state" \
  -H 'Accept: application/json'
# Use data.aid from the successful account-state response.
export ACCOUNT_ID='YOUR_ACCOUNT_ID'
export SODEX_API_KEY_NAME='quickstart-key'
# Set SODEX_MASTER_PRIVATE_KEY and SODEX_API_KEY_PRIVATE_KEY securely.
npx tsx register-api-key.ts
```

```typescript
import { concatHex } from "viem";
import { privateKeyToAccount } from "viem/accounts";

async function main() {
  const accountID = Number(process.env.ACCOUNT_ID);
  const name = process.env.SODEX_API_KEY_NAME;
  const masterKey = process.env.SODEX_MASTER_PRIVATE_KEY;
  const apiKey = process.env.SODEX_API_KEY_PRIVATE_KEY;
  if (!Number.isSafeInteger(accountID) || accountID <= 0) {
    throw new Error("Set an exact account ID from account state; use an SDK for larger IDs");
  }
  if (!name || !/^[0-9a-zA-Z_-]{1,36}$/.test(name) || name === "default") {
    throw new Error("Set a valid, non-reserved API key name");
  }
  if (!masterKey || !apiKey) throw new Error("Set both local private keys");
  const master = privateKeyToAccount(masterKey as `0x${string}`);
  const tradingKey = privateKeyToAccount(apiKey as `0x${string}`);
  if (master.address === tradingKey.address) throw new Error("Use a separate trading key");
  const nonce = BigInt(Date.now());
  const expiresAt = Number(nonce) + 24 * 60 * 60 * 1000;
  const signature = await master.signTypedData({
    domain: { name: "universal", version: "1", chainId: 138565,
      verifyingContract: "0x0000000000000000000000000000000000000000" },
    types: { AddAPIKey: [
      { name: "accountID", type: "uint64" },
      { name: "name", type: "string" },
      { name: "keyType", type: "uint8" },
      { name: "publicKey", type: "bytes" },
      { name: "expiresAt", type: "uint64" },
      { name: "nonce", type: "uint64" },
    ] },
    primaryType: "AddAPIKey",
    message: { nonce, accountID: BigInt(accountID), name,
      keyType: 1, publicKey: tradingKey.address, expiresAt: BigInt(expiresAt) },
  });
  // Normalize viem's recovery byte (27/28) to the wire format (0/1).
  const recovery = Number.parseInt(signature.slice(-2), 16);
  const wireSignature = (signature.slice(0, -2) +
    (recovery >= 27 ? recovery - 27 : recovery).toString(16).padStart(2, "0")) as `0x${string}`;
  const response = await fetch(
    `https://testnet-gw.sodex.dev/api/v1/user/${master.address}/api-keys`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json", "Accept": "application/json",
        "X-API-Chain": "138565", "X-API-Nonce": nonce.toString(),
        "X-API-Sign": concatHex(["0x02", wireSignature]),
      },
      body: JSON.stringify({ accountID, name, type: 1,
        publicKey: tradingKey.address, expiresAt }),
    },
  );
  const result = await response.json();
  if (!response.ok || result.code !== 0) {
    throw new Error(result.error ?? `Registration failed: HTTP ${response.status}`);
  }
  console.log("Registration accepted; query API keys before placing an order.");
}

main().catch((error) => { console.error(error.message); process.exitCode = 1; });
```

Registration uses the **master wallet**, the `universal` domain, and signature prefix `0x02`. The EIP-712 message calls the key type `keyType`, while the HTTP body calls it `type`. The nonce belongs in both the signed message and the header, not the HTTP body. See [Add API Key signing](/documentation/for-developers/developers/trading/authentication-and-signing.md#add-api-key) for the original structures.

### Confirm registration

Use the same master wallet address, account ID, and key name:

```bash
curl -sS --get "$BASE_URL/api/v1/user/$USER_ADDRESS/api-keys" \
  --data-urlencode "accountID=$ACCOUNT_ID" \
  --data-urlencode "name=$SODEX_API_KEY_NAME" \
  -H 'Accept: application/json'
```

Check `code == 0`, then confirm the key's `name`, `publicKey`, and future `expiresAt` in both `data.spot` and `data.perps`. Registration succeeds only when both engines accept it; indexed queries may lag. If the response is ambiguous or the key is not visible yet, query again before retrying registration. Do not trade until the key appears for the intended account in both engines.

Keep `SODEX_API_KEY_NAME` and `SODEX_API_KEY_PRIVATE_KEY` for trading; the master private key is not needed for orders. See [Sodex nonces](#sodex-nonces) for nonce coordination and [Revoke API Key](/documentation/for-developers/api-reference/trading-api/rest-v1/sodex-rest-public-api.md#revoke-api-key) when rotating or retiring a key.

## Sodex nonces

Similar to Hyperliquid, on Sodex the **`100`** highest nonces are stored per signing address. Every new transaction must have a nonce larger than the smallest nonce in this set and must never have been used before.

Nonces are tracked per signing address:

* For trading actions signed by an API key, this is the **API key's public key** (EVM address) — the one you registered via `addAPIKey`. Direct master-wallet signing uses the master wallet's address instead.
* For `addAPIKey` / `revokeAPIKey`, this is the **master wallet's address**, which has its own independent nonce counter.

Nonces must be within `(T - 2 days, T + 1 day)`, where `T` is the Unix millisecond timestamp on the block of the transaction.

The following steps may help port over an automated strategy from a centralized exchange:

1. Use a separate API key per trading process. Nonces are tracked per signing address (see above), so two sub-accounts that both sign with the same API key share a single nonce tracker — concurrent strategies on each sub-account will race on the nonce. Create one API key per sub-account to avoid this.
2. The trading logic tasks send orders and cancels to the batching task.
3. For each batch of orders or cancels, fetch and increment an atomic counter that ensures a unique nonce for the address. The atomic counter can be fast-forwarded to current Unix milliseconds if needed.

This structure is robust to out-of-order transactions within `2` seconds, which should be sufficient for an automated strategy geographically near an API server.

## Signing requests

{% content-ref url="/pages/o3Yj9kHutFbmg6uDLnhS" %}
[Authentication & Signing](/documentation/for-developers/developers/trading/authentication-and-signing.md)
{% endcontent-ref %}
