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

# Python SDK

> Install and get started with the official Upside Python SDK — EIP-712 wallet-signed REST reads and writes plus realtime WebSocket streams, with no API keys.

The **Upside Python SDK** is the official client for the Upside perpetuals
exchange. It wraps the REST read API (`POST /info`), the signed write API
(`POST /exchange`), and the realtime WebSocket streams behind two typed client
classes. Authentication is **EIP-712 wallet signing** (secp256k1) — there are no
API keys to manage.

<Card title="upsidemax/upside-python-sdk" icon="github" href="https://github.com/upsidemax/upside-python-sdk">
  Source, examples, and issue tracker on GitHub. MIT licensed.
</Card>

* **EIP-712 request signing** across the Agent and Typed paths — handled for you.
* **Synchronous REST** with a **threaded WebSocket** client that auto-reconnects.
* Raw-dict responses, typed inputs, and full type hints (ships `py.typed`).
* Agent (API-wallet) delegation, TP/SL, leverage / margin, and collateral actions.

## Installation

```bash theme={null}
pip install upside-python-sdk
```

Requires **Python 3.9+**. Runtime dependencies: `requests`, `websocket-client`,
`eth-account`, `eth-utils`.

## Quick start

```python theme={null}
from upside import Info, Exchange
from upside.utils import constants

# --- reads (no signing) ---
info = Info(base_url=constants.QA_API_URL)
cfg = info.configs()
contract = next(c for c in cfg["contracts"] if c["status"] == "Active")
asset = contract["contractId"]
print(info.market_state(asset))

# --- writes (EIP-712 signed) ---
exchange = Exchange("0x<private-key>", base_url=constants.QA_API_URL)

# Register (QA requires an alpha test invitation code from the Upside team).
# A 10,000 USDC test airdrop lands within ~10s.
exchange.register_account(invite_code="<your-code>")

# Place a resting limit buy. Prices and sizes are raw integer strings —
# scale them with the contract's priceScale / qtyScale from configs.
exchange.order(asset=asset, is_buy=True, size="10", price="50")
```

## The two clients

<CardGroup cols={2}>
  <Card title="Info — reads & streams" href="/sdk/python/reading-data">
    Market data, account and order queries, and realtime WebSocket
    subscriptions. No wallet or signing required.
  </Card>

  <Card title="Exchange — signed writes" href="/sdk/python/trading">
    Orders, cancels, margin and leverage, TP/SL, collateral, and agent
    delegation — every call signed with your wallet key.
  </Card>
</CardGroup>

## Key behaviors

<Note>
  **Prices and sizes are raw integer strings.** The wire protocol is
  integer-only: convert a display price with the contract's `priceScale` and a
  display size with its `qtyScale`, both read from
  [`configs`](/info/configs) — never hardcode a scale. For example, on a contract
  with `priceScale = 2`, a display price of `500.00` is sent as `"50000"`.
</Note>

* **Order placement is asynchronous.** A batch returns
  `{"status": "accepted", "response": {"type": "order", "data": {"count": n}}}` —
  **not** the resting order id. Read the result from
  [`Info.user_orders`](/sdk/python/reading-data) / `orders_by_cloids`, or the
  `orderUpdates` / `userFills` WebSocket channels. Cancels, modifies, and margin
  actions respond synchronously.
* **HTTP 200 does not always mean success.** Gateway-level failures (invalid
  signature, reused nonce, rate limiting) raise `ClientError` (4xx) or
  `ServerError` (5xx). Business rejections come back as HTTP 200 — a per-item
  `error` string inside `statuses[]`, or a non-zero `errorCode` inside
  `response.data`. Always inspect the response.

## Error handling

Every SDK error derives from `UpsideError`:

| Exception        | Raised when                                                                     |
| ---------------- | ------------------------------------------------------------------------------- |
| `ClientError`    | The gateway returns a 4xx (bad request, invalid signature, invite/nonce issue). |
| `ServerError`    | The gateway returns a 5xx (including `DOWNSTREAM_TIMEOUT`).                     |
| `WebsocketError` | A streaming/connection error on the WebSocket client.                           |
| `APIError`       | Base class for the HTTP errors above.                                           |

```python theme={null}
from upside import ClientError, ServerError

try:
    exchange.order(asset=1, is_buy=True, size="10", price="50")
except ClientError as e:
    print("rejected before processing:", e)
except ServerError as e:
    print("server error, safe to re-check state:", e)
```

## Environments

The SDK defaults to the **QA testnet**, `constants.QA_API_URL`
(`https://dev.upsidemax.xyz`). Pass `base_url` explicitly to target a different
environment. For UAT or production access, contact the Upside team.

Contract IDs, coin IDs, scales, and tick / step sizes are **server-assigned and
differ per environment** — always read them from `Info.configs()` rather than
hardcoding.

## Signing

Every `/exchange` write is authorized by an EIP-712 signature over the fixed
domain (`Exchange` / version `1` / chain id `9767` / zero verifying contract).
The SDK selects the correct path automatically — the **Typed path** for
`registerAccount`, `approveAgent`, `revokeAgent`, and the collateral actions, and
the **Agent path** for everything else. Nonces are strictly increasing
millisecond timestamps managed per `Exchange` instance. See
[Authentication](/guide/authentication) for the full scheme.
