# Overview

## Endpoint

* Mainnet REST Endpoint
  * Spot: `https://mainnet-gw.sodex.dev/api/v1/spot`
  * Perps: `https://mainnet-gw.sodex.dev/api/v1/perps`
* Mainnet WebSocket Endpoint
  * Spot: `wss://mainnet-gw.sodex.dev/ws/spot`
  * Perps: `wss://mainnet-gw.sodex.dev/ws/perps`
* Testnet REST Endpoint
  * Spot: `https://testnet-gw.sodex.dev/api/v1/spot`
  * Perps: `https://testnet-gw.sodex.dev/api/v1/perps`
* Testnet WebSocket Endpoint
  * Spot: `wss://testnet-gw.sodex.dev/ws/spot`
  * Perps: `wss://testnet-gw.sodex.dev/ws/perps`

## Key terminology

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, so it should stay offline and be used only for the one-time `addAPIKey` setup and for revocation.                                                                                                                                            | `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` / `revokeAPIKey`                                                | Master wallet        | Master wallet's private key |
| All other trading actions (e.g. `newOrder`, `cancelOrder`, `transferAsset`) | A registered API key | That API key's private key  |

> **Recommended workflow:** use the master wallet's private key **only** to register and revoke API keys. 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.

**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 and nonces

### 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](#get-account-id) for how to retrieve your account ID.

### 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, this is the **API key's public key** (EVM address) — the one you registered via `addAPIKey`.
* 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.

### Typed signature

In Sodex, we use [EIP712](https://eips.ethereum.org/EIPS/eip-712) for Typed structured data hashing and signing. We use different domains for different actions.

#### Trading actions

```typescript
{
  types: {
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" }
    ],
    ExchangeAction: [
      { name: 'payloadHash', type: "bytes32" },
      { name: 'nonce', type: 'uint64' }
    ],
  },
  domain: {
    name: "spot" or "futures",
    version: "1",
    chainId: 286623 or 138565,
    verifyingContract: "0x0000000000000000000000000000000000000000"
  },
  primaryType: "ExchangeAction",
  message: {
    payloadHash: "0x7521d1cadbcfa91eec65aa16715b94ffc1c9654ba57ea2ef1a2127bca1127a83",
    nonce: 1760373925000,
  }
}
```

For mainnet use `286623` for `domain.chainId`, for testnet use `138565` for `domain.chainId`. For spot actions, use `spot` for `domain.name` and for perps actions, `futures` for `domain.name`.

After you get the signature bytes `sig`, append byte `1` before the sig bytes to get typed signature. For example, your signed signature is `0x789a6bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab`, the correct typed signature is `0x01789a6bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab`.

#### How to compute `payloadHash`

`payloadHash = Keccak256(json.Marshal(payload))`

The payload is a JSON object with two fields:

```typescript
{
  "type": "<actionName>",   // e.g. "newOrder", "cancelOrder", "updateLeverage"
  "params": { ... }         // action-specific parameters
}
```

* For spot: `type` is one of `"newOrder"`, `"cancelOrder"`, `"transferAsset"`, `"scheduleCancel"`, `"revokeAPIKey"`, and more.
* For perps: `type` is one of `"newOrder"`, `"cancelOrder"`, `"updateLeverage"`, `"updateMargin"`, `"updateCollateral"` (testnet only), `"transferAsset"`, `"scheduleCancel"`, `"revokeAPIKey"`, and more.

You must use your private key of the master account to sign `revokeAPIKey` action.

**Important rules for producing a correct `payloadHash`:**

1. **Compact JSON** — no whitespace or newlines. Use `json.Marshal` in Go, or `JSON.stringify` without extra arguments in JavaScript.
2. **Key order must match the Go struct field order** — the server verifies signatures by parsing the request body into Go structs and re-marshaling via `json.Marshal`, which serializes fields in struct definition order. If your JSON keys are in a different order, the hash will differ and signature verification will fail.
   * Refer to the struct definitions in [sodex-go-sdk-public](https://github.com/sodex-tech/sodex-go-sdk-public) for the authoritative field order.
   * For example, `PerpsOrderItem` fields must appear in this order: `clOrdID`, `modifier`, `side`, `type`, `timeInForce`, `price`, `quantity`, `funds`, `stopPrice`, `stopType`, `triggerType`, `reduceOnly`, `positionSide`.
3. **`DecimalString` fields are JSON strings, not numbers** — fields typed as `DecimalString` in the schema (e.g. `price`, `quantity`, `funds`, `stopPrice`) must be serialized as quoted strings in the signing payload (e.g. `"quantity":"0.001"`, not `"quantity":0.001`). The HTTP request body uses the same format.
4. **`omitempty` fields must be omitted when unset** — optional pointer fields in the Go struct (those with `json:",omitempty"`) must not appear in the JSON when they have no value. Non-optional fields (e.g. `modifier`, `reduceOnly`, `positionSide`) must always be present, even with zero values.

**Example** — perps market buy order signing payload:

```json
{"type":"newOrder","params":{"accountID":12345,"symbolID":1,"orders":[{"clOrdID":"my-order-1","modifier":1,"side":1,"type":2,"timeInForce":3,"quantity":"0.001","reduceOnly":false,"positionSide":1}]}}
```

Note: the HTTP request body contains only the `params` object (without the `type` wrapper), using the same field order and types as the signing payload.

### End-to-end signing example

Signing a `newOrder` action with a registered API key, end-to-end.

> Prerequisite: you have already called `addAPIKey` (signed by the master wallet — see [Add API Key](https://github.com/sosovalue-tech/sodex-docs/blob/main/trading-api/add-api-key.md)) and hold the API key's private key locally.

#### Inputs

```
Registered API key:
  name                   = "api-key-01"         ← this is what X-API-Key carries
  publicKey (EVM addr)   = 0x3d4595c8742d0a58173a9963c05755b59a8f8256
  private key            = 0x2222222222222222222222222222222222222222222222222222222222222222
                           ← held by the client, used to produce X-API-Sign

Nonce                    = 1760373925001
Signing payload hash     = keccak256(compact JSON of {type, params})
                         = 0x7521d1cadbcfa91eec65aa16715b94ffc1c9654ba57ea2ef1a2127bca1127a83
```

#### Steps

1. Compute `payloadHash` as described in [How to compute `payloadHash`](#how-to-compute-payloadhash).
2. EIP-712-sign the `ExchangeAction{payloadHash, nonce}` struct with the **API key's private key** (`0x2222…`) under domain `{name:"futures", chainId:286623, verifyingContract:0x00…00}`.
3. Prepend byte `0x01` to the 65-byte signature → this is the value of `X-API-Sign`.
4. Send the request with `X-API-Key` set to the **name** of the API key:

```bash
curl -X POST https://mainnet-gw.sodex.dev/api/v1/perps/trade/orders \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H "X-API-Key: api-key-01" \
  -H "X-API-Sign: 0x01<65-byte-signature-from-api-key-priv>" \
  -H "X-API-Nonce: 1760373925001" \
  -d '{"accountID":12345,"symbolID":1,"orders":[…]}'
```

The server looks up the API key named `api-key-01` on `accountID=12345`, recovers the signer address from `X-API-Sign`, and verifies it equals the API key's registered `publicKey` (`0x3d45…8256`).

#### Common pitfalls

* Putting the API key's EVM address (`0x3d45…8256`) in `X-API-Key` — **wrong**; use the `name` string.
* Signing the payload with the master wallet's private key — **wrong** for trading actions; the server verifies against the API key's registered public key and will reject the signature. (Exception: `addAPIKey` / `revokeAPIKey` — those *must* be signed by the master wallet, since they change the API key set itself.)
* Forgetting the `0x01` prefix — the server rejects un-typed raw 65-byte signatures.

## Get account ID

Account IDs are required to query account data and to sign trading actions. Retrieve them via either of:

### Via REST API

```bash
curl -X GET "${PERPS_ENDPOINT}/accounts/{userAddress}/state" \
  -H 'Accept: application/json'
```

The `aid` field in the response is the account ID. By default this is the primary account; pass `?accountID=<id>` to query a specific sub-account. The same endpoint is available under `${SPOT_ENDPOINT}` for spot.

See [WsPerpsState](/documentation/trading-api/rest-v1/schema.md#wsperpsstate) for the full response shape.

## Reference Pages

{% content-ref url="/pages/jkJqX14fY6MLgpgQvq3B" %}
[Go SDK Signing Guide](/documentation/trading-api/go-sdk-signing-guide.md)
{% endcontent-ref %}

{% content-ref url="/pages/HWeWqIzQUTNao2smQ2pp" %}
[API Rate Limits](/documentation/trading-api/api-rate-limits.md)
{% endcontent-ref %}

{% content-ref url="/pages/JhkOXmaIr0osLMjp2DfF" %}
[REST API v1](/documentation/trading-api/rest-v1.md)
{% endcontent-ref %}

{% content-ref url="/pages/U25Aeh8OoqsoN02ofoj4" %}
[WebSocket API v1](/documentation/trading-api/websocket-v1.md)
{% endcontent-ref %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sodex.com/documentation/trading-api/trading-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
