# Page 1

## Private API

### Introduction

The Outpoll Private API provides full access to trading, balances, positions, history, and real-time data. All endpoints require HMAC-SHA256 authentication.

**Base URL**

```
https://api.outpoll.com
```

All requests must be made over HTTPS.

***

### Authentication

Every request must include three headers:

| Header                  | Description                             |
| ----------------------- | --------------------------------------- |
| `OUTPOLL-API-KEY`       | Your API key (starts with `op_k_`)      |
| `OUTPOLL-API-SIGNATURE` | Base64URL-encoded HMAC-SHA256 signature |
| `OUTPOLL-API-TIMESTAMP` | Current Unix timestamp in seconds       |

#### Creating a Signature

The signature is computed over a message that concatenates the timestamp, HTTP method, request path (without query string), and request body:

```
message = timestamp + method + path + body
```

> Use only the path portion (e.g. `/orders/limit`), not the full URL or query string.

Sign with your API secret:

```
signature = Base64URL(HMAC-SHA256(Base64Decode(apiSecret), message))
```

**Example (Python)**

```python
import hmac, hashlib, base64, time, requests

api_key = "op_k_your_api_key"
api_secret = "your_api_secret"

timestamp = str(int(time.time()))
method = "POST"
path = "/orders/market"
body = '{"e":"54ccea1a-...","s":"BUY","am":50}'

message = timestamp + method + path + body
secret_bytes = base64.urlsafe_b64decode(api_secret)
signature = base64.urlsafe_b64encode(
    hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
).decode().rstrip("=")

response = requests.post(
    "https://api.outpoll.com" + path,
    headers={
        "OUTPOLL-API-KEY": api_key,
        "OUTPOLL-API-SIGNATURE": signature,
        "OUTPOLL-API-TIMESTAMP": timestamp,
        "Content-Type": "application/json",
    },
    data=body,
)
```

**Example (JavaScript)**

```javascript
const crypto = require("crypto");

const apiKey = "op_k_your_api_key";
const apiSecret = "your_api_secret";

const timestamp = Math.floor(Date.now() / 1000).toString();
const method = "POST";
const path = "/orders/market";
const body = JSON.stringify({ e: "54ccea1a-...", s: "BUY", am: 50 });

const message = timestamp + method + path + body;
const secretBytes = Buffer.from(apiSecret, "base64url");
const signature = crypto
  .createHmac("sha256", secretBytes)
  .update(message)
  .digest("base64url");

const response = await fetch("https://api.outpoll.com" + path, {
  method,
  headers: {
    "OUTPOLL-API-KEY": apiKey,
    "OUTPOLL-API-SIGNATURE": signature,
    "OUTPOLL-API-TIMESTAMP": timestamp,
    "Content-Type": "application/json",
  },
  body,
});
```

> Timestamps must be within **30 seconds** of the server time. Requests outside this window are rejected.

> Do **not** include an `Authorization: Bearer` header alongside API key headers. Requests with both are rejected with `400 Bad Request`.

#### API Key Access Scope

API keys grant access to the following endpoints:

| Endpoint                                        | Method   | Access   |
| ----------------------------------------------- | -------- | -------- |
| `/orders/limit`                                 | POST     | API key  |
| `/orders/market`                                | POST     | API key  |
| `/api/user-balances`                            | GET      | API key  |
| `/api/user-balances/full`                       | GET      | API key  |
| `/api/history/orders`                           | GET      | API key  |
| `/api/history/trades`                           | GET      | API key  |
| `/api/history/deals/open-positions`             | GET      | API key  |
| `/api/history/deals/active-orders`              | GET      | API key  |
| `/api/history/activity`                         | GET      | API key  |
| `/api/history/stats/profile/{userId}`           | GET      | API key  |
| `/api/history/stats/profile/{userId}/positions` | GET      | API key  |
| `/orders/tpsl`                                  | GET/POST | API key  |
| `/orders/tpsl/{tpslId}`                         | DELETE   | API key  |
| `/orders/limit/{orderId}`                       | DELETE   | API key  |
| `/api/deposit/usdc-address`                     | GET      | JWT only |
| `/api/transactions`                             | GET      | JWT only |

> Endpoints marked **JWT only** require a `Bearer` token obtained via `POST /auth/login`. API key authentication returns `403 Forbidden` for these endpoints.

#### Reusable Client (Python)

The examples below use this helper class:

```python
import hmac, hashlib, base64, time, json, requests

class OutpollClient:
    BASE_URL = "https://api.outpoll.com"

    def __init__(self, api_key: str, api_secret: str):
        self.api_key = api_key
        self.api_secret = api_secret

    def _sign(self, method: str, path: str, body: str = "") -> dict:
        timestamp = str(int(time.time()))
        message = timestamp + method + path + body
        secret_bytes = base64.urlsafe_b64decode(self.api_secret)
        signature = base64.urlsafe_b64encode(
            hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
        ).decode().rstrip("=")
        return {
            "OUTPOLL-API-KEY": self.api_key,
            "OUTPOLL-API-SIGNATURE": signature,
            "OUTPOLL-API-TIMESTAMP": timestamp,
            "Content-Type": "application/json",
        }

    def get(self, path: str, params: dict = None):
        headers = self._sign("GET", path)
        return requests.get(self.BASE_URL + path, headers=headers, params=params)

    def post(self, path: str, data: dict = None):
        body = json.dumps(data) if data else ""
        headers = self._sign("POST", path, body)
        return requests.post(self.BASE_URL + path, headers=headers, data=body)

    def delete(self, path: str):
        headers = self._sign("DELETE", path)
        return requests.delete(self.BASE_URL + path, headers=headers)


client = OutpollClient("op_k_your_api_key", "your_api_secret")
```

***

### API Key Management

API keys are managed via JWT-authenticated endpoints. Log in first via `POST /auth/login` to obtain a Bearer token.

#### Send Verification Code

```
POST /auth/api-key/send-code
```

Sends an email confirmation code required for API key creation.

**Headers**

| Header        | Value           |
| ------------- | --------------- |
| Authorization | Bearer \<token> |

**Response** `200 OK`

***

#### Create API Key

```
POST /auth/api-key
```

**Headers**

| Header        | Value           |
| ------------- | --------------- |
| Authorization | Bearer \<token> |

**Request Body**

| Field     | Type   | Required | Description                                  |
| --------- | ------ | -------- | -------------------------------------------- |
| code      | string | Yes      | Email confirmation code                      |
| otp       | string | No       | 2FA OTP code (if 2FA is enabled)             |
| expiresAt | string | No       | Expiration time (ISO 8601). Null = no expiry |

**Response** `200 OK`

```json
{
  "id": "uuid",
  "key": "op_k_1a2b3c4d5e6f...",
  "secret": "base64_encoded_secret",
  "createdAt": "2026-01-15T10:30:00Z",
  "expiresAt": null
}
```

| Field     | Type   | Description                    |
| --------- | ------ | ------------------------------ |
| id        | string | API key record ID              |
| key       | string | Public API key                 |
| secret    | string | Secret for HMAC signing        |
| createdAt | string | Creation timestamp (ISO 8601)  |
| expiresAt | string | Expiration timestamp or `null` |

> The `secret` is shown **only once** upon creation. Store it securely.

> Creating a new key automatically deactivates any previous key.

***

#### Get Active API Key

```
GET /auth/api-key
```

**Headers**

| Header        | Value           |
| ------------- | --------------- |
| Authorization | Bearer \<token> |

Returns the active API key metadata. The `secret` is **not** returned.

***

#### Revoke API Key

```
DELETE /auth/api-key
```

**Headers**

| Header        | Value           |
| ------------- | --------------- |
| Authorization | Bearer \<token> |

Revokes the active API key. A new key must be created to continue API access.

***

## Orders

### Place Limit Order

Place a limit order at a specific price. The order remains in the order book until filled or cancelled.

```
POST /orders/limit
```

**Request Body**

| Field | Type   | Required | Description                                                   |
| ----- | ------ | -------- | ------------------------------------------------------------- |
| e     | string | Yes      | Event ID                                                      |
| o     | string | Yes      | Outcome ID (market)                                           |
| ba    | string | Yes      | Betting asset ID (the runner/outcome token to trade)          |
| qa    | string | Yes      | Quote asset ID (USDC: `078dcd98-928d-479f-8110-ff6d27e44de2`) |
| s     | string | Yes      | Side — `BUY` or `SELL`                                        |
| p     | number | Yes      | Price (0.01–0.99)                                             |
| q     | number | Yes      | Quantity (number of shares)                                   |

**Example Request**

```json
{
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
  "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
  "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
  "s": "BUY",
  "p": 0.42,
  "q": 100
}
```

**Response** `200 OK` / `202 Accepted`

```json
{
  "i": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

| Field | Type   | Description |
| ----- | ------ | ----------- |
| i     | string | Order ID    |

**Python**

```python
order = client.post("/orders/limit", {
    "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
    "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
    "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
    "s": "BUY",
    "p": 0.42,
    "q": 100,
}).json()

print(f"Order ID: {order['i']}")
```

***

### Place Market Order

Execute an order immediately at the best available price.

```
POST /orders/market
```

**Request Body**

| Field | Type   | Required    | Description                                      |
| ----- | ------ | ----------- | ------------------------------------------------ |
| e     | string | Yes         | Event ID                                         |
| o     | string | Yes         | Outcome ID (market)                              |
| ba    | string | Yes         | Betting asset ID                                 |
| qa    | string | Yes         | Quote asset ID                                   |
| s     | string | Yes         | Side — `BUY` or `SELL`                           |
| am    | number | Conditional | Amount in USDC to spend (required for `BUY`)     |
| q     | number | Conditional | Quantity of shares to sell (required for `SELL`) |

**Example — BUY**

```json
{
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
  "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
  "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
  "s": "BUY",
  "am": 50
}
```

**Example — SELL**

```json
{
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
  "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
  "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
  "s": "SELL",
  "q": 100
}
```

**Response** `200 OK` / `202 Accepted`

```json
{
  "i": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

**Python — BUY**

```python
order = client.post("/orders/market", {
    "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
    "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
    "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
    "s": "BUY",
    "am": 50,
}).json()

print(f"Order ID: {order['i']}")
```

**Python — SELL**

```python
order = client.post("/orders/market", {
    "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
    "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
    "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
    "s": "SELL",
    "q": 100,
}).json()

print(f"Order ID: {order['i']}")
```

***

### Cancel Order

Cancel an active limit order.

```
DELETE /orders/limit/{orderId}
```

**Path Parameters**

| Parameter | Type   | Description               |
| --------- | ------ | ------------------------- |
| orderId   | string | ID of the order to cancel |

**Response** `200 OK` / `204 No Content`

**Python**

```python
order_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
response = client.delete(f"/orders/limit/{order_id}")
print(f"Cancelled: {response.status_code}")
```

***

### Set Take Profit / Stop Loss

Attach TP/SL conditions to an open position. When the price hits your target, the position is automatically closed.

```
POST /orders/tpsl
```

**Request Body**

| Field | Type   | Required | Description                   |
| ----- | ------ | -------- | ----------------------------- |
| e     | string | Yes      | Event ID                      |
| o     | string | Yes      | Outcome ID (market)           |
| ba    | string | Yes      | Betting asset ID              |
| qa    | string | Yes      | Quote asset ID                |
| i     | string | Yes      | Intent — `YES` or `NO`        |
| s     | string | Yes      | Side — `SELL`                 |
| q     | number | Yes      | Quantity of shares to close   |
| t     | number | No       | Take-profit price (0.01–0.99) |
| l     | number | No       | Stop-loss price (0.01–0.99)   |

> At least one of `t` (take profit) or `l` (stop loss) must be provided.

**Example Request**

```json
{
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
  "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
  "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
  "i": "YES",
  "s": "SELL",
  "q": 100,
  "t": 0.95,
  "l": 0.10
}
```

**Response** `200 OK` / `202 Accepted`

**Python**

```python
response = client.post("/orders/tpsl", {
    "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
    "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "ba": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
    "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
    "i": "YES",
    "s": "SELL",
    "q": 100,
    "t": 0.95,
    "l": 0.10,
})
print(f"TP/SL set: {response.status_code}")
```

***

### Get TP/SL Orders

Retrieve active take-profit / stop-loss orders for a given asset.

```
GET /orders/tpsl
```

**Query Parameters**

| Parameter | Type    | Required | Description                                         |
| --------- | ------- | -------- | --------------------------------------------------- |
| assetId   | string  | Yes      | Asset / runner ID                                   |
| primary   | boolean | Yes      | Filter by primary (`true`) or non-primary (`false`) |
| status    | string  | No       | Filter by status (e.g. `ACTIVE`)                    |

**Response** `200 OK`

```json
[
  {
    "i": "tpsl-order-id",
    "q": 100,
    "tpp": 0.95,
    "slp": 0.10
  }
]
```

| Field | Type   | Description       |
| ----- | ------ | ----------------- |
| i     | string | Order ID          |
| q     | number | Quantity          |
| tpp   | number | Take-profit price |
| slp   | number | Stop-loss price   |

**Python**

```python
asset_id = "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813"
orders = client.get("/orders/tpsl", params={"assetId": asset_id, "primary": True, "status": "ACTIVE"}).json()

for order in orders:
    print(f"TP: {order['tpp']}, SL: {order['slp']}, qty: {order['q']}")
```

***

### Cancel TP/SL Order

Cancel an active take-profit / stop-loss order.

```
DELETE /orders/tpsl/{tpslId}
```

**Path Parameters**

| Parameter | Type   | Description           |
| --------- | ------ | --------------------- |
| tpslId    | string | ID of the TP/SL order |

**Response** `200 OK` / `204 No Content`

**Python**

```python
tpsl_id = "tpsl-order-id"
response = client.delete(f"/orders/tpsl/{tpsl_id}")
print(f"Cancelled: {response.status_code}")
```

***

## Positions

### Get Open Positions

Retrieve all open positions for the authenticated user.

```
GET /api/history/deals/open-positions
```

**Query Parameters**

| Parameter | Type    | Required | Default | Description    |
| --------- | ------- | -------- | ------- | -------------- |
| page      | integer | No       | 0       | Page number    |
| size      | integer | No       | 100     | Items per page |

**Response** `200 OK`

```json
{
  "i": [
    {
      "oi": "order-id",
      "ei": "event-id",
      "eoi": "outcome-id",
      "ai": "asset-id",
      "et": "Will BTC reach $100k?",
      "eic": "https://example.com/icon.png",
      "es": "btc-100k-2026",
      "eim": "https://example.com/image.png",
      "on": "Yes",
      "tn": "YES-BTC-100K",
      "ip": true,
      "tsc": 100,
      "asc": 100,
      "sp": 0.50,
      "c": 50.00,
      "cp": 0.55,
      "v": 55.00,
      "tpsl": [],
      "pv": 5.00,
      "pp": 10.0,
      "od": "2026-01-15T10:30:00Z"
    }
  ],
  "p": 0,
  "s": 100,
  "tp": 1
}
```

| Field | Type    | Description            |
| ----- | ------- | ---------------------- |
| oi    | string  | Order ID               |
| ei    | string  | Event ID               |
| eoi   | string  | Event outcome ID       |
| ai    | string  | Asset ID               |
| et    | string  | Event title            |
| eic   | string  | Event icon URL         |
| es    | string  | Event slug             |
| eim   | string  | Event image URL        |
| on    | string  | Outcome name           |
| tn    | string  | Token name             |
| ip    | boolean | Is primary outcome     |
| tsc   | number  | Total shares count     |
| asc   | number  | Available shares count |
| sp    | number  | Share price (entry)    |
| c     | number  | Cost                   |
| cp    | number  | Current price          |
| v     | number  | Current value          |
| tpsl  | array   | Attached TP/SL orders  |
| pv    | number  | PnL value              |
| pp    | number  | PnL percent            |
| od    | string  | Open date (ISO 8601)   |

**Python**

```python
positions = client.get("/api/history/deals/open-positions", params={"page": 0, "size": 100}).json()

for pos in positions["i"]:
    print(f"{pos['et']} — {pos['on']}: {pos['asc']} shares, PnL: {pos['pv']:+.2f} ({pos['pp']:+.1f}%)")
```

***

### Get Active Orders

Retrieve all active (unfilled) orders.

```
GET /api/history/deals/active-orders
```

**Query Parameters**

| Parameter | Type    | Required | Default | Description    |
| --------- | ------- | -------- | ------- | -------------- |
| page      | integer | No       | 0       | Page number    |
| size      | integer | No       | 100     | Items per page |

**Response** `200 OK`

```json
{
  "i": [
    {
      "oi": "order-id",
      "ei": "event-id",
      "eoi": "outcome-id",
      "ai": "asset-id",
      "et": "Will BTC reach $100k?",
      "eic": "https://example.com/icon.png",
      "es": "btc-100k-2026",
      "on": "Yes",
      "tn": "YES-BTC-100K",
      "ip": true,
      "sc": 100,
      "sp": 0.42,
      "si": "BUY",
      "fq": 0,
      "oq": 100,
      "cp": 0.55,
      "od": "2026-01-15T10:30:00Z",
      "st": "ACTIVE"
    }
  ],
  "p": 0,
  "s": 100,
  "tp": 1
}
```

| Field | Type    | Description             |
| ----- | ------- | ----------------------- |
| oi    | string  | Order ID                |
| ei    | string  | Event ID                |
| eoi   | string  | Event outcome ID        |
| ai    | string  | Asset ID                |
| et    | string  | Event title             |
| eic   | string  | Event icon URL          |
| es    | string  | Event slug              |
| on    | string  | Outcome name            |
| tn    | string  | Token name              |
| ip    | boolean | Is primary outcome      |
| sc    | number  | Shares count            |
| sp    | number  | Share price             |
| si    | string  | Side — `BUY` or `SELL`  |
| fq    | number  | Filled quantity         |
| oq    | number  | Original order quantity |
| cp    | number  | Current price           |
| od    | string  | Open date (ISO 8601)    |
| st    | string  | Status                  |

**Python**

```python
orders = client.get("/api/history/deals/active-orders", params={"page": 0, "size": 100}).json()

for order in orders["i"]:
    print(f"{order['si']} {order['oq']} @ {order['sp']} — {order['st']} (filled: {order['fq']})")
```

***

## Balances

### Get User Balances

Retrieve balances for all coins/tokens held by the authenticated user.

```
GET /api/user-balances
```

**Query Parameters**

| Parameter     | Type    | Required | Default | Description                         |
| ------------- | ------- | -------- | ------- | ----------------------------------- |
| eventTokenIds | string  | No       | —       | Comma-separated token IDs to filter |
| page          | integer | No       | 0       | Page number                         |
| size          | integer | No       | 100     | Items per page                      |

**Response** `200 OK`

```json
{
  "i": [
    {
      "i": "balance-id",
      "u": "user-id",
      "et": "event-token-id",
      "aop": 0.50,
      "c": 50.00,
      "v": 55.00,
      "tv": 55.00
    }
  ],
  "p": 0,
  "s": 100,
  "tp": 1
}
```

| Field | Type   | Description        |
| ----- | ------ | ------------------ |
| i     | string | Balance ID         |
| u     | string | User ID            |
| et    | string | Event token ID     |
| aop   | number | Average open price |
| c     | number | Cost               |
| v     | number | Value              |
| tv    | number | Total value        |

**Python**

```python
balances = client.get("/api/user-balances", params={"page": 0, "size": 100}).json()

for b in balances["i"]:
    print(f"Token: {b['et']}, value: {b['v']}, cost: {b['c']}")
```

***

### Get Full Balances

Retrieve balances with event/outcome metadata.

```
GET /api/user-balances/full
```

**Response** `200 OK`

Same paginated wrapper as `/api/user-balances`. Each item includes the base fields plus:

| Field | Type    | Description        |
| ----- | ------- | ------------------ |
| eq    | string  | Event question     |
| ei    | string  | Event icon URL     |
| es    | string  | Event slug         |
| on    | string  | Outcome name       |
| tn    | string  | Token name         |
| ip    | boolean | Is primary outcome |

**Python**

```python
balances = client.get("/api/user-balances/full").json()

for b in balances["i"]:
    print(f"{b['eq']} — {b['on']}: value={b['v']}, cost={b['c']}")
```

***

## Deposit

### Get Deposit Address

Retrieve the USDC deposit address for the authenticated user.

```
GET /api/deposit/usdc-address
```

**Response** `200 OK`

```json
{
  "i": "078dcd98-928d-479f-8110-ff6d27e44de2",
  "a": "0x1234567890abcdef1234567890abcdef12345678"
}
```

| Field | Type   | Description                     |
| ----- | ------ | ------------------------------- |
| i     | string | Asset ID                        |
| a     | string | Blockchain address for deposits |

**Response** `404 Not Found` — Address not yet generated.

**Python**

```python
# This endpoint requires JWT Bearer authentication (see API Key Access Scope)
import requests

response = requests.get(
    "https://api.outpoll.com/api/deposit/usdc-address",
    headers={"Authorization": f"Bearer {jwt_token}"},
)

if response.status_code == 200:
    data = response.json()
    print(f"Deposit to: {data['a']} (asset: {data['i']})")
else:
    print("Address not yet generated")
```

***

## History

### Order History

Retrieve historical orders for the authenticated user.

```
GET /api/history/orders
```

**Query Parameters**

| Parameter      | Type    | Required | Default | Description                     |
| -------------- | ------- | -------- | ------- | ------------------------------- |
| page           | integer | No       | 0       | Page number                     |
| size           | integer | No       | 20      | Items per page                  |
| eventOutcomeId | string  | No       | —       | Filter by outcome/market ID     |
| sort           | string  | No       | desc    | Sort direction (`asc` / `desc`) |

**Response** `200 OK`

```json
{
  "i": [
    {
      "i": "order-id",
      "u": "user-id",
      "e": "event-id",
      "o": "outcome-id",
      "a": "asset-id",
      "oq": 100,
      "si": "BUY",
      "t": "LIMIT",
      "p": 0.42,
      "m": 1710000000000,
      "ex": null,
      "st": "FILLED",
      "q": 100
    }
  ],
  "p": 0,
  "s": 20,
  "tp": 8
}
```

| Field | Type   | Description                              |
| ----- | ------ | ---------------------------------------- |
| i     | string | Order ID                                 |
| u     | string | User ID                                  |
| e     | string | Event ID                                 |
| o     | string | Event outcome ID                         |
| a     | string | Asset ID                                 |
| oq    | number | Original quantity                        |
| si    | string | Side — `BUY` or `SELL`                   |
| t     | string | Type — `LIMIT` or `MARKET`               |
| p     | number | Price                                    |
| m     | number | Placement time (Unix ms)                 |
| ex    | string | Expire at (ISO 8601) or `null`           |
| st    | string | Status — `ACTIVE`, `FILLED`, `CANCELLED` |
| q     | number | Quantity (filled)                        |

**Python**

```python
history = client.get("/api/history/orders", params={"page": 0, "size": 20, "sort": "desc"}).json()

for order in history["i"]:
    print(f"{order['si']} {order['oq']} @ {order['p']} ({order['t']}) — {order['st']}")
```

***

### Trade History

Retrieve executed trades (filled orders).

```
GET /api/history/trades
```

**Query Parameters**

| Parameter      | Type    | Required | Default | Description                 |
| -------------- | ------- | -------- | ------- | --------------------------- |
| page           | integer | No       | 0       | Page number                 |
| size           | integer | No       | 20      | Items per page              |
| eventOutcomeId | string  | No       | —       | Filter by outcome/market ID |

**Response** `200 OK`

```json
{
  "i": [
    {
      "i": "trade-record-id",
      "t": "trade-id",
      "u": "user-id",
      "or": "order-id",
      "s": "BUY",
      "e": "event-id",
      "o": "outcome-id",
      "a": "asset-id",
      "q": 100,
      "p": 0.50,
      "f": 0.25,
      "m": "2026-01-15T10:30:00Z"
    }
  ],
  "p": 0,
  "s": 20,
  "tp": 8
}
```

| Field | Type   | Description               |
| ----- | ------ | ------------------------- |
| i     | string | Trade record ID           |
| t     | string | Trade ID                  |
| u     | string | User ID                   |
| or    | string | Order ID                  |
| s     | string | Side — `BUY` or `SELL`    |
| e     | string | Event ID                  |
| o     | string | Event outcome ID          |
| a     | string | Asset ID                  |
| q     | number | Quantity                  |
| p     | number | Price                     |
| f     | number | Fee                       |
| m     | string | Execution time (ISO 8601) |

**Python**

```python
trades = client.get("/api/history/trades", params={"page": 0, "size": 20}).json()

for trade in trades["i"]:
    print(f"{trade['s']} {trade['q']} @ {trade['p']} (fee: {trade['f']})")
```

***

### Transaction History

Retrieve a paginated list of all transactions (deposits, trades, etc.).

```
GET /api/transactions
```

**Query Parameters**

| Parameter | Type    | Required | Default | Description    |
| --------- | ------- | -------- | ------- | -------------- |
| page      | integer | No       | 0       | Page number    |
| size      | integer | No       | 20      | Items per page |

**Response** `200 OK`

```json
{
  "i": [
    {
      "i": "tx-id",
      "u": "user-id",
      "a": "asset-id",
      "eq": "Will BTC reach $100k?",
      "ei": "https://example.com/icon.png",
      "es": "btc-100k-2026",
      "on": "Yes",
      "tn": "YES-BTC-100K",
      "ip": true,
      "t": "BUY",
      "am": 50.00,
      "s": "COMPLETED",
      "cat": "2026-01-15T10:30:00Z",
      "e": null,
      "f": 0.25,
      "ta": null,
      "bu": null,
      "bn": null
    }
  ],
  "p": 0,
  "s": 20,
  "tp": 5
}
```

| Field | Type    | Description                |
| ----- | ------- | -------------------------- |
| i     | string  | Transaction ID             |
| u     | string  | User ID                    |
| a     | string  | Asset ID                   |
| eq    | string  | Event question             |
| ei    | string  | Event icon URL             |
| es    | string  | Event slug                 |
| on    | string  | Outcome name               |
| tn    | string  | Token name                 |
| ip    | boolean | Is primary outcome         |
| t     | string  | Transaction type           |
| am    | number  | Amount                     |
| s     | string  | Status                     |
| cat   | string  | Created at (ISO 8601)      |
| e     | string  | External transaction ID    |
| f     | number  | Fees                       |
| ta    | string  | Withdrawal wallet address  |
| bu    | string  | Blockchain transaction URL |
| bn    | string  | Blockchain name            |

**Python**

```python
# This endpoint requires JWT Bearer authentication (see API Key Access Scope)
import requests

txs = requests.get(
    "https://api.outpoll.com/api/transactions",
    headers={"Authorization": f"Bearer {jwt_token}"},
    params={"page": 0, "size": 20},
).json()

for tx in txs["i"]:
    print(f"{tx['t']}: {tx['am']} — {tx['s']}")
```

***

### Activity Feed

Retrieve the user's recent activity (trades, position changes).

```
GET /api/history/activity
```

**Query Parameters**

| Parameter | Type    | Required | Default | Description    |
| --------- | ------- | -------- | ------- | -------------- |
| page      | integer | No       | 0       | Page number    |
| size      | integer | No       | 20      | Items per page |

**Response** `200 OK`

```json
{
  "i": [
    {
      "u": "user-id",
      "tra": "trade-record-id",
      "n": "trader_name",
      "oi": "outcome-id",
      "on": "Yes",
      "p": "Yes",
      "si": "BUY",
      "q": 14.90,
      "x": "2026-01-15T10:30:00.000000",
      "eq": "Will BTC reach $100k?",
      "ei": "https://example.com/icon.png",
      "es": "btc-100k-2026",
      "tn": "Yes",
      "ip": true,
      "pr": 0.47,
      "ev": 0,
      "uv": null
    }
  ],
  "p": 0,
  "s": 20,
  "t": 1500
}
```

| Field | Type    | Description                   |
| ----- | ------- | ----------------------------- |
| u     | string  | User ID                       |
| tra   | string  | Trade record ID               |
| n     | string  | Username                      |
| oi    | string  | Outcome ID                    |
| on    | string  | Outcome name                  |
| p     | string  | Position direction (Yes / No) |
| si    | string  | Side — `BUY` or `SELL`        |
| q     | number  | Quantity                      |
| x     | string  | Execution time (ISO 8601)     |
| eq    | string  | Event question                |
| ei    | string  | Event icon URL                |
| es    | string  | Event slug                    |
| tn    | string  | Token name                    |
| ip    | boolean | Is primary outcome            |
| pr    | number  | Price                         |
| ev    | number  | Event volume                  |
| uv    | number  | User volume (nullable)        |

> This endpoint uses `t` (total items) in its pagination wrapper instead of the standard `tp` (total pages).

**Python**

```python
activity = client.get("/api/history/activity", params={"page": 0, "size": 20}).json()

for a in activity["i"]:
    print(f"{a['si']} {a['q']} {a['on']} @ {a['pr']} — {a['eq']}")
```

***

### Profile Stats

Retrieve trading statistics for a user profile.

```
GET /api/history/stats/profile/{userId}
```

**Path Parameters**

| Parameter | Type   | Description |
| --------- | ------ | ----------- |
| userId    | string | User ID     |

**Response** `200 OK`

```json
{
  "profile": {
    "userId": "user-id",
    "username": "trader_name",
    "joinedAt": "2025-06-01T00:00:00Z",
    "me": true
  },
  "cards": {
    "positionValue": 5000.00,
    "volumeTraded": 125000.00,
    "marketsTraded": 42
  }
}
```

| Field               | Type    | Description                            |
| ------------------- | ------- | -------------------------------------- |
| profile.userId      | string  | User ID                                |
| profile.username    | string  | Username                               |
| profile.joinedAt    | string  | Join date (ISO 8601)                   |
| profile.me          | boolean | Whether this is the authenticated user |
| cards.positionValue | number  | Total position value                   |
| cards.volumeTraded  | number  | Total volume traded                    |
| cards.marketsTraded | number  | Number of markets traded               |

**Python**

```python
user_id = "your-user-id"
stats = client.get(f"/api/history/stats/profile/{user_id}").json()

profile = stats["profile"]
cards = stats["cards"]
print(f"{profile['username']} — positions: ${cards['positionValue']:.2f}, volume: ${cards['volumeTraded']:.2f}")
```

***

### Position Stats

Retrieve position-level statistics for a user profile, including open positions and position history.

```
GET /api/history/stats/profile/{userId}/positions
```

**Path Parameters**

| Parameter | Type   | Description |
| --------- | ------ | ----------- |
| userId    | string | User ID     |

**Response** `200 OK`

```json
{
  "tab": null,
  "openPositions": {
    "i": [
      {
        "ei": "event-id",
        "eoi": "outcome-id",
        "ai": "asset-id",
        "et": "Will BTC reach $100k?",
        "eic": "/icons/btc.svg",
        "es": "btc-100k-2026",
        "on": "Yes",
        "tn": "Yes",
        "sc": 100.00,
        "sp": 50.00,
        "si": "BUY",
        "c": 50.00,
        "v": 55.00,
        "pv": 5.00,
        "pp": 10.0
      }
    ],
    "p": 0,
    "s": 20,
    "tp": 1,
    "ti": 5
  },
  "positionHistory": {
    "i": [
      {
        "ei": "event-id",
        "eoi": "outcome-id",
        "ai": "asset-id",
        "et": "Will BTC reach $100k?",
        "eic": "/icons/btc.svg",
        "es": "btc-100k-2026",
        "on": "Yes",
        "tn": "Yes",
        "sc": 100.00,
        "sp": 50.00,
        "si": "BUY",
        "c": 50.00,
        "v": 100.00,
        "pv": 50.00,
        "pp": 100.0,
        "od": "2026-01-15T10:30:00Z",
        "cd": "2026-02-01T12:00:00Z"
      }
    ],
    "p": 0,
    "s": 10,
    "tp": 1,
    "ti": 10
  }
}
```

| Field | Type   | Description           |
| ----- | ------ | --------------------- |
| tab   | string | Active tab (nullable) |

**Open positions / Position history fields**

| Field | Type   | Description                                     |
| ----- | ------ | ----------------------------------------------- |
| ei    | string | Event ID                                        |
| eoi   | string | Event outcome ID                                |
| ai    | string | Asset ID                                        |
| et    | string | Event title                                     |
| eic   | string | Event icon URL                                  |
| es    | string | Event slug                                      |
| on    | string | Outcome name                                    |
| tn    | string | Token name                                      |
| sc    | number | Shares count                                    |
| sp    | number | Share price (entry)                             |
| si    | string | Side — `BUY` or `SELL`                          |
| c     | number | Cost                                            |
| v     | number | Current value                                   |
| pv    | number | PnL value                                       |
| pp    | number | PnL percent                                     |
| od    | string | Open date (ISO 8601) — *position history only*  |
| cd    | string | Close date (ISO 8601) — *position history only* |

> Both `openPositions` and `positionHistory` use the standard paginated wrapper with `ti` (total items).

**Python**

```python
user_id = "your-user-id"
pos_stats = client.get(f"/api/history/stats/profile/{user_id}/positions").json()

print("Open positions:")
for pos in pos_stats["openPositions"]["i"]:
    print(f"  {pos['et']} — {pos['on']}: {pos['sc']} shares, PnL: {pos['pv']:+.2f} ({pos['pp']:+.1f}%)")

print("Position history:")
for pos in pos_stats["positionHistory"]["i"]:
    print(f"  {pos['et']} — {pos['on']}: closed {pos['cd']}, PnL: {pos['pv']:+.2f}")
```

***

## Real-time Data (WebSocket)

> For detailed WebSocket documentation including all message types, authentication flows, and Python examples, see the **WebSocket API** document.

***

## Errors

| Code | Description                                            |
| ---- | ------------------------------------------------------ |
| 200  | OK — Request succeeded                                 |
| 201  | Created — Resource created successfully                |
| 202  | Accepted — Request accepted for async processing       |
| 204  | No Content — Success with no response body             |
| 400  | Bad Request — Invalid parameters                       |
| 401  | Unauthorized — Missing or invalid API key / signature  |
| 403  | Forbidden — Insufficient API key permissions           |
| 404  | Not Found — Resource does not exist                    |
| 422  | Unprocessable Entity — Business logic validation error |
| 429  | Too Many Requests — Rate limit exceeded                |
| 500  | Internal Server Error                                  |

**Error Response (General)**

```json
{
  "s": 401,
  "e": "Unauthorized",
  "m": "Invalid signature",
  "p": "/orders/limit",
  "t": "2026-01-15T10:30:00.000000Z"
}
```

| Field | Type    | Description      |
| ----- | ------- | ---------------- |
| s     | integer | HTTP status code |
| e     | string  | Error name       |
| m     | string  | Error message    |
| p     | string  | Request path     |
| t     | string  | Timestamp        |

**Error Response (Order Service)**

Order endpoints return a different error format with an error code:

```json
{
  "e": "ORD-1401",
  "m": "The limit order cannot be created or executed because the event is inactive",
  "s": 422,
  "t": "2026-01-15T10:30:00.000000000"
}
```

| Field | Type    | Description                  |
| ----- | ------- | ---------------------------- |
| e     | string  | Error code (e.g. `ORD-1401`) |
| m     | string  | Error message                |
| s     | integer | HTTP status code             |
| t     | string  | Timestamp                    |
| d     | string  | Error details (optional)     |

**Rate Limit Error**

```json
{
  "s": 429,
  "e": "Too Many Requests",
  "m": "Blocked due to excessive requests. Retry in 5 min",
  "p": "/orders/limit",
  "t": "2026-01-15T10:30:00.000000Z"
}
```

***

## Rate Limits

API requests are rate-limited per API key using a 3-tier sliding window.

| Window | Limit           |
| ------ | --------------- |
| Minute | 20 requests     |
| Hour   | 600 requests    |
| Day    | 10,000 requests |

**Violation escalation:**

| Violation                    | Action                      |
| ---------------------------- | --------------------------- |
| 1st rate limit exceeded      | Temporary block (5 minutes) |
| 2nd violation within 2 hours | Permanent API key block     |

When rate-limited, the response includes a `Retry-After` header with the number of seconds to wait.

***

## Pagination

Paginated endpoints accept `page` (0-indexed) and `size` query parameters.

**Standard format**

<pre class="language-json"><code class="lang-json">{
  "i": [...],
  "p": 0,
  "s": 20,
<strong>  "tp": 25
</strong>}
</code></pre>

| Field | Type    | Description                  |
| ----- | ------- | ---------------------------- |
| i     | array   | Items                        |
| p     | integer | Current page                 |
| s     | integer | Page size                    |
| tp    | integer | Total pages                  |
| ti    | integer | Total items (some endpoints) |


---

# 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://docs.outpoll.com/api/rest-api/private-endpoints/page-1.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.
