# What is Outpoll?

***

Outpoll is a CeDeFi (Centralized-Decentralized Finance) prediction market platform that leverages proprietary blockchain and backend technology. It allows users to trade on real-world events such as politics, sports, finance, and entertainment by buying and selling event-linked tokens that mirror market sentiment.

#### **Key Features of Outpoll:**

1. **Hybrid CeDeFi Model** – Combines the efficiency of centralized systems with the transparency of decentralized finance.&#x20;
2. **Proprietary Blockchain Tech** – Built on a custom internal blockchain for fast, scalable, and secure transactions. In the future, the project will be migrated to another popular blockchain, or we will make our own blockchain public after a series of tests.
3. **Prediction Markets** – Users trade "Yes" or "No" tokens on event outcomes (e.g., "Will Tesla stock hit $300 by 2025?").
4. **Stablecoin Integration** – Supports USDC for seamless, low-volatility trading.
5. **Smart Contract Backend** – Ensures fair, tamper-proof resolution of events while maintaining user control.
6. **Dynamic Odds** – Real-time pricing reflects crowd wisdom, turning sentiment into tradable assets.

#### **How It Works:**

* Deposit stablecoins (e.g., USDC) or any other crypto into your Outpoll account.
* Buy tokens (shares) in prediction markets (e.g., "Will Argentina win the next FIFA World Cup?").
* If correct, redeem shares at full value; if wrong, they expire worthless.

Outpoll merges the best of CeFi and DeFi, offering a next-gen prediction market experience.

***


# Outpoll token

The token has not been released yet. Currently, it can be credited to a user’s platform account, but **it cannot be traded or exchanged**.

Additional information about the token will be announced in due course.


# Deposits


# Withdrawals


# FAQ


# Prediction Market Trading Explained


# Order and Positions


# Order types (Limit & Market)


# Take profit & Stop loss


# Positions (Manage executed orders)


# Interface explained


# FAQ


# Convert your crypto assets


# Fees and conditions


# FAQ


# Account Security


# Stay Away from Crypto Hacks and Scams


# Recognize & Avoid Phishing Attacks


# SMS & Text Message Scams or How to Spot a Spam


# Keep Your Account Secured


# Don't share your password and API Secret


# Stay Alert to Fake Outpoll Scams


# Customer Support


# FAQ


# Overview


# FAQ


# Introduction

Overview of the Outpoll APIs

Use the API section to integrate Outpoll with external services.

### What you will find here

* Authentication and integration rules
* REST API for request-response workflows
* Websockets for real-time updates

### Recommended reading order

1. Start with FAQ.
2. Use Public Endpoints for open market data.
3. Use Private Endpoints for account-level actions.
4. Use Websockets when you need live events.


# FAQ

Authentication, API keys, rate limits, and core integration rules

### How to create your API key?

Log in to your account, go to **Settings**, and click the [**Modify**](https://outpoll.com/en/account/api-key) button. From there, you can create an API key that supports all frontend actions except for the crypto conversion feature and any operations requiring code confirmation (such as withdrawals or enabling/disabling two-factor authentication).\
\
Copy your API KEY and SECRET. Please note that the SECRET is displayed only once, at the time of key creation. If you lose it, you will need to regenerate the API KEY, which will also generate a new SECRET.

The generated API key can be valid for up to 1 year, and you can select the expiration option that best suits your needs during creation.

### How do I connect to the Outpoll API?

The API key is intended solely for accessing data within your personal account. **All public endpoints are available without requiring any additional keys.**

To work with private endpoints via the API, you will need at least the following 2 variables:

1. API KEY + SECRET ([generate your ones](https://outpoll.com/en/account/api-key))
2. SIGNATURE ([see examples of generation](/api/rest-api/private-endpoints#signature-example))

### Do I have any rate limits?

<table><thead><tr><th width="170.11328125">Requests per 1 sec</th><th width="172.1640625">Requests per 1 min</th><th width="174.63671875">Requests per 1 hour</th><th width="170.69140625">Requests per 1 day</th></tr></thead><tbody><tr><td>3</td><td>50</td><td>2000</td><td>10000</td></tr></tbody></table>

These limits apply to both public and private endpoints that require an API key. If you exceed the limits, you may be temporarily blocked. In the case of repeated violations, your API key may be permanently blocked.

### Why do I need an API?

You can use the API to retrieve specific data from the Outpoll platform. By using API keys, you can automate trading, create your own trading bots, and build custom analytics tools.

### Can I see some examples on Python?

To simplify integration and interaction with our API, we provide ready-to-use examples written in Python. We recommend reviewing [several examples](/api/rest-api/python-examples) before you begin.


# REST API

Use REST API for request-response integrations.

### Sections

#### Public Endpoints

* [Public Endpoints](/api/rest-api/public-endpoints)
* [Public API Overview](/api/rest-api/public-endpoints/public-api-overview)
* [Search Events](/api/rest-api/public-endpoints/search-events)
* [Get Available Coins](/api/rest-api/public-endpoints/get-available-coins)
* [Get Categories](/api/rest-api/public-endpoints/get-categories)
* [Get Popular Tags](/api/rest-api/public-endpoints/get-popular-tags)

#### Private Endpoints

* [API Keys](/api/rest-api/private-endpoints/api-key-management/api-keys)
* [Orders](/api/rest-api/private-endpoints/orders)
* [Portfolio](/api/rest-api/private-endpoints/portfolio)
* [History & Stats](/api/rest-api/private-endpoints/history-and-stats)

#### Shared rules

* [FAQ](/api/faq) for authentication, rate limits, errors, and pagination.
* Examples use readable placeholder IDs such as `evt_btc_100k_2026` and `ord_7f3a9c2d`.


# Public Endpoints

Public endpoints return data without authentication.

### Gateway

Unlike the Private API, the Public API routes through a single gateway. Each path prefix maps to the same host:

There is no single gateway. Each path prefix maps to its own service:

| Path prefix       | Host                                      |
| ----------------- | ----------------------------------------- |
| `/api/coins`      | `https://wallet-mutator-view.outpoll.com` |
| `/api/events/*`   | `https://event-service.outpoll.com`       |
| `/api/categories` | `https://event-service.outpoll.com`       |
| `/api/tags/*`     | `https://event-service.outpoll.com`       |

Use HTTPS for every request.

This host serves every public REST endpoint in this section.

### Authentication

No authentication required.

### Sections

* [Public API Overview](/api/rest-api/public-endpoints/public-api-overview) for the shared quickstart, errors, and pagination rules.
* [Search Events](/api/rest-api/public-endpoints/search-events) for market discovery and filtering.
* [Get Available Coins](/api/rest-api/public-endpoints/get-available-coins) for supported assets and collateral metadata.
* [Get Categories](/api/rest-api/public-endpoints/get-categories) for category filters and navigation.
* [Get Popular Tags](/api/rest-api/public-endpoints/get-popular-tags) for trending discovery tags.

### Example requests

Use the gateway directly for both `GET` and `POST` endpoints.

#### Search events

```bash
curl -X POST "https://outpoll.com/api/events/search?page=0&size=20" \
  -H "Content-Type: application/json" \
  --data '{"sb":"VOLUME_24H","sd":"DESC","l":"en","ss":"bitcoin"}'
```

### Endpoints

* [Get Available Coins](/api/rest-api/public-endpoints/get-available-coins) — `GET /api/coins`
* [Search Events](/api/rest-api/public-endpoints/search-events) — `POST /api/events/search`
* [Get Categories](/api/rest-api/public-endpoints/get-categories) — `GET /api/categories`
* [Get Popular Tags](/api/rest-api/public-endpoints/get-popular-tags) — `GET /api/tags/popular`

### Pagination

Only [Search Events](/api/rest-api/public-endpoints/search-events) is paginated.

Use these query parameters:

* `page` — zero-based page number
* `size` — items per page

Typical paginated responses include:

* `content` — current page items
* `totalElements` — total matching items
* `totalPages` — total page count

### Example IDs

* `evt_btc_100k_2026` for an event
* `mkt_btc_100k_yes_no` for a market
* `asset_btc_100k_yes` and `asset_btc_100k_no` for outcome assets
* `asset_usdc` for the quote asset


# Get Available Coins

Return the list of supported coins and assets

`GET`

### Overview

Returns the list of all supported coins and assets on the platform.

### Authentication

No authentication required.

### Request

* Method: `GET`
* Path: `/api/coins`
* Query parameters: None

### Response `200 OK`

```json
[
  {
    "i": "078dcd98-928d-479f-8110-ff6d27e44de2",
    "n": "USD Coin",
    "sn": "USDC",
    "ur": 1.0,
    "urd": "2026-01-15T10:30:00.000Z",
    "dpf": 6,
    "dps": 3,
    "ac": "ACTIVE",
    "iu": "https://example.com/usdc.png",
    "sc": false,
    "pmc": true
  }
]
```

| Field | Type    | Description                               |
| ----- | ------- | ----------------------------------------- |
| i     | string  | Coin ID                                   |
| n     | string  | Full coin name                            |
| sn    | string  | Short name or ticker                      |
| ur    | number  | USD exchange rate                         |
| urd   | string  | Rate update time in ISO 8601 format       |
| dpf   | integer | Decimal places for full precision         |
| dps   | integer | Decimal places for short display          |
| ac    | string  | Coin status                               |
| iu    | string  | Icon URL                                  |
| sc    | boolean | System coin flag                          |
| pmc   | boolean | Available as prediction market collateral |

### Error variants

Common public endpoint errors:

| Code | Description                         |
| ---- | ----------------------------------- |
| 400  | Bad Request — Invalid parameters    |
| 404  | Not Found — Resource does not exist |
| 500  | Internal Server Error               |

**Error response**

```json
{
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid parameters"
}
```

### Python example

Install the dependency first.

```bash
pip install requests
```

Send the request and iterate over the result.

{% code title="get\_coins.py" %}

```python
import requests

BASE_URL = "https://wallet-mutator-view.outpoll.com"

response = requests.get(
    f"{BASE_URL}/api/coins",
    timeout=10,
)
response.raise_for_status()

coins = response.json()

for coin in coins:
    print(f"{coin['sn']:<6} {coin['n']:<20} rate={coin['ur']}")
```

{% endcode %}

Example output:

```
USDC   USD Coin             rate=1.0
```

{% hint style="info" %}
Use `response.raise_for_status()` to fail fast on HTTP errors.
{% endhint %}


# Search Events

Search and browse prediction markets

`POST`

### Overview

Search and browse available prediction markets.

### Authentication

No authentication required.

### Request

* Method: `POST`
* Path: `/api/events/search`

#### Query Parameters

| Parameter | Type    | Required | Default | Description     |
| --------- | ------- | -------- | ------- | --------------- |
| page      | integer | No       | 0       | Zero-based page |
| size      | integer | No       | 20      | Items per page  |

#### Request body

| Field | Type      | Required | Description                                                                                      |
| ----- | --------- | -------- | ------------------------------------------------------------------------------------------------ |
| sb    | string    | No       | Sort by — `VOLUME_24H`, `TOTAL_VOLUME`, `LIQUIDITY`, `NEWEST`, `OLDEST`, `ENDING_SOON`, `CHANCE` |
| sd    | string    | No       | Sort direction — `ASC` or `DESC`                                                                 |
| l     | string    | No       | Language — `en`, `de`, `es`, `pt`, `zh`, `ja`, `ko`, `id`                                        |
| ss    | string    | No       | Search string                                                                                    |
| c     | string\[] | No       | Category slugs to filter                                                                         |
| t     | string\[] | No       | Tag names to filter                                                                              |
| ft    | boolean   | No       | Featured events only                                                                             |

#### Example request

```json
{
  "sb": "VOLUME_24H",
  "sd": "DESC",
  "l": "en",
  "ss": "bitcoin"
}
```

### Response `200 OK`

```json
{
  "content": [
    {
      "id": "54ccea1a-16fd-469c-8018-84b375243e8a",
      "title": "Will BTC reach $100k by end of 2026?",
      "outcomes": [
        {
          "marketId": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
          "yesId": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
          "noId": "c7b372ab-de41-4cea-b51a-12807ac63029"
        }
      ]
    }
  ],
  "totalElements": 500,
  "totalPages": 25
}
```

| Field               | Type    | Description                  |
| ------------------- | ------- | ---------------------------- |
| content             | array   | Array of events              |
| content\[].id       | string  | Event ID                     |
| content\[].title    | string  | Event title                  |
| content\[].outcomes | array   | List of outcomes and markets |
| totalElements       | integer | Total number of events       |
| totalPages          | integer | Total number of pages        |

### Error variants

Common public endpoint errors:

| Code | Description                         |
| ---- | ----------------------------------- |
| 400  | Bad Request — Invalid parameters    |
| 404  | Not Found — Resource does not exist |
| 500  | Internal Server Error               |

**Error response**

```json
{
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid parameters"
}
```

### Python example

{% code title="search\_events.py" %}

```python
import requests

response = requests.post(
    "https://event-service.outpoll.com/api/events/search",
    params={"page": 0, "size": 20},
    json={
        "sb": "VOLUME_24H",
        "sd": "DESC",
        "l": "en",
        "ss": "bitcoin",
    },
    timeout=10,
)
response.raise_for_status()

data = response.json()

for event in data["content"]:
    print(f"{event['id']} | {event['title']}")
```

{% endcode %}


# Get Categories

Retrieve all event categories

`GET`

### Overview

Retrieve all event categories. Use this endpoint to build filters or navigation.

### Authentication

No authentication required.

### Request

* Method: `GET`
* Path: `/api/categories`
* Query parameters: None

### Response `200 OK`

```json
[
  {
    "i": "category-id",
    "n": "Crypto",
    "s": "crypto",
    "pi": null,
    "sc": [
      {
        "i": "sub-id",
        "n": "Bitcoin",
        "s": "bitcoin"
      }
    ]
  }
]
```

| Field | Type   | Description                   |
| ----- | ------ | ----------------------------- |
| i     | string | Category ID                   |
| n     | string | Category name                 |
| s     | string | Category slug                 |
| pi    | string | Parent category ID, or `null` |
| sc    | array  | Nested subcategories          |

### Error variants

Common public endpoint errors:

| Code | Description                         |
| ---- | ----------------------------------- |
| 400  | Bad Request — Invalid parameters    |
| 404  | Not Found — Resource does not exist |
| 500  | Internal Server Error               |

**Error response**

```json
{
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid parameters"
}
```

### Python example

{% code title="get\_categories.py" %}

```python
import requests

response = requests.get(
    "https://event-service.outpoll.com/api/categories",
    timeout=10,
)
response.raise_for_status()

for category in response.json():
    print(f"{category['n']} ({category['s']})")
```

{% endcode %}


# Get Popular Tags

Retrieve trending tags

`GET`

### Overview

Retrieve trending tags.

### Authentication

No authentication required.

### Request

* Method: `GET`
* Path: `/api/tags/popular`

#### Query parameters

| Parameter | Type    | Required | Default | Description              |
| --------- | ------- | -------- | ------- | ------------------------ |
| limit     | integer | No       | 10      | Number of tags to return |

### Response `200 OK`

```json
[
  {
    "n": "Bitcoin",
    "r": 1
  }
]
```

| Field | Type    | Description     |
| ----- | ------- | --------------- |
| n     | string  | Tag name        |
| r     | integer | Popularity rank |

### Error variants

Common public endpoint errors:

| Code | Description                         |
| ---- | ----------------------------------- |
| 400  | Bad Request — Invalid parameters    |
| 404  | Not Found — Resource does not exist |
| 500  | Internal Server Error               |

**Error response**

```json
{
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid parameters"
}
```

### Python example

{% code title="get\_popular\_tags.py" %}

```python
import requests

response = requests.get(
    "https://event-service.outpoll.com/api/tags/popular",
    params={"limit": 10},
    timeout=10,
)
response.raise_for_status()

for tag in response.json():
    print(f"#{tag['r']} {tag['n']}")
```

{% endcode %}


# Public API Overview

Base URL, endpoint summary, pagination, and Python examples for public REST endpoints

Use public endpoints for read-only market data.

### Base URL

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

Use HTTPS for every request.

### Authentication

No authentication required.

### Common errors

| Code | Description                         |
| ---- | ----------------------------------- |
| 200  | OK — Request succeeded              |
| 400  | Bad Request — Invalid parameters    |
| 404  | Not Found — Resource does not exist |
| 500  | Internal Server Error               |

**Error response**

```json
{
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid parameters"
}
```

### Available endpoints

* [Get Available Coins](/api/rest-api/public-endpoints/get-available-coins) — supported assets and coin metadata
* [Search Events](/api/rest-api/public-endpoints/search-events) — market discovery with filters and pagination
* [Get Categories](/api/rest-api/public-endpoints/get-categories) — category tree for events
* [Get Popular Tags](/api/rest-api/public-endpoints/get-popular-tags) — trending tags for discovery

### Python quickstart

Install the HTTP client first.

```bash
pip install requests
```

Use a shared session and always set a timeout.

{% code title="public\_api.py" %}

```python
import requests

BASE_URL = "https://api.outpoll.com"
TIMEOUT = 10

session = requests.Session()


def get_json(method: str, path: str, **kwargs):
    response = session.request(
        method=method,
        url=f"{BASE_URL}{path}",
        timeout=TIMEOUT,
        **kwargs,
    )
    response.raise_for_status()
    return response.json()
```

{% endcode %}

### Python examples

#### Get available coins

Use this endpoint to fetch supported assets.

`GET /api/coins`

{% code title="get\_coins.py" %}

```python
coins = get_json("GET", "/api/coins")

for coin in coins:
    print(f"{coin['sn']}: {coin['n']} | rate={coin['ur']}")
```

{% endcode %}

#### Search events

Use this endpoint to browse markets.

`POST /api/events/search`

{% code title="search\_events.py" %}

```python
payload = {
    "sb": "VOLUME_24H",
    "sd": "DESC",
    "l": "en",
    "ss": "bitcoin",
}

params = {
    "page": 0,
    "size": 20,
}

data = get_json(
    "POST",
    "/api/events/search",
    params=params,
    json=payload,
)

for event in data["content"]:
    print(f"{event['i']} | {event['ti']}")
```

{% endcode %}

#### Get categories

Use this endpoint to build filters or navigation.

`GET /api/categories`

{% code title="get\_categories.py" %}

```python
categories = get_json("GET", "/api/categories")

for category in categories:
    print(f"{category['n']} ({category['s']})")
```

{% endcode %}

#### Get popular tags

Use this endpoint to surface trending topics.

`GET /api/tags/popular`

{% code title="get\_popular\_tags.py" %}

```python
tags = get_json(
    "GET",
    "/api/tags/popular",
    params={"limit": 10},
)

for tag in tags:
    print(f"#{tag['r']} {tag['n']}")
```

{% endcode %}

### Pagination

Paginated endpoints accept these query parameters:

* `page` — zero-based page number
* `size` — number of items per page

Typical paginated responses include:

* `content` — current page items
* `totalElements` — total matching items
* `totalPages` — total page count

#### Python example: iterate through pages

{% code title="paginate\_events.py" %}

```python
page = 0

while True:
    data = get_json(
        "POST",
        "/api/events/search",
        params={"page": page, "size": 50},
        json={"sb": "NEWEST", "sd": "DESC", "l": "en"},
    )

    for event in data["content"]:
        print(event["ti"])

    page += 1
    if page >= data["totalPages"]:
        break
```

{% endcode %}

### Next pages

Use the detailed endpoint pages when you need full request and response schemas:

* [Get Available Coins](/api/rest-api/public-endpoints/get-available-coins)
* [Search Events](/api/rest-api/public-endpoints/search-events)
* [Get Categories](/api/rest-api/public-endpoints/get-categories)
* [Get Popular Tags](/api/rest-api/public-endpoints/get-popular-tags)


# Private Endpoints

Private endpoints cover trading, balances, account actions, and trading history.

**Service Hosts**

There is no single gateway. Each path prefix maps to its own service:

| Path prefix            | Host                                      |
| ---------------------- | ----------------------------------------- |
| `/orders/*`            | `https://order-service.outpoll.com`       |
| `/api/events/*`        | `https://event-service.outpoll.com`       |
| `/api/user-balances/*` | `https://wallet-mutator-view.outpoll.com` |
| `/api/history/*`       | `https://history-service.outpoll.com`     |
| `/api/deposit/*`       | `https://wallet-mutator-view.outpoll.com` |
| `/api/transactions`    | `https://wallet-mutator-view.outpoll.com` |
| `/auth/*`              | `https://auth-service.outpoll.com`        |

### Authentication

Trading and data endpoints use API key headers.

| Header                  | Description                             |
| ----------------------- | --------------------------------------- |
| `OUTPOLL-API-KEY`       | API key. Starts with `op_k_`            |
| `OUTPOLL-API-SIGNATURE` | Base64URL-encoded HMAC-SHA256 signature |
| `OUTPOLL-API-TIMESTAMP` | Unix timestamp in seconds               |

Build the signature payload as:

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

Use only the path.

Do not include the full URL.

Do not include the query string.

Timestamps must be within **30 seconds** of server time.

### Signature example

Use this worked example for a signed `POST` request.

#### Input values

* API key: `op_k_abc123`
* API secret: `dGVzdF9zZWNyZXRfMTIzNDU2Nzg`
* timestamp: `1712500000`
* method: `POST`
* path: `/orders/market`

{% code title="body.json" %}

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

{% endcode %}

{% stepper %}
{% step %}

### Build the message

Concatenate `timestamp + method + path + body`.

{% code title="message.txt" %}

```
1712500000POST/orders/market{"e":"54ccea1a-16fd-469c-8018-84b375243e8a","o":"b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76","ba":"a1b2c3d4-...","qa":"078dcd98-928d-479f-8110-ff6d27e44de2","s":"BUY","am":50}
```

{% endcode %}
{% endstep %}

{% step %}

### Decode the secret

The secret is base64url encoded.

This value is already correctly padded.

{% code title="secret.txt" %}

```
dGVzdF9zZWNyZXRfMTIzNDU2Nzg
```

{% endcode %}

Decode it to raw bytes before signing.
{% endstep %}

{% step %}

### Compute the digest

Generate `HMAC-SHA256(secret_bytes, message)`.

The output is 32 raw bytes.
{% endstep %}

{% step %}

### Encode the signature

Base64url-encode the digest.

Strip any trailing `=` characters.

{% code title="signature.txt" %}

```
k7Hj9xQ2mN4pL1rT5vW8yB3cF6gJ0sD_eI2uA4wK7Zo
```

{% endcode %}
{% endstep %}

{% step %}

### Send the headers

Include the API key, signature, and timestamp headers.

{% code title="headers.http" %}

```http
OUTPOLL-API-KEY: op_k_abc123
OUTPOLL-API-SIGNATURE: k7Hj9xQ2mN4pL1rT5vW8yB3cF6gJ0sD_eI2uA4wK7Zo
OUTPOLL-API-TIMESTAMP: 1712500000
```

{% endcode %}
{% endstep %}
{% endstepper %}

{% hint style="info" %}
The body must match the exact JSON string sent on the wire. Any spacing or field order change will change the signature.
{% endhint %}

#### cURL example

Use this shell example to compute the signature and send the request with `curl`.

{% code title="signed\_market\_order.sh" %}

```bash
API_KEY='op_k_abc123'
API_SECRET='dGVzdF9zZWNyZXRfMTIzNDU2Nzg'
TIMESTAMP='1712500000'
METHOD='POST'
PATH='/orders/market'
BODY='{"e":"54ccea1a-16fd-469c-8018-84b375243e8a","o":"b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76","ba":"a1b2c3d4-...","qa":"078dcd98-928d-479f-8110-ff6d27e44de2","s":"BUY","am":50}'

MESSAGE="${TIMESTAMP}${METHOD}${PATH}${BODY}"

SECRET_B64="$(printf '%s' "$API_SECRET" | tr '_-' '/+' | awk '{ n = length($0) % 4; if (n == 2) printf "%s==", $0; else if (n == 3) printf "%s=", $0; else printf "%s", $0 }')"
SECRET_HEX="$(printf '%s' "$SECRET_B64" | base64 -d | xxd -p -c 256)"

SIGNATURE="$(
  printf '%s' "$MESSAGE" \
  | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$SECRET_HEX" -binary \
  | openssl base64 -A \
  | tr '+/' '-_' \
  | tr -d '='
)"

curl -X POST "https://order-service.outpoll.com${PATH}" \
  -H "Content-Type: application/json" \
  -H "OUTPOLL-API-KEY: ${API_KEY}" \
  -H "OUTPOLL-API-SIGNATURE: ${SIGNATURE}" \
  -H "OUTPOLL-API-TIMESTAMP: ${TIMESTAMP}" \
  --data "$BODY"
```

{% endcode %}

#### Python signature example

Use this example to sign and send a market order request directly to the order service.

{% code title="signed\_market\_order.py" %}

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

API_KEY = "op_k_abc123"
API_SECRET = "dGVzdF9zZWNyZXRfMTIzNDU2Nzg"
HOST = "https://order-service.outpoll.com"
PATH = "/orders/market"
BODY = {
    "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
    "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "ba": "a1b2c3d4-...",
    "qa": "078dcd98-928d-479f-8110-ff6d27e44de2",
    "s": "BUY",
    "am": 50,
}

body = json.dumps(BODY)
timestamp = str(int(time.time()))
message = timestamp + "POST" + PATH + body
secret_padded = API_SECRET + "=" * (4 - len(API_SECRET) % 4) if len(API_SECRET) % 4 else API_SECRET
secret_bytes = base64.urlsafe_b64decode(secret_padded)
signature = base64.urlsafe_b64encode(
    hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
).decode().rstrip("=")

response = requests.post(
    HOST + PATH,
    headers={
        "OUTPOLL-API-KEY": API_KEY,
        "OUTPOLL-API-SIGNATURE": signature,
        "OUTPOLL-API-TIMESTAMP": timestamp,
        "Content-Type": "application/json",
    },
    data=body,
    timeout=10,
)

print(response.status_code, response.text)
```

{% endcode %}

#### JavaScript signature example

Use this example to sign and send the same request with `fetch`.

{% code title="signed\_market\_order.js" %}

```javascript
import crypto from "node:crypto";

const apiKey = "op_k_abc123";
const apiSecret = "dGVzdF9zZWNyZXRfMTIzNDU2Nzg";
const host = "https://order-service.outpoll.com";
const path = "/orders/market";
const body = JSON.stringify({
  e: "54ccea1a-16fd-469c-8018-84b375243e8a",
  o: "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
  ba: "a1b2c3d4-...",
  qa: "078dcd98-928d-479f-8110-ff6d27e44de2",
  s: "BUY",
  am: 50,
});

const timestamp = String(Math.floor(Date.now() / 1000));
const message = timestamp + "POST" + path + body;
const secret = Buffer.from(
  apiSecret + "=".repeat((4 - apiSecret.length % 4) % 4),
  "base64url",
);
const signature = crypto
  .createHmac("sha256", secret)
  .update(message)
  .digest("base64url");

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

console.log(response.status, await response.text());
```

{% endcode %}

#### OutpollClient

Use a path-to-host map when one client needs to call multiple private services.

{% code title="outpoll\_client.py" %}

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

SERVICE_MAP = {
    "/api/events": "https://event-service.outpoll.com",
    "/api/categories": "https://event-service.outpoll.com",
    "/api/tags": "https://event-service.outpoll.com",
    "/api/coins": "https://wallet-mutator-view.outpoll.com",
    "/api/user-balances": "https://wallet-mutator-view.outpoll.com",
    "/api/history": "https://history-service.outpoll.com",
    "/orders": "https://order-service.outpoll.com",
    "/auth": "https://auth-service.outpoll.com",
}

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

    def _host(self, path: str) -> str:
        for prefix, host in SERVICE_MAP.items():
            if path.startswith(prefix):
                return host
        return "https://event-service.outpoll.com"

    def _sign(self, method: str, path: str, body: str = "") -> dict:
        timestamp = str(int(time.time()))
        message = timestamp + method + path + body
        padded = self.api_secret + "=" * (4 - len(self.api_secret) % 4) if len(self.api_secret) % 4 else self.api_secret
        secret_bytes = base64.urlsafe_b64decode(padded)
        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._host(path) + 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._host(path) + path, headers=headers, data=body)

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

{% endcode %}

### Sections

* [API Keys](/api/rest-api/private-endpoints/api-key-management/api-keys) for key creation, inspection, and revocation.
* [Orders](/api/rest-api/private-endpoints/orders) for limit, market, and TP/SL orders.
* [Portfolio](/api/rest-api/private-endpoints/portfolio) for portfolio-related endpoints.
* [Positions](/api/rest-api/private-endpoints/positions) for open positions and active orders.
* [Balances](/api/rest-api/private-endpoints/balances) for balance queries.
* [Deposit](/api/rest-api/private-endpoints/deposit) for the USDC deposit address.
* [History & Stats](/api/rest-api/private-endpoints/history-and-stats) for history coverage.
* [History](/api/rest-api/private-endpoints/history) for orders, trades, transactions, activity, and profile metrics.

### Example IDs

Examples use readable placeholders:

* `evt_btc_100k_2026` for an event
* `mkt_btc_100k_yes_no` for a market
* `asset_btc_100k_yes` for the YES asset
* `asset_usdc` for USDC
* `ord_7f3a9c2d` for an order
* `tpsl_4e2b1a90` for a TP/SL order

### Shared rules

See [FAQ](/api/faq) for rate limits, pagination, and common error handling.


# Portfolio

Positions, balances, active orders, and deposit endpoints

Use these pages to inspect holdings and funding details.

### Coverage

* [Positions](/api/rest-api/private-endpoints/positions) for open positions and active orders.
* [Balances](/api/rest-api/private-endpoints/balances) for balance snapshots.
* [Deposit](/api/rest-api/private-endpoints/deposit) for the USDC deposit address.

### Authentication

* Positions and balances use API key authentication.
* Deposit address uses JWT Bearer authentication.

### Notes

* Pagination uses `page` and `size`.
* Examples use `asset_usdc` for USDC.
* Active order placement and cancellation live in [Orders](/api/rest-api/private-endpoints/orders).


# History & Stats

Orders, trades, transactions, activity, and trading metrics

Use these endpoints to inspect completed activity and profile metrics.

### Coverage

* [History](/api/rest-api/private-endpoints/history) includes orders, trades, transactions, activity, and profile stats.
* [Order Feed](/api/rest-api/websockets/account-and-history-streams/order-feed), [Activity Feed](/api/rest-api/websockets/account-and-history-streams/activity-feed), and [Last Trades Feed](/api/rest-api/websockets/account-and-history-streams/last-trades-feed) cover live order, activity, and trade streams.

### Authentication

* Orders, trades, activity, and profile stats use API key authentication.
* Transactions use JWT Bearer authentication.

### Notes

* Most endpoints use paginated wrappers with `page` and `size`.
* `GET /api/history/activity` returns `t` for total items.
* Profile stats use a `userId` path parameter.


# API Key Management

Create, inspect, and revoke API keys

Manage API keys with account endpoints.

### Endpoints

* [Send Verification Code](/api/rest-api/private-endpoints/api-key-management/api-keys)
* [Create API Key](/api/rest-api/private-endpoints/api-key-management/create-api-key)
* [Get Active API Key](/api/rest-api/private-endpoints/api-key-management/get-active-api-key)
* [Revoke API Key](/api/rest-api/private-endpoints/api-key-management/revoke-api-key)


# Revoke API Key

Revoke the active API key

`DELETE`

### Overview

Revokes the active API key.

Create a new key before resuming API access.

### Authentication

Authenticated account access required.

### Common errors

| Code | Description                                      |
| ---- | ------------------------------------------------ |
| 200  | OK — Request succeeded                           |
| 201  | Created — Resource created successfully          |
| 204  | No Content — Success with no response body       |
| 400  | Bad Request — Invalid parameters                 |
| 401  | Unauthorized — Missing or invalid authentication |
| 403  | Forbidden — Operation not allowed                |
| 404  | Not Found — Resource does not exist              |
| 429  | Too Many Requests — Rate limit exceeded          |
| 500  | Internal Server Error                            |

### Request

* Method: `DELETE`
* Path: `/auth/api-key`
* Query parameters: None

### Response

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

The active API key is revoked.


# Get Active API Key

Return metadata for the active API key

`GET`

### Overview

Returns metadata for the active API key.

The `secret` is not returned.

### Authentication

Authenticated account access required.

### Common errors

| Code | Description                                      |
| ---- | ------------------------------------------------ |
| 200  | OK — Request succeeded                           |
| 201  | Created — Resource created successfully          |
| 204  | No Content — Success with no response body       |
| 400  | Bad Request — Invalid parameters                 |
| 401  | Unauthorized — Missing or invalid authentication |
| 403  | Forbidden — Operation not allowed                |
| 404  | Not Found — Resource does not exist              |
| 429  | Too Many Requests — Rate limit exceeded          |
| 500  | Internal Server Error                            |

### Request

* Method: `GET`
* Path: `/auth/api-key`
* Query parameters: None

### Response

**Response** `200 OK`

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

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


# Create API Key

Create a new API key and return its secret once

Use these endpoints for order placement and risk controls.

All endpoints on this page require API key authentication.

### Place Limit Order

```
POST /orders/limit
```

#### Request Body

| Field | Type   | Required | Description                      |
| ----- | ------ | -------- | -------------------------------- |
| e     | string | Yes      | Event ID                         |
| o     | string | Yes      | Market ID                        |
| ba    | string | Yes      | Outcome asset ID to trade        |
| qa    | string | Yes      | Quote asset ID. Use `asset_usdc` |
| s     | string | Yes      | Side: `BUY` or `SELL`            |
| p     | number | Yes      | Price from `0.01` to `0.99`      |
| q     | number | Yes      | Quantity in shares               |

#### Example Request

```json
{
  "e": "evt_btc_100k_2026",
  "o": "mkt_btc_100k_yes_no",
  "ba": "asset_btc_100k_yes",
  "qa": "asset_usdc",
  "s": "BUY",
  "p": 0.42,
  "q": 100
}
```

#### Response `200 OK` or `202 Accepted`

```json
{
  "i": "ord_7f3a9c2d"
}
```

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

### Place Market Order

```
POST /orders/market
```

Creates a new API key.

The `secret` is shown only once.

Creating a new key deactivates the previous key.

### Authentication

Authenticated account access required.

### Common errors

| Code | Description                                      |
| ---- | ------------------------------------------------ |
| 200  | OK — Request succeeded                           |
| 201  | Created — Resource created successfully          |
| 204  | No Content — Success with no response body       |
| 400  | Bad Request — Invalid parameters                 |
| 401  | Unauthorized — Missing or invalid authentication |
| 403  | Forbidden — Operation not allowed                |
| 404  | Not Found — Resource does not exist              |
| 429  | Too Many Requests — Rate limit exceeded          |
| 500  | Internal Server Error                            |

#### Request Body

| Field | Type   | Required    | Description                                 |
| ----- | ------ | ----------- | ------------------------------------------- |
| e     | string | Yes         | Event ID                                    |
| o     | string | Yes         | Market ID                                   |
| ba    | string | Yes         | Outcome asset ID                            |
| qa    | string | Yes         | Quote asset ID                              |
| s     | string | Yes         | Side: `BUY` or `SELL`                       |
| am    | number | Conditional | USDC amount to spend. Required for `BUY`    |
| q     | number | Conditional | Share quantity to sell. Required for `SELL` |

#### Example Request for BUY

```json
{
  "e": "evt_btc_100k_2026",
  "o": "mkt_btc_100k_yes_no",
  "ba": "asset_btc_100k_yes",
  "qa": "asset_usdc",
  "s": "BUY",
  "am": 50
}
```

#### Example Request for SELL

```json
{
  "e": "evt_btc_100k_2026",
  "o": "mkt_btc_100k_yes_no",
  "ba": "asset_btc_100k_yes",
  "qa": "asset_usdc",
  "s": "SELL",
  "q": 100
}
```

#### Response `200 OK` or `202 Accepted`

```json
{
  "i": "ord_7f3a9c2d"
}
```

### Cancel Order

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

#### Path Parameters

| Parameter | Type   | Description        |
| --------- | ------ | ------------------ |
| orderId   | string | Order ID to cancel |

#### Example

```
DELETE /orders/limit/ord_7f3a9c2d
```

#### Response `200 OK` or `204 No Content`

### Set Take Profit / Stop Loss

```
POST /orders/tpsl
```

#### Request Body

| Field | Type   | Required | Description               |
| ----- | ------ | -------- | ------------------------- |
| e     | string | Yes      | Event ID                  |
| o     | string | Yes      | Market ID                 |
| ba    | string | Yes      | Position asset ID         |
| oa    | string | Yes      | Opening asset ID          |
| qa    | string | Yes      | Quote asset ID            |
| i     | string | Yes      | Indication: `YES` or `NO` |
| s     | string | Yes      | Side. Use `SELL`          |
| q     | number | Yes      | Quantity to close         |
| t     | number | No       | Take-profit price         |
| l     | number | No       | Stop-loss price           |

At least one of `t` or `l` is required.

#### Example Request

```json
{
  "e": "evt_btc_100k_2026",
  "o": "mkt_btc_100k_yes_no",
  "ba": "asset_btc_100k_yes",
  "oa": "asset_btc_100k_yes",
  "qa": "asset_usdc",
  "i": "YES",
  "s": "SELL",
  "q": 100,
  "t": 0.95,
  "l": 0.1
}
```

#### Response `200 OK` or `202 Accepted`

### Get TP/SL Orders

```
GET /orders/tpsl
```

#### Query Parameters

| Parameter | Type    | Required | Description                    |
| --------- | ------- | -------- | ------------------------------ |
| assetId   | string  | Yes      | Asset ID                       |
| status    | string  | No       | Status filter such as `ACTIVE` |
| primary   | boolean | No       | Filter primary orders          |

#### Example

```
GET /orders/tpsl?assetId=asset_btc_100k_yes&status=ACTIVE
```

* Method: `POST`
* Path: `/auth/api-key`

#### 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 in ISO 8601 format |

#### Response `200 OK`

```json
[
  {
    "i": "tpsl_4e2b1a90",
    "t": 0.95,
    "l": 0.1,
    "q": 100,
    "status": "ACTIVE"
  }
]
```

### Cancel TP/SL Order

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

#### Path Parameters

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

#### Example

```
DELETE /orders/tpsl/tpsl_4e2b1a90
```

#### Response `200 OK` or `204 No Content`

**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` |


# API Keys

Create, inspect, and revoke API keys

Manage API keys with account endpoints.

### Send Verification Code

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

#### Response `200 OK`

Sends the email confirmation code required for API key creation.

### Create API Key

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

#### 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 in ISO 8601. `null` means no expiry |

#### Response `200 OK`

```json
{
  "id": "key_live_primary",
  "key": "op_k_live_primary",
  "secret": "sec_live_primary_base64url",
  "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 in ISO 8601 |
| expiresAt | string | Expiration timestamp or `null` |

The `secret` is returned only once.

Creating a new key deactivates the previous key.

### Get Active API Key

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

#### Response `200 OK`

Returns active key metadata. The `secret` is not returned.

### Revoke API Key

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

#### Response `200 OK` or `204 No Content`

Revokes the active key.


# Orders

Place, inspect, and cancel trading orders

Trading endpoints use API key authentication.

### Shared request fields

| Field | Description                    |
| ----- | ------------------------------ |
| `e`   | Event ID                       |
| `o`   | Outcome or market ID           |
| `ba`  | Betting asset ID               |
| `qa`  | Quote asset ID                 |
| `s`   | Side: `BUY` or `SELL`          |
| `q`   | Quantity in shares             |
| `am`  | Amount in USDC for market buys |

### Endpoints

* [Place Limit Order](/api/rest-api/private-endpoints/orders/place-limit-order)
* [Place Market Order](/api/rest-api/private-endpoints/orders/place-market-order)
* [Cancel Order](/api/rest-api/private-endpoints/orders/cancel-order)
* [Set Take Profit / Stop Loss](/api/rest-api/private-endpoints/orders/take-profit-stop-loss)
* [Get TP/SL Orders](/api/rest-api/private-endpoints/orders/get-tp-sl-orders)
* [Cancel TP/SL Order](/api/rest-api/private-endpoints/orders/cancel-tp-sl-order)

### Related pages

* [Positions](/api/rest-api/private-endpoints/positions) for active orders and open positions.
* [History](/api/rest-api/private-endpoints/history) for order and trade history.
* [Order Updates](/api/rest-api/websockets/account-and-history-streams/order-updates) for `ORDER_UPDATE` and `TPSL_UPDATE`.


# Place Market Order

Execute an order at the best available price

`POST`

### Overview

Executes an order immediately at the best available price.

Use `am` for `BUY` orders and `q` for `SELL` orders.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `POST`
* Path: `/orders/market`

#### Request body

| Field | Type   | Required    | Description                                     |
| ----- | ------ | ----------- | ----------------------------------------------- |
| e     | string | Yes         | Event ID                                        |
| o     | string | Yes         | Outcome ID                                      |
| 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 in shares to sell. Required for `SELL` |

### Response

**Response** `200 OK` or `202 Accepted`

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

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


# Get Active Orders

Return active unfilled orders

`GET`

### Overview

Returns active orders that are not fully filled.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `GET`
* Path: `/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

**Response** `200 OK`

```json
{
  "i": [
    {
      "id": "order-id",
      "status": "ACTIVE",
      "side": "BUY",
      "price": 0.42,
      "quantity": 100
    }
  ]
}
```

| Field    | Type   | Description          |
| -------- | ------ | -------------------- |
| id       | string | Order ID             |
| status   | string | Order status         |
| side     | string | Order side           |
| price    | number | Limit price          |
| quantity | number | Remaining order size |


# Cancel Order

Cancel an active limit order

`DELETE`

### Overview

Cancels an active limit order.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `DELETE`
* Path: `/orders/limit/{orderId}`

#### Path parameters

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

### Response

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

The order is canceled if it is still active.


# Place Limit Order

Place a limit order at a specific price

`POST`

### Overview

Places a limit order at a specific price.

The order stays active until it fills or is canceled.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `POST`
* Path: `/orders/limit`

#### Request body

| Field | Type   | Required | Description                                                         |
| ----- | ------ | -------- | ------------------------------------------------------------------- |
| e     | string | Yes      | Event ID                                                            |
| o     | string | Yes      | Outcome ID                                                          |
| ba    | string | Yes      | Betting asset ID                                                    |
| qa    | string | Yes      | Quote asset ID (`USDC` uses `078dcd98-928d-479f-8110-ff6d27e44de2`) |
| s     | string | Yes      | Side: `BUY` or `SELL`                                               |
| p     | number | Yes      | Price from `0.01` to `0.99`                                         |
| q     | number | Yes      | Quantity in shares                                                  |

### Response

**Response** `200 OK` or `202 Accepted`

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

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


# Take Profit / Stop Loss

Create take-profit and stop-loss orders for a position

`POST`

### Overview

Creates take-profit and stop-loss orders for an open position.

When a trigger price is reached, the position is closed automatically.

Provide at least one of `t` or `l`.

Use `SELL` to close the position.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `POST`
* Path: `/orders/tpsl`

#### Request body

| Field | Type   | Required | Description                             |
| ----- | ------ | -------- | --------------------------------------- |
| e     | string | Yes      | Event ID                                |
| o     | string | Yes      | Outcome ID                              |
| 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 in shares to close             |
| t     | number | No       | Take-profit price from `0.01` to `0.99` |
| l     | number | No       | Stop-loss price from `0.01` to `0.99`   |

{% hint style="info" %}
At least one of `t` or `l` must be provided.
{% endhint %}

### Response

**Response** `200 OK` or `202 Accepted`

The TP/SL order is created if the request is accepted.


# Get TP/SL Orders

Return active take-profit and stop-loss orders

`GET`

### Overview

Returns active take-profit and stop-loss orders for a specific asset.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `GET`
* Path: `/orders/tpsl`

#### Query parameters

| Parameter | Type    | Required | Description                                  |
| --------- | ------- | -------- | -------------------------------------------- |
| assetId   | string  | Yes      | Asset or runner ID                           |
| primary   | boolean | Yes      | Filter by primary asset: `true` or `false`   |
| status    | string  | No       | Filter by order status, for example `ACTIVE` |

### Response

**Response** `200 OK`

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

| Field | Type   | Description       |
| ----- | ------ | ----------------- |
| i     | string | TP/SL order ID    |
| q     | number | Quantity          |
| tpp   | number | Take-profit price |
| slp   | number | Stop-loss price   |


# Cancel TP/SL Order

Cancel an active take-profit or stop-loss order

`DELETE`

### Overview

Cancels an active take-profit or stop-loss order.

### Authentication

HMAC-SHA256 authentication required.

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

Generate the signature from `timestamp + method + path + body`.

Timestamps must be within `30` seconds of server time.

### Common 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 — API key permanently blocked               |
| 404  | Not Found — Resource does not exist                   |
| 429  | Too Many Requests — Rate limit exceeded               |
| 500  | Internal Server Error                                 |

### Request

* Method: `DELETE`
* Path: `/orders/tpsl/{tpslId}`

#### Path parameters

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

### Response

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

The TP/SL order is canceled if it is still active.


# Positions

Open positions and active order snapshots

Use API key authentication for every endpoint on this page.

### Get Open Positions

```
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.5,
      "c": 50,
      "cp": 0.55,
      "v": 55,
      "tpsl": [],
      "pv": 5,
      "pp": 10,
      "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 | Primary outcome flag  |
| tsc   | number  | Total shares count    |
| asc   | number  | Available shares      |
| sp    | number  | Entry price           |
| 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             |

### Get Active 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 | Primary outcome flag  |
| sc    | number  | Shares count          |
| sp    | number  | Share price           |
| si    | string  | Side: `BUY` or `SELL` |
| fq    | number  | Filled quantity       |
| oq    | number  | Original quantity     |
| cp    | number  | Current price         |
| od    | string  | Open date             |
| st    | string  | Status                |

### Related pages

* [Orders](/api/rest-api/private-endpoints/orders) for order placement and cancellation.
* [History](/api/rest-api/private-endpoints/history) for filled orders and activity.


# Balances

Available and enriched balance views

Use API key authentication for every endpoint on this page.

Need live balance deltas.

Use [Balance Updates](/api/rest-api/websockets/account-and-history-streams/balance-updates) for the `balance/ws` stream.

### Get User Balances

```
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.5,
      "c": 50,
      "v": 55,
      "tv": 55
    }
  ],
  "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        |

### Get Full Balances

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

#### Response `200 OK`

Uses the 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 | Primary flag   |


# Deposit

Retrieve the authenticated user's USDC deposit address

Use authenticated account access for this endpoint.

### Get Deposit Address

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

#### Example request

Use the wallet service host for this endpoint.

{% code title="get\_deposit\_address.sh" %}

```bash
curl "https://wallet-mutator-view.outpoll.com/api/deposit/usdc-address" \
  -H "Authorization: Bearer <token>"
```

{% endcode %}

#### Response `200 OK`

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

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

#### Response `404 Not Found`

Address has not been generated yet.


# History

Orders, trades, transactions, activity, and profile metrics

Use these endpoints to inspect completed trading activity and account metrics.

Need live events instead of polling.

Use [Order Feed](/api/rest-api/websockets/account-and-history-streams/order-feed), [Activity Feed](/api/rest-api/websockets/account-and-history-streams/activity-feed), and [Last Trades Feed](/api/rest-api/websockets/account-and-history-streams/last-trades-feed) for live updates.

### Authentication

* Use API key authentication for orders, trades, activity, and profile stats.
* Use JWT Bearer authentication for transactions.

### Order History

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

#### Query parameters

<table><thead><tr><th width="156.609375">Parameter</th><th>Type</th><th>Required</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>page</td><td>integer</td><td>No</td><td>0</td><td>Page number</td></tr><tr><td>size</td><td>integer</td><td>No</td><td>20</td><td>Items per page</td></tr><tr><td>eventOutcomeId</td><td>string</td><td>No</td><td>—</td><td>Filter by outcome or market ID</td></tr><tr><td>sort</td><td>string</td><td>No</td><td>desc</td><td>Sort direction: <code>asc</code> or <code>desc</code></td></tr></tbody></table>

#### 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 | Outcome ID          |
| a     | string | Asset ID            |
| oq    | number | Original quantity   |
| si    | string | Side                |
| t     | string | Order type          |
| p     | number | Price               |
| m     | number | Placement time      |
| ex    | string | Expire at or `null` |
| st    | string | Status              |
| q     | number | Filled quantity     |

### Trade History

```
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 or 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.5,
      "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            |
| e     | string | Event ID        |
| o     | string | Outcome ID      |
| a     | string | Asset ID        |
| q     | number | Quantity        |
| p     | number | Price           |
| f     | number | Fee             |
| m     | string | Execution time  |

### Transaction History

```
GET /api/transactions
```

#### Headers

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

#### Query parameters

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

#### Example request

Use the wallet service host for transaction history.

{% code title="get\_transactions.sh" %}

```bash
curl "https://wallet-mutator-view.outpoll.com/api/transactions?page=0&size=20" \
  -H "Authorization: Bearer <token>"
```

{% endcode %}

#### 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,
      "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 | Primary outcome flag       |
| t     | string  | Transaction type           |
| am    | number  | Amount                     |
| s     | string  | Status                     |
| cat   | string  | Created at                 |
| e     | string  | External transaction ID    |
| f     | number  | Fee                        |
| ta    | string  | Withdrawal address         |
| bu    | string  | Blockchain transaction URL |
| bn    | string  | Blockchain name            |

### Activity Feed

```
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.9,
      "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    |
| si    | string  | Side                  |
| q     | number  | Quantity              |
| x     | string  | Execution time        |
| eq    | string  | Event question        |
| ei    | string  | Event icon URL        |
| es    | string  | Event slug            |
| tn    | string  | Token name            |
| ip    | boolean | Primary outcome flag  |
| pr    | number  | Price                 |
| ev    | number  | Event volume          |
| uv    | number  | User volume or `null` |

{% hint style="info" %}
This endpoint returns `t` for total items instead of `tp`.
{% endhint %}

### Profile Stats

```
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,
    "volumeTraded": 125000,
    "marketsTraded": 42
  }
}
```

| Field               | Type    | Description        |
| ------------------- | ------- | ------------------ |
| profile.userId      | string  | User ID            |
| profile.username    | string  | Username           |
| profile.joinedAt    | string  | Join date          |
| profile.me          | boolean | Authenticated user |
| cards.positionValue | number  | Position value     |
| cards.volumeTraded  | number  | Volume traded      |
| cards.marketsTraded | number  | Markets traded     |

### Position Stats

```
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,
        "sp": 50,
        "si": "BUY",
        "c": 50,
        "v": 55,
        "pv": 5,
        "pp": 10
      }
    ],
    "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,
        "sp": 50,
        "si": "BUY",
        "c": 50,
        "v": 100,
        "pv": 50,
        "pp": 100,
        "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  |
| ti    | number | Total items |
| od    | string | Open date   |
| cd    | string | Close date  |


# 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) |


# Websockets

Use Websockets for low-latency market and account updates.

### Authentication models

Use the auth model required by each stream:

* No auth for Order Book, Chance History, Activity Feed, and Last Trades Feed
* In-band `AUTH` for Order Updates, Balance Updates, and Order Feed
* Private streams use API key auth only

{% hint style="info" %}
After reconnect, authenticate again and restore every subscription.
{% endhint %}

### Endpoint pages

* [Order Book](/api/rest-api/websockets/order-book) — real-time best bid, best ask, and last chance per outcome
* [Chance History](/api/rest-api/websockets/chance-history) — live probability updates and chart history
* [Account & History Streams](/api/rest-api/websockets/account-and-history-streams) — orders, balances, activity, and last trades
* [WebSocket API Reference](/api/rest-api/websockets/websocket-api-reference) — full auth, message, and stream reference

### Private stream auth

Send this message right after connect.

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Build `signature` from `timestamp + "GET" + path`.

### Stream groups

#### Public market data

Connect directly:

* `wss://order-book.outpoll.com/event/ws`
* `wss://chance-history-service.outpoll.com/ws`

#### Private account data

Send `AUTH` after connect:

* `wss://order-service.outpoll.com/order/ws`
* `wss://wallet-mutator-view.outpoll.com/balance/ws`
* `wss://history-service.outpoll.com/order/ws`

#### Public activity and trade broadcasts

No auth required:

* `wss://history-service.outpoll.com/history/ws`
* `wss://history-service.outpoll.com/last-trades/ws`


# Order Book

Real-time best bid, best ask, and last chance updates

Use this stream for live order book updates by event.

### Endpoint

```
wss://order-book.outpoll.com/event/ws
```

### Authentication

No authentication required.

### Message envelope

Every message uses the same top-level fields:

```json
{
  "t": "<TYPE>",
  "e": "<EVENT_ID>",
  "d": [...],
  "r": "<ERROR_MESSAGE>"
}
```

| Field | Type   | Description                    |
| ----- | ------ | ------------------------------ |
| `t`   | string | Message type                   |
| `e`   | string | Event ID                       |
| `d`   | array  | Outcome price payload          |
| `r`   | string | Error message for failed calls |

### Message types

| Type                | Direction | Description              |
| ------------------- | --------- | ------------------------ |
| `SUBSCRIBE`         | Client    | Subscribe to one event   |
| `SUBSCRIBED`        | Server    | Subscription confirmed   |
| `UNSUBSCRIBE`       | Client    | Remove one subscription  |
| `UNSUBSCRIBED`      | Server    | Unsubscribe confirmed    |
| `REQUEST_DATA`      | Client    | Request a full snapshot  |
| `PRICES_DATA`       | Server    | Full price snapshot      |
| `PRICES_DATA_DELTA` | Server    | Incremental price update |
| `PING`              | Server    | Heartbeat                |
| `ERROR`             | Server    | Request error            |

### Subscribe

```json
{
  "t": "SUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a"
}
```

Server confirmation:

```json
{
  "t": "SUBSCRIBED",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a"
}
```

### Price payload

`PRICES_DATA` sends the current state.

`PRICES_DATA_DELTA` sends only changed outcomes.

```json
{
  "t": "PRICES_DATA",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "d": [
    {
      "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
      "c": 0.55,
      "b": 0.54,
      "a": 0.56
    }
  ]
}
```

| Field | Type   | Description |
| ----- | ------ | ----------- |
| `o`   | string | Outcome ID  |
| `c`   | number | Last chance |
| `b`   | number | Best bid    |
| `a`   | number | Best ask    |

### Python example

{% code title="order\_book.py" %}

```python
import asyncio, json, websockets

async def order_book():
    uri = "wss://order-book.outpoll.com/event/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "t": "SUBSCRIBE",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
        }))

        async for msg in ws:
            data = json.loads(msg)
            if data["t"] in ("PRICES_DATA", "PRICES_DATA_DELTA"):
                for price in data["d"]:
                    print(price["o"], price["c"], price["b"], price["a"])
```

{% endcode %}


# Chance History

Real-time probability updates and chart history

Use this stream for live probability updates and chart backfill.

### Endpoint

```
wss://chance-history-service.outpoll.com/ws
```

### Authentication

No authentication required.

### Message envelope

```json
{
  "t": "<TYPE>",
  "e": "<EVENT_ID>",
  "o": ["<OUTCOME_ID>"],
  "d": { ... },
  "r": "<ERROR_MESSAGE>"
}
```

| Field | Type      | Description                    |
| ----- | --------- | ------------------------------ |
| `t`   | string    | Message type                   |
| `e`   | string    | Event ID                       |
| `o`   | string\[] | Outcome IDs                    |
| `d`   | object    | Message payload                |
| `r`   | string    | Error message for failed calls |

### Subscribe

```json
{
  "t": "SUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "97b06d16-aeff-4dd4-b08b-1ba30a2b8895"
  ]
}
```

After subscribe, the server sends:

1. `probability` with the latest known value
2. `success` with subscribe confirmation

### Live updates

#### Probability update

```json
{
  "t": "probability",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": {
    "t": 1712160300,
    "p": 0.67
  }
}
```

#### History update

```json
{
  "t": "UPDATE_HISTORY",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": {
    "t": 1712160300,
    "p": 0.67
  }
}
```

| Field | Type   | Description                     |
| ----- | ------ | ------------------------------- |
| `d.t` | number | Unix timestamp in seconds       |
| `d.p` | number | Probability from `0.0` to `1.0` |

### Unsubscribe

```json
{
  "t": "UNSUBSCRIBE",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
  ]
}
```

Success response:

```json
{
  "t": "success",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": { "message": "unsubscribe successful" }
}
```

### Request history

```json
{
  "t": "HISTORY",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
  ],
  "d": {
    "p": "1H",
    "f": "2026-01-01T00:00:00Z",
    "t": "2026-01-01T12:00:00Z"
  }
}
```

#### Supported periods

| Code  |
| ----- |
| `1s`  |
| `5s`  |
| `5M`  |
| `10M` |
| `30M` |
| `1H`  |
| `3H`  |
| `4H`  |
| `6H`  |
| `1d`  |
| `7d`  |
| `1m`  |

You can also pass a custom number of seconds as a string.

### History response

```json
{
  "t": "HISTORY_RESPONSE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": {
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76": [
      { "t": 1735689600, "p": 0.50 },
      { "t": 1735693200, "p": 0.52 },
      { "t": 1735696800, "p": 0.55 }
    ]
  }
}
```

Each point includes:

| Field | Type   | Description             |
| ----- | ------ | ----------------------- |
| `t`   | number | Period end in Unix time |
| `p`   | number | Close probability value |

{% hint style="info" %}
The first point is an anchor value at or before `from`. If no data exists in range, the service returns a flat line.
{% endhint %}

### REST parity

The same history is available over REST:

```
GET /api/events/{eventId}/outcomes/{outcomeId}/history
```

### Python example

{% code title="chance\_history.py" %}

```python
import asyncio, json, websockets

async def chart_stream():
    uri = "wss://chance-history-service.outpoll.com/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "t": "SUBSCRIBE",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
            "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
        }))

        async for msg in ws:
            data = json.loads(msg)
            print(data["t"], data["d"])
```

{% endcode %}


# Account & History Streams

Order, balance, activity, and trade WebSocket endpoints

Use these streams for private account events and public history broadcasts.

### Endpoint pages

* [Order Updates](/api/rest-api/websockets/account-and-history-streams/order-updates) — order execution and TP/SL status events
* [Balance Updates](/api/rest-api/websockets/account-and-history-streams/balance-updates) — initial balance snapshot and live balance deltas
* [Order Feed](/api/rest-api/websockets/account-and-history-streams/order-feed) — live order status events from the history service
* [Activity Feed](/api/rest-api/websockets/account-and-history-streams/activity-feed) — live event activity with subscribe and unsubscribe
* [Last Trades Feed](/api/rest-api/websockets/account-and-history-streams/last-trades-feed) — public stream of the latest platform trades

### Shared authentication

Only private streams need auth:

* Order Updates
* Balance Updates
* Order Feed

Send this message after connect.

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Build `signature` from `timestamp + "GET" + path`.

{% hint style="info" %}
After reconnect, authenticate again and restore every subscription.
{% endhint %}

### Public streams

* **Activity Feed** is public. Use subscribe and unsubscribe messages.
* **Last Trades Feed** is public. Connect and read messages.

### When to use which stream

* Use **Order Updates** right after placing or changing orders.
* Use **Balance Updates** for wallet and position balance changes.
* Use **Order Feed** for history-service order events.
* Use **Activity Feed** for per-event live activity.
* Use **Last Trades Feed** for public trade tape style updates.

### Common errors

```json
{
  "t": "error",
  "e": "event-id",
  "o": ["outcome-id"],
  "d": {
    "error": "Error description"
  }
}
```

| Error                          | Cause                         |
| ------------------------------ | ----------------------------- |
| Invalid or missing credentials | API key auth failed           |
| No outcome IDs provided        | Subscribe without outcome IDs |
| Invalid time range             | `from` is after `to`          |
| Invalid period                 | Unsupported history period    |
| Unknown message type           | Unsupported `t` value         |


# Order Updates

Real-time order execution and TP/SL status events

Use this stream for private order lifecycle events.

### Endpoint

```
wss://order-service.outpoll.com/order/ws
```

### Authentication

Send this message right after connect.

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Build `signature` from `timestamp + "GET" + "/order/ws"`.

Success response:

```json
{
  "t": "success",
  "d": "authorized",
  "r": null
}
```

Failure response:

```json
{
  "t": "error",
  "d": {
    "error": "Invalid or missing credentials"
  }
}
```

### Order event

```json
{
  "t": "ORDER_UPDATE",
  "d": {
    "i": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "u": "user-id",
    "s": "SUCCESS"
  }
}
```

| Field | Type   | Description                   |
| ----- | ------ | ----------------------------- |
| `d.i` | string | Order ID                      |
| `d.u` | string | User ID                       |
| `d.s` | string | Status. `SUCCESS` or `FAILED` |

### TP/SL event

```json
{
  "t": "TPSL_UPDATE",
  "d": {
    "i": "order-id",
    "u": "user-id",
    "a": "asset-id",
    "q": 100,
    "tpp": 0.95,
    "slp": 0.10,
    "s": "ACTIVE"
  }
}
```

| Field   | Type   | Description       |
| ------- | ------ | ----------------- |
| `d.i`   | string | Order ID          |
| `d.u`   | string | User ID           |
| `d.a`   | string | Asset ID          |
| `d.q`   | number | Quantity          |
| `d.tpp` | number | Take-profit price |
| `d.slp` | number | Stop-loss price   |
| `d.s`   | string | Status            |

### Heartbeat

```json
{
  "t": "PING",
  "d": null,
  "r": null
}
```

### Python example

{% code title="order\_updates.py" %}

```python
import asyncio, json, websockets

def auth_message(api_key, signature, timestamp):
    return {
        "t": "AUTH",
        "d": {
            "apiKey": api_key,
            "signature": signature,
            "timestamp": timestamp,
        },
    }

async def order_updates(api_key, signature, timestamp):
    uri = "wss://order-service.outpoll.com/order/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps(auth_message(api_key, signature, timestamp)))

        async for msg in ws:
            print(json.loads(msg))
```

{% endcode %}


# Balance Updates

Initial balance snapshot and live balance delta events

Use this stream for private balance snapshots and live balance changes.

### Endpoint

```
wss://wallet-mutator-view.outpoll.com/balance/ws
```

### Authentication

Send this message right after connect.

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Build `signature` from `timestamp + "GET" + "/balance/ws"`.

### Initial snapshot

After auth, the server sends the current balances:

```json
{
  "t": "success",
  "d": {
    "l": [
      {
        "i": "078dcd98-928d-479f-8110-ff6d27e44de2",
        "b": 1250.50,
        "f": 1200.00
      }
    ]
  },
  "r": null
}
```

| Field     | Type   | Description  |
| --------- | ------ | ------------ |
| `d.l[].i` | string | Asset ID     |
| `d.l[].b` | number | Balance      |
| `d.l[].f` | number | Free balance |

### Balance delta

```json
{
  "u": "user-id",
  "a": "asset-id",
  "aop": 0.50,
  "c": 50.00,
  "b": 1200.50,
  "f": 1150.00
}
```

| Field | Type   | Description        |
| ----- | ------ | ------------------ |
| `u`   | string | User ID            |
| `a`   | string | Asset ID           |
| `aop` | number | Average open price |
| `c`   | number | Cost               |
| `b`   | number | Balance            |
| `f`   | number | Free balance       |

### Python example

{% code title="balance\_updates.py" %}

```python
import asyncio, json, websockets

def auth_message(api_key, signature, timestamp):
    return {
        "t": "AUTH",
        "d": {
            "apiKey": api_key,
            "signature": signature,
            "timestamp": timestamp,
        },
    }

async def balance_updates(api_key, signature, timestamp):
    uri = "wss://wallet-mutator-view.outpoll.com/balance/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps(auth_message(api_key, signature, timestamp)))

        async for msg in ws:
            print(json.loads(msg))
```

{% endcode %}


# Order Feed

Live order status events from the history service

Use this stream for authenticated order status events from the history service.

### Endpoint

```
wss://history-service.outpoll.com/order/ws
```

### Authentication

Send this message right after connect.

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Build `signature` from `timestamp + "GET" + "/order/ws"`.

Success response:

```json
{
  "t": "SUCCESS",
  "d": {
    "message": "AUTH successful"
  },
  "r": null
}
```

### Notes

* Use this stream for live order status events.
* Message payloads can vary by event type.
* Re-authenticate after reconnect.

### Python example

{% code title="order\_feed.py" %}

```python
import asyncio, json, websockets

def auth_message(api_key, signature, timestamp):
    return {
        "t": "AUTH",
        "d": {
            "apiKey": api_key,
            "signature": signature,
            "timestamp": timestamp,
        },
    }

async def order_feed(api_key, signature, timestamp):
    uri = "wss://history-service.outpoll.com/order/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps(auth_message(api_key, signature, timestamp)))

        async for msg in ws:
            print(json.loads(msg))
```

{% endcode %}


# Activity Feed

Live event activity stream with subscribe and unsubscribe

Use this stream for live trades and position-related activity by event outcome.

### Endpoint

```
wss://history-service.outpoll.com/history/ws
```

### Authentication

No authentication required.

### Message envelope

```json
{
  "t": "<TYPE>",
  "e": "<EVENT_ID>",
  "o": "<OUTCOME_ID>",
  "d": { ... },
  "r": "<ERROR_MESSAGE>"
}
```

| Field | Type   | Description   |
| ----- | ------ | ------------- |
| `t`   | string | Message type  |
| `e`   | string | Event ID      |
| `o`   | string | Outcome ID    |
| `d`   | object | Event payload |
| `r`   | string | Error message |

### Subscribe

```json
{
  "t": "SUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
}
```

### Unsubscribe

```json
{
  "t": "UNSUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
}
```

### Common errors

```json
{
  "t": "error",
  "e": "event-id",
  "o": ["outcome-id"],
  "d": {
    "error": "Error description"
  }
}
```

| Error                   | Cause                         |
| ----------------------- | ----------------------------- |
| No outcome IDs provided | Subscribe without outcome IDs |
| Unknown message type    | Unsupported `t` value         |

### Python example

{% code title="activity\_feed.py" %}

```python
import asyncio, json, websockets

async def activity_feed():
    uri = "wss://history-service.outpoll.com/history/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "t": "SUBSCRIBE",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
            "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
        }))

        async for msg in ws:
            print(json.loads(msg))
```

{% endcode %}


# Last Trades Feed

Public stream of the latest platform trades

Use this stream for the latest public trades across the platform.

### Endpoint

```
wss://history-service.outpoll.com/last-trades/ws
```

### Authentication

No authentication required.

### Subscription

No subscribe message required.

Connect and start reading messages.

### Notes

* This is a public broadcast stream.
* Reconnect and continue consuming on disconnect.
* Payloads represent the latest completed trades.

### Python example

{% code title="last\_trades.py" %}

```python
import asyncio, json, websockets

async def last_trades():
    uri = "wss://history-service.outpoll.com/last-trades/ws"
    async with websockets.connect(uri) as ws:
        async for msg in ws:
            print(json.loads(msg))
```

{% endcode %}


# WebSocket API Reference

Full authentication, message, and stream reference for all WebSocket endpoints

## WebSocket API Reference

### Introduction

The Outpoll WebSocket API provides real-time streaming data for order books, probability charts, order updates, balance changes, and trade feeds.

All WebSocket connections use the `wss://` protocol.

***

### Authentication

Some endpoints are **public** (no authentication required): Order Book, Chart, Activity Feed, Last Trades.

Private endpoints (Order Updates, Balance Updates, Order Feed) require API key authentication.

#### API Key (in-band)

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Signature is computed as:

```
message   = timestamp + "GET" + path
signature = base64url_no_padding(HMAC-SHA256(base64url_decode(api_secret), message))
```

Where `path` is the WebSocket endpoint path (e.g. `/order/ws`, `/balance/ws`). Timestamp drift tolerance is ±30 seconds.

#### Sign with API Key (Python)

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

api_key = "op_k_your_api_key"
api_secret = "your_api_secret"

def ws_auth_message(path: str) -> dict:
    timestamp = str(int(time.time()))
    message = timestamp + "GET" + path
    secret_padded = api_secret + "=" * (4 - len(api_secret) % 4) if len(api_secret) % 4 else api_secret
    secret_bytes = base64.urlsafe_b64decode(secret_padded)
    signature = base64.urlsafe_b64encode(
        hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
    ).decode().rstrip("=")
    return {
        "t": "AUTH",
        "d": {
            "apiKey": api_key,
            "signature": signature,
            "timestamp": timestamp,
        },
    }
```

> All WebSocket examples below use the `websockets` library: `pip install websockets`

***

## Order Book

Real-time price updates for prediction market outcomes. No authentication required.

```
wss://order-book.outpoll.com/event/ws
```

All messages follow a common envelope:

```json
{
  "t": "<TYPE>",
  "e": "<EVENT_ID>",
  "d": [...],
  "r": "<ERROR_MESSAGE>"
}
```

| Field | Type   | Description                      |
| ----- | ------ | -------------------------------- |
| t     | string | Message type                     |
| e     | string | Event ID                         |
| d     | array  | Payload (list of outcome prices) |
| r     | string | Error message (only on errors)   |

**Message Types**

| Type                | Direction | Description               |
| ------------------- | --------- | ------------------------- |
| SUBSCRIBE           | Client    | Subscribe to an event     |
| SUBSCRIBED          | Server    | Subscription confirmed    |
| UNSUBSCRIBE         | Client    | Unsubscribe from an event |
| UNSUBSCRIBED        | Server    | Unsubscription confirmed  |
| REQUEST\_DATA       | Client    | Request current prices    |
| PRICES\_DATA        | Server    | Full price snapshot       |
| PRICES\_DATA\_DELTA | Server    | Incremental price update  |
| PING                | Server    | Heartbeat                 |
| ERROR               | Server    | Error message             |

***

### Subscribe

Subscribe to price updates for an event.

**Client -> Server**

```json
{
  "t": "SUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a"
}
```

**Server -> Client** (confirmation)

```json
{
  "t": "SUBSCRIBED",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a"
}
```

***

### Price Data

After subscribing, the server sends `PRICES_DATA` with the current state, and `PRICES_DATA_DELTA` on each change.

**Server -> Client**

```json
{
  "t": "PRICES_DATA",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "d": [
    {
      "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
      "c": 0.55,
      "b": 0.54,
      "a": 0.56
    },
    {
      "o": "97b06d16-aeff-4dd4-b08b-1ba30a2b8895",
      "c": 0.45,
      "b": 0.44,
      "a": 0.46
    }
  ]
}
```

| Field | Type   | Description               |
| ----- | ------ | ------------------------- |
| o     | string | Outcome ID                |
| c     | number | Chance (last trade price) |
| b     | number | Best bid                  |
| a     | number | Best ask                  |

> `PRICES_DATA_DELTA` has the same format but only includes outcomes whose prices changed.

**Python**

```python
import asyncio, json, websockets

async def order_book():
    uri = "wss://order-book.outpoll.com/event/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "t": "SUBSCRIBE",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
        }))

        async for msg in ws:
            data = json.loads(msg)
            if data["t"] in ("PRICES_DATA", "PRICES_DATA_DELTA"):
                for p in data["d"]:
                    print(f"Outcome {p['o']}: chance={p['c']}, bid={p['b']}, ask={p['a']}")
            elif data["t"] == "PING":
                pass

asyncio.run(order_book())
```

***

## Chart (Chance History)

Real-time probability chart data and historical OHLC-style probability aggregations. No authentication required.

```
wss://chance-history-service.outpoll.com/ws
```

All messages follow a common envelope:

```json
{
  "t": "<TYPE>",
  "e": "<EVENT_ID>",
  "o": ["<OUTCOME_ID_1>", "<OUTCOME_ID_2>"],
  "d": { ... },
  "r": "<ERROR_MESSAGE>"
}
```

| Field | Type      | Description                      |
| ----- | --------- | -------------------------------- |
| t     | string    | Message type                     |
| e     | string    | Event ID (UUID)                  |
| o     | string\[] | Outcome IDs (UUIDs)              |
| d     | object    | Payload (varies by message type) |
| r     | string    | Error message (only on errors)   |

***

### Subscribe

Subscribe to real-time probability updates for specific outcomes.

**Client -> Server**

```json
{
  "t": "SUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "97b06d16-aeff-4dd4-b08b-1ba30a2b8895"
  ]
}
```

| Field | Type      | Required | Description         |
| ----- | --------- | -------- | ------------------- |
| t     | string    | Yes      | `SUBSCRIBE`         |
| e     | string    | Yes      | Event ID            |
| o     | string\[] | Yes      | List of outcome IDs |

**Server -> Client** (confirmation + initial data)

On subscribe, the server sends:

1. A `probability` message with the last known value for each outcome
2. A `success` confirmation

```json
{
  "t": "probability",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": {
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76": {
      "t": 1712160000,
      "p": 0.65
    }
  }
}
```

```json
{
  "t": "success",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": { "message": "subscribe successful" }
}
```

**Python**

```python
import asyncio, json, websockets

async def chart_stream():
    uri = "wss://chance-history-service.outpoll.com/ws"
    async with websockets.connect(uri) as ws:
        # Subscribe
        await ws.send(json.dumps({
            "t": "SUBSCRIBE",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
            "o": [
                "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
                "97b06d16-aeff-4dd4-b08b-1ba30a2b8895",
            ],
        }))

        # Listen for probability updates
        async for msg in ws:
            data = json.loads(msg)
            if data["t"] == "probability":
                print(f"Probability: {data['d']}")
            elif data["t"] == "UPDATE_HISTORY":
                print(f"History point: {data['d']}")

asyncio.run(chart_stream())
```

***

### Unsubscribe

Stop receiving probability updates for specific outcomes.

**Client -> Server**

```json
{
  "t": "UNSUBSCRIBE",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
  ]
}
```

**Server -> Client**

```json
{
  "t": "success",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": { "message": "unsubscribe successful" }
}
```

**Python**

```python
# Inside an active WebSocket connection:
await ws.send(json.dumps({
    "t": "UNSUBSCRIBE",
    "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
}))
```

***

### Probability Update

Pushed in real-time when the probability of a subscribed outcome changes.

**Server -> Client**

```json
{
  "t": "probability",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": {
    "t": 1712160300,
    "p": 0.67
  }
}
```

| Field | Type   | Description                    |
| ----- | ------ | ------------------------------ |
| d.t   | number | Timestamp (Unix epoch seconds) |
| d.p   | number | Probability (0.0–1.0)          |

***

### History Update

Pushed when a new aggregated data point is available for a subscribed outcome.

**Server -> Client**

```json
{
  "t": "UPDATE_HISTORY",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
  "d": {
    "t": 1712160300,
    "p": 0.67
  }
}
```

Same data format as probability updates.

***

### Request Historical Data

Request aggregated historical probability data for chart rendering.

**Client -> Server**

```json
{
  "t": "HISTORY",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "97b06d16-aeff-4dd4-b08b-1ba30a2b8895"
  ],
  "d": {
    "p": "1H",
    "f": "2026-01-01T00:00:00Z",
    "t": "2026-01-01T12:00:00Z"
  }
}
```

| Field | Type      | Required | Description                                             |
| ----- | --------- | -------- | ------------------------------------------------------- |
| t     | string    | Yes      | `HISTORY`                                               |
| e     | string    | Yes      | Event ID                                                |
| o     | string\[] | Yes      | Outcome IDs to fetch                                    |
| d.p   | string    | No       | Aggregation period (see table below). Omit for raw data |
| d.f   | string    | Yes      | Start time (ISO 8601)                                   |
| d.t   | string    | Yes      | End time (ISO 8601)                                     |

#### Aggregation Periods

| Code | Duration   |
| ---- | ---------- |
| 1s   | 1 second   |
| 5s   | 5 seconds  |
| 5M   | 5 minutes  |
| 10M  | 10 minutes |
| 30M  | 30 minutes |
| 1H   | 1 hour     |
| 3H   | 3 hours    |
| 4H   | 4 hours    |
| 6H   | 6 hours    |
| 1d   | 1 day      |
| 7d   | 1 week     |
| 1m   | 1 month    |

> You can also pass a custom number of seconds as a string (e.g. `"300"` for 5-minute buckets).

**Server -> Client**

```json
{
  "t": "HISTORY_RESPONSE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": [
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
    "97b06d16-aeff-4dd4-b08b-1ba30a2b8895"
  ],
  "d": {
    "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76": [
      { "t": 1735689600, "p": 0.50 },
      { "t": 1735693200, "p": 0.52 },
      { "t": 1735696800, "p": 0.55 },
      { "t": 1735700400, "p": 0.53 }
    ],
    "97b06d16-aeff-4dd4-b08b-1ba30a2b8895": [
      { "t": 1735689600, "p": 0.30 },
      { "t": 1735693200, "p": 0.32 },
      { "t": 1735696800, "p": 0.28 }
    ]
  }
}
```

Response data is a map of `outcomeId -> array of data points`:

| Field | Type   | Description                     |
| ----- | ------ | ------------------------------- |
| t     | number | Period end (Unix epoch seconds) |
| p     | number | Close probability (0.0–1.0)     |

> The first point in each array is an **anchor** — the last known probability value at or before the requested `from` time. This allows charts to draw a continuous line from the left edge.

> If no data exists in the requested range, the server returns two points (at `from` and `to`) with the last known probability, creating a flat line.

**Python — Request Historical Data**

```python
import asyncio, json, websockets

async def get_history():
    uri = "wss://chance-history-service.outpoll.com/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "t": "HISTORY",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
            "o": ["b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"],
            "d": {
                "p": "1H",
                "f": "2026-01-01T00:00:00Z",
                "t": "2026-01-01T12:00:00Z",
            },
        }))

        async for msg in ws:
            data = json.loads(msg)
            if data["t"] == "HISTORY_RESPONSE":
                for outcome_id, points in data["d"].items():
                    print(f"Outcome {outcome_id}:")
                    for pt in points:
                        print(f"  {pt['t']} -> {pt['p']}")
                break

asyncio.run(get_history())
```

***

### Chart History REST Endpoint

The same historical data is also available via REST.

```
GET /api/events/{eventId}/outcomes/{outcomeId}/history
```

**Query Parameters**

| Parameter | Type   | Required | Description                         |
| --------- | ------ | -------- | ----------------------------------- |
| period    | string | Yes      | Aggregation period code (e.g. `1H`) |
| from      | string | Yes      | Start time (ISO 8601)               |
| to        | string | Yes      | End time (ISO 8601)                 |

**Example**

```
GET /api/events/54ccea1a-.../outcomes/b21f6fd8-.../history?period=1H&from=2026-01-01T00:00:00Z&to=2026-01-01T12:00:00Z
```

**Response** `200 OK`

```json
[
  { "t": 1735689600, "p": 0.50 },
  { "t": 1735693200, "p": 0.52 },
  { "t": 1735696800, "p": 0.55 }
]
```

**Python**

```python
import requests

event_id = "54ccea1a-16fd-469c-8018-84b375243e8a"
outcome_id = "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"

response = requests.get(
    f"https://chance-history-service.outpoll.com/api/events/{event_id}/outcomes/{outcome_id}/history",
    params={"period": "1H", "from": "2026-01-01T00:00:00Z", "to": "2026-01-01T12:00:00Z"},
)

for point in response.json():
    print(f"{point['t']} -> {point['p']}")
```

***

## Order Updates

Real-time notifications about your order executions and TP/SL status changes.

```
wss://order-service.outpoll.com/order/ws
```

> Requires authentication. Send an `AUTH` message after connecting.

***

### Authenticate

**Option 1 — JWT Token**

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Signature: `base64url_no_padding(HMAC-SHA256(secret, timestamp + "GET" + "/order/ws"))`

**Server -> Client** (success)

```json
{
  "t": "success",
  "d": "authorized",
  "r": null
}
```

**Server -> Client** (failure)

```json
{
  "t": "error",
  "d": {
    "error": "Invalid or missing credentials"
  }
}
```

***

### Order Update

Sent when an order is placed or fails.

**Server -> Client**

```json
{
  "t": "ORDER_UPDATE",
  "d": {
    "i": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "u": "user-id",
    "s": "SUCCESS"
  }
}
```

| Field | Type   | Description                    |
| ----- | ------ | ------------------------------ |
| d.i   | string | Order ID                       |
| d.u   | string | User ID                        |
| d.s   | string | Status — `SUCCESS` or `FAILED` |

***

### TP/SL Update

Sent when a take-profit or stop-loss order changes state.

**Server -> Client**

```json
{
  "t": "TPSL_UPDATE",
  "d": {
    "i": "order-id",
    "u": "user-id",
    "a": "asset-id",
    "q": 100.00,
    "tpp": 0.95,
    "slp": 0.10,
    "s": "ACTIVE"
  }
}
```

| Field | Type   | Description       |
| ----- | ------ | ----------------- |
| d.i   | string | Order ID          |
| d.u   | string | User ID           |
| d.a   | string | Asset ID          |
| d.q   | number | Quantity          |
| d.tpp | number | Take-profit price |
| d.slp | number | Stop-loss price   |
| d.s   | string | Status            |

***

### Server Ping

The server sends a heartbeat every 15 seconds. No response required.

**Server -> Client**

```json
{
  "t": "PING",
  "d": null,
  "r": null
}
```

**Python**

```python
import asyncio, json, websockets

async def order_updates():
    uri = "wss://order-service.outpoll.com/order/ws"
    async with websockets.connect(uri) as ws:
        # Authenticate
        await ws.send(json.dumps(ws_auth_message("/order/ws")))

        async for msg in ws:
            data = json.loads(msg)
            if data["t"] == "success":
                print("Authenticated")
            elif data["t"] == "ORDER_UPDATE":
                print(f"Order {data['d']['i']}: {data['d']['s']}")
            elif data["t"] == "TPSL_UPDATE":
                print(f"TP/SL {data['d']['i']}: {data['d']['s']}")
            elif data["t"] == "PING":
                pass  # heartbeat, ignore

asyncio.run(order_updates())
```

***

## Balance Updates

Real-time balance changes and transaction updates.

```
wss://wallet-mutator-view.outpoll.com/balance/ws
```

> Requires authentication. Send an `AUTH` message after connecting.

***

### Authenticate

**Option 1 — JWT Token**

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Signature: `base64url_no_padding(HMAC-SHA256(secret, timestamp + "GET" + "/balance/ws"))`

After successful authentication, the server sends your current balances wrapped in a success message:

**Server -> Client** (auth success + initial balances)

```json
{
  "t": "success",
  "d": {
    "l": [
      {
        "i": "078dcd98-928d-479f-8110-ff6d27e44de2",
        "b": 1250.50,
        "f": 1200.00
      },
      {
        "i": "e7c4fed5-e7f4-4ec4-9b55-0bb41b83d813",
        "b": 100,
        "f": 100
      }
    ]
  },
  "r": null
}
```

| Field    | Type   | Description              |
| -------- | ------ | ------------------------ |
| t        | string | Message type (`success`) |
| d.l      | array  | List of balances         |
| d.l\[].i | string | Asset ID                 |
| d.l\[].b | number | Balance                  |
| d.l\[].f | number | Free balance             |
| r        | null   | Error (null on success)  |

***

### Balance Update

Sent when any balance changes (trade, deposit, withdrawal).

**Server -> Client**

```json
{
  "u": "user-id",
  "a": "asset-id",
  "aop": 0.50,
  "c": 50.00,
  "b": 1200.50,
  "f": 1150.00
}
```

| Field | Type   | Description        |
| ----- | ------ | ------------------ |
| u     | string | User ID            |
| a     | string | Asset ID           |
| aop   | number | Average open price |
| c     | number | Cost               |
| b     | number | Balance            |
| f     | number | Free balance       |

**Python**

```python
import asyncio, json, websockets

async def balance_updates():
    uri = "wss://wallet-mutator-view.outpoll.com/balance/ws"
    async with websockets.connect(uri) as ws:
        # Authenticate
        await ws.send(json.dumps(ws_auth_message("/balance/ws")))

        async for msg in ws:
            data = json.loads(msg)
            if data.get("t") == "success":
                # Initial balances after auth
                for b in data["d"]["l"]:
                    print(f"Asset {b['i']}: balance={b['b']}, free={b['f']}")
            elif "a" in data:
                # Balance update
                print(f"Update — asset {data['a']}: balance={data['b']}, free={data['f']}")

asyncio.run(balance_updates())
```

***

## Trade & Activity Feed

The history service exposes three separate WebSocket endpoints.

***

### Order Feed

Real-time order status updates for the authenticated user.

```
wss://history-service.outpoll.com/order/ws
```

> Requires authentication. Send an `AUTH` message after connecting.

```json
{
  "t": "AUTH",
  "d": {
    "apiKey": "<API_KEY>",
    "signature": "<SIGNATURE>",
    "timestamp": "<UNIX_EPOCH_SECONDS>"
  }
}
```

Signature: `base64url_no_padding(HMAC-SHA256(secret, timestamp + "GET" + "/order/ws"))`

**Server -> Client** (success)

```json
{
  "t": "SUCCESS",
  "d": {
    "message": "AUTH successful"
  },
  "r": null
}
```

**Python**

```python
import asyncio, json, websockets

async def order_feed():
    uri = "wss://history-service.outpoll.com/order/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps(ws_auth_message("/order/ws")))

        async for msg in ws:
            data = json.loads(msg)
            print(f"Order update: {data}")

asyncio.run(order_feed())
```

***

### Activity Feed

Real-time activity updates (trades, position changes) with subscribe/unsubscribe.

```
wss://history-service.outpoll.com/history/ws
```

All messages follow a common envelope:

```json
{
  "t": "<TYPE>",
  "e": "<EVENT_ID>",
  "o": "<OUTCOME_ID>",
  "d": { ... },
  "r": "<ERROR_MESSAGE>"
}
```

**Client -> Server** (subscribe)

```json
{
  "t": "SUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
}
```

**Client -> Server** (unsubscribe)

```json
{
  "t": "UNSUBSCRIBE",
  "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
  "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"
}
```

**Python**

```python
import asyncio, json, websockets

async def activity_feed():
    uri = "wss://history-service.outpoll.com/history/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "t": "SUBSCRIBE",
            "e": "54ccea1a-16fd-469c-8018-84b375243e8a",
            "o": "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76",
        }))

        async for msg in ws:
            data = json.loads(msg)
            print(f"Activity: {data}")

asyncio.run(activity_feed())
```

***

### Last Trades Feed

Real-time broadcast of the latest trades across the platform. No authentication or subscription required.

```
wss://history-service.outpoll.com/last-trades/ws
```

**Python**

```python
import asyncio, json, websockets

async def last_trades():
    uri = "wss://history-service.outpoll.com/last-trades/ws"
    async with websockets.connect(uri) as ws:
        async for msg in ws:
            data = json.loads(msg)
            print(f"Trade: {data}")

asyncio.run(last_trades())
```

***

## Errors

WebSocket error messages follow this format:

```json
{
  "t": "error",
  "e": "event-id",
  "o": ["outcome-id"],
  "d": {
    "error": "Error description"
  }
}
```

Common errors:

| Error                          | Cause                           |
| ------------------------------ | ------------------------------- |
| Invalid or missing credentials | API key auth failed             |
| No outcome IDs provided        | Subscribe without outcome IDs   |
| Invalid time range             | `from` is after `to`            |
| Invalid period                 | Unrecognized aggregation period |
| Unknown message type           | Unsupported `t` value           |


# Python Examples

End-to-end Python scripts for common Outpoll REST and WebSocket workflows

## Python Examples

Use these scripts to automate common Outpoll workflows.

Each script now lives in its own subsection.

### Start here

Use [Setup & Helpers](/api/rest-api/python-examples/setup-and-helpers) first.

That page covers:

* Python version and package install
* authenticated REST helper
* WebSocket auth helper
* placeholder values you must replace

### Trading workflows

* [Buy a Liquid Event](/api/rest-api/python-examples/buy-a-liquid-event) — buy the most liquid market when the YES price hits a target
* [Set TP/SL on Open Positions](/api/rest-api/python-examples/set-tp-sl-on-open-positions) — apply take-profit and stop-loss orders across all open positions
* [Copy Large Trades](/api/rest-api/python-examples/copy-large-trades) — watch the public trade feed and mirror trades above a chosen threshold

### Monitoring

* [Cross-Platform Price Monitor](/api/rest-api/python-examples/cross-platform-price-monitor) — compare live best bids across Kalshi, Polymarket, and Outpoll

### Related pages

Use these references when adapting the scripts:

* [Public API Overview](/api/rest-api/public-endpoints/public-api-overview)
* [WebSocket API Reference](/api/rest-api/websockets/websocket-api-reference)
* [Orders](/api/rest-api/private-endpoints/orders)
* [Balances](/api/rest-api/private-endpoints/balances)


# Setup & Helpers

Python environment setup and shared REST and WebSocket helpers

Use this page before running any script in this section.

### Requirements

Use Python `3.10+`.

Install the required packages first.

{% code title="install.sh" %}

```bash
pip install requests websockets cryptography
```

{% endcode %}

{% hint style="warning" %}
Replace every placeholder API key, secret, event ID, outcome ID, and asset ID before running a script.
{% endhint %}

### Authenticated REST client

Use this client for authenticated REST requests.

Requests are routed to the correct service by path prefix.

{% code title="outpoll\_client.py" %}

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

SERVICE_MAP = {
    "/api/events":      "https://event-service.outpoll.com",
    "/api/categories":  "https://event-service.outpoll.com",
    "/api/tags":        "https://event-service.outpoll.com",
    "/api/coins":       "https://wallet-mutator-view.outpoll.com",
    "/api/user-balances": "https://wallet-mutator-view.outpoll.com",
    "/api/history":     "https://history-service.outpoll.com",
    "/orders":          "https://order-service.outpoll.com",
    "/auth":            "https://auth-service.outpoll.com",
}

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

    def _host(self, path: str) -> str:
        for prefix, host in SERVICE_MAP.items():
            if path.startswith(prefix):
                return host
        return "https://event-service.outpoll.com"

    def _sign(self, method: str, path: str, body: str = "") -> dict:
        timestamp = str(int(time.time()))
        message = timestamp + method + path + body
        padded = self.api_secret + "=" * (4 - len(self.api_secret) % 4) if len(self.api_secret) % 4 else self.api_secret
        secret_bytes = base64.urlsafe_b64decode(padded)
        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._host(path) + 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._host(path) + path, headers=headers, data=body)

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

{% endcode %}

### WebSocket auth helper

Use this helper for private WebSocket streams.

{% code title="ws\_auth.py" %}

```python
def ws_auth_message(api_key: str, api_secret: str, path: str) -> dict:
    timestamp = str(int(time.time()))
    message = timestamp + "GET" + path
    secret_padded = api_secret + "=" * (4 - len(api_secret) % 4) if len(api_secret) % 4 else api_secret
    secret_bytes = base64.urlsafe_b64decode(secret_padded)
    signature = base64.urlsafe_b64encode(
        hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
    ).decode().rstrip("=")
    return {
        "t": "AUTH",
        "d": {
            "apiKey": api_key,
            "signature": signature,
            "timestamp": timestamp,
        },
    }
```

{% endcode %}

### Related pages

Use these references when adapting the helpers:

* [Public API Overview](/api/rest-api/public-endpoints/public-api-overview)
* [WebSocket API Reference](/api/rest-api/websockets/websocket-api-reference)
* [Orders](/api/rest-api/private-endpoints/orders)
* [Balances](/api/rest-api/private-endpoints/balances)


# Buy a Liquid Event

Monitor a liquid market and place a market buy when the YES price reaches a target

Use this script to buy into a liquid event at a target price.

### What this script does

* finds the most liquid event
* watches the order book in real time
* places a market buy when the YES price reaches `$0.40`
* spends `10%` of available USDC balance

Start with [Setup & Helpers](/api/rest-api/python-examples/setup-and-helpers) if you still need the shared prerequisites.

{% code title="buy\_liquid\_event.py" %}

```python
#!/usr/bin/env python3
"""Buy YES token at $0.40 on the most liquid event, spending 10% of USDC balance."""
import asyncio, json, requests, websockets
import hmac, hashlib, base64, time

API_KEY = "op_k_your_api_key"
API_SECRET = "your_api_secret"
USDC_ID = "078dcd98-928d-479f-8110-ff6d27e44de2"
TARGET_PRICE = 0.40
BALANCE_FRACTION = 0.10

SERVICE_MAP = {
    "/api/events":      "https://event-service.outpoll.com",
    "/api/user-balances": "https://wallet-mutator-view.outpoll.com",
    "/api/history":     "https://history-service.outpoll.com",
    "/orders":          "https://order-service.outpoll.com",
}

class OutpollClient:
    def __init__(self, api_key, api_secret):
        self.api_key = api_key
        self.api_secret = api_secret

    def _host(self, path):
        for prefix, host in SERVICE_MAP.items():
            if path.startswith(prefix):
                return host
        return "https://event-service.outpoll.com"

    def _sign(self, method, path, body=""):
        timestamp = str(int(time.time()))
        message = timestamp + method + path + body
        padded = self.api_secret + "=" * (4 - len(self.api_secret) % 4) if len(self.api_secret) % 4 else self.api_secret
        secret_bytes = base64.urlsafe_b64decode(padded)
        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 post(self, path, data=None):
        body = json.dumps(data) if data else ""
        headers = self._sign("POST", path, body)
        return requests.post(self._host(path) + path, headers=headers, data=body)


def ws_auth_msg(path):
    timestamp = str(int(time.time()))
    message = timestamp + "GET" + path
    s = API_SECRET
    s += "=" * (4 - len(s) % 4) if len(s) % 4 else ""
    secret_bytes = base64.urlsafe_b64decode(s)
    signature = base64.urlsafe_b64encode(
        hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
    ).decode().rstrip("=")
    return {"t": "AUTH", "d": {"apiKey": API_KEY, "signature": signature, "timestamp": timestamp}}


# Step 1 — Find the most liquid event
print("Searching for most liquid event...")
resp = requests.post(
    "https://event-service.outpoll.com/api/events/search",
    params={"page": 0, "size": 1},
    json={"sb": "LIQUIDITY", "sd": "DESC", "l": "en"},
)
event = resp.json()["content"][0]
market = event["ma"][0]
yes_runner = next(r for r in market["r"] if r["ip"])

EVENT_ID = event["i"]
OUTCOME_ID = market["i"]
RUNNER_ID = yes_runner["i"]

print(f"Event:   {event['ti']}")
print(f"YES price: ${yes_runner['p']:.2f}")
print(f"Target:  ${TARGET_PRICE:.2f}")


async def run():
    client = OutpollClient(API_KEY, API_SECRET)

    # Step 2 — Get USDC balance via Balance WebSocket
    print("\nFetching USDC balance...")
    usdc_balance = 0.0
    async with websockets.connect("wss://wallet-mutator-view.outpoll.com/balance/ws") as ws:
        await ws.send(json.dumps(ws_auth_msg("/balance/ws")))
        msg = json.loads(await asyncio.wait_for(ws.recv(), timeout=10))
        if msg.get("t") == "success":
            for b in msg["d"]["l"]:
                if b["i"] == USDC_ID:
                    usdc_balance = b["f"]
                    break
    spend = round(usdc_balance * BALANCE_FRACTION, 2)
    print(f"USDC free: ${usdc_balance:.2f}")
    print(f"Will spend: ${spend:.2f} (10%)")

    if spend < 1:
        print("Insufficient balance. Exiting.")
        return

    # Step 3 — Monitor order book, wait for YES ≤ $0.40
    print(f"\nWatching order book for YES ≤ ${TARGET_PRICE}...")
    async with websockets.connect("wss://order-book.outpoll.com/event/ws") as ws:
        await ws.send(json.dumps({"t": "SUBSCRIBE", "e": EVENT_ID}))

        async for raw in ws:
            data = json.loads(raw)
            if data["t"] not in ("PRICES_DATA", "PRICES_DATA_DELTA"):
                continue

            for p in data["d"]:
                if p["o"] != RUNNER_ID:
                    continue

                chance = p["c"]
                print(f"  YES: ${chance:.4f} (bid={p['b']:.4f}, ask={p['a']:.4f})", end="\r")

                if chance <= TARGET_PRICE:
                    # Step 4 — Market buy
                    print(f"\n\nPrice hit ${chance:.4f} ≤ ${TARGET_PRICE}! Buying...")
                    order = client.post("/orders/market", {
                        "e": EVENT_ID,
                        "o": OUTCOME_ID,
                        "ba": RUNNER_ID,
                        "qa": USDC_ID,
                        "s": "BUY",
                        "am": spend,
                    })
                    print(f"Order placed: {order.json()}")
                    return


asyncio.run(run())
```

{% endcode %}

### Related pages

* [Order Book](/api/rest-api/websockets/order-book)
* [Balances](/api/rest-api/private-endpoints/balances)
* [Place Market Order](/api/rest-api/private-endpoints/orders/place-market-order)


# Set TP/SL on Open Positions

Fetch open positions and submit one take-profit and stop-loss order per position

Use this script to protect every open position with one TP/SL order.

### What this script does

* loads all open positions
* calculates take-profit at `+10%`
* calculates stop-loss at `-10%`
* submits one TP/SL order per position

Start with [Setup & Helpers](/api/rest-api/python-examples/setup-and-helpers) if you still need the shared prerequisites.

{% code title="set\_tpsl\_all\_positions.py" %}

```python
#!/usr/bin/env python3
"""Set TP/SL at ±10% of current price on every open position."""
import json, requests
import hmac, hashlib, base64, time

API_KEY = "op_k_your_api_key"
API_SECRET = "your_api_secret"
USDC_ID = "078dcd98-928d-479f-8110-ff6d27e44de2"
TP_OFFSET = 0.10   # +10%
SL_OFFSET = 0.10   # -10%

SERVICE_MAP = {
    "/api/events":      "https://event-service.outpoll.com",
    "/api/user-balances": "https://wallet-mutator-view.outpoll.com",
    "/api/history":     "https://history-service.outpoll.com",
    "/orders":          "https://order-service.outpoll.com",
}

class OutpollClient:
    def __init__(self, api_key, api_secret):
        self.api_key = api_key
        self.api_secret = api_secret

    def _host(self, path):
        for prefix, host in SERVICE_MAP.items():
            if path.startswith(prefix):
                return host
        return "https://event-service.outpoll.com"

    def _sign(self, method, path, body=""):
        timestamp = str(int(time.time()))
        message = timestamp + method + path + body
        padded = self.api_secret + "=" * (4 - len(self.api_secret) % 4) if len(self.api_secret) % 4 else self.api_secret
        secret_bytes = base64.urlsafe_b64decode(padded)
        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, params=None):
        headers = self._sign("GET", path)
        return requests.get(self._host(path) + path, headers=headers, params=params)

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


client = OutpollClient(API_KEY, API_SECRET)

# Step 1 — Get all open positions
print("Fetching open positions...")
positions = client.get("/api/history/deals/open-positions", params={"page": 0, "size": 100}).json()

if not positions["i"]:
    print("No open positions.")
    exit()

print(f"Found {len(positions['i'])} position(s)\n")

# Step 2 — For each position, set TP/SL
for pos in positions["i"]:
    current_price = pos["cp"]
    tp_price = min(round(current_price * (1 + TP_OFFSET), 2), 0.99)
    sl_price = max(round(current_price * (1 - SL_OFFSET), 2), 0.01)

    print(f"{pos['et']} — {pos['on']}")
    print(f"  Current: ${current_price:.2f}  |  TP: ${tp_price:.2f}  |  SL: ${sl_price:.2f}")
    print(f"  Shares: {pos['asc']}")

    resp = client.post("/orders/tpsl", {
        "e": pos["ei"],
        "o": pos["eoi"],
        "ba": pos["ai"],
        "qa": USDC_ID,
        "i": "YES" if pos["ip"] else "NO",
        "s": "SELL",
        "q": pos["asc"],
        "t": tp_price,
        "l": sl_price,
    })

    if resp.status_code in (200, 202):
        print(f"  TP/SL set\n")
    else:
        print(f"  Error {resp.status_code}: {resp.text[:100]}\n")

print("Done.")
```

{% endcode %}

### Related pages

* [Get TP/SL Orders](/api/rest-api/private-endpoints/orders/get-tp-sl-orders)
* [Set Take Profit / Stop Loss](/api/rest-api/private-endpoints/orders/take-profit-stop-loss)
* [Positions](/api/rest-api/private-endpoints/positions)


# Copy Large Trades

Listen to the public trade feed and copy trades above a chosen value threshold

Use this script to mirror larger trades from the public feed.

### What this script does

* listens to the public last trades feed
* filters trades above `$1,000`
* resolves the matching runner
* copies each qualifying trade with `5%` of free USDC balance

Start with [Setup & Helpers](/api/rest-api/python-examples/setup-and-helpers) if you still need the shared prerequisites.

{% code title="copy\_large\_trades.py" %}

```python
#!/usr/bin/env python3
"""Copy trades > $1000 from the public feed, each trade = 5% of initial balance."""
import asyncio, json, requests, websockets
import hmac, hashlib, base64, time

API_KEY = "op_k_your_api_key"
API_SECRET = "your_api_secret"
USDC_ID = "078dcd98-928d-479f-8110-ff6d27e44de2"
MIN_TRADE_VALUE = 1000   # only copy trades > $1000
TRADE_FRACTION = 0.05    # 5% of balance per trade

SERVICE_MAP = {
    "/api/events":      "https://event-service.outpoll.com",
    "/api/user-balances": "https://wallet-mutator-view.outpoll.com",
    "/api/history":     "https://history-service.outpoll.com",
    "/orders":          "https://order-service.outpoll.com",
}

class OutpollClient:
    def __init__(self, api_key, api_secret):
        self.api_key = api_key
        self.api_secret = api_secret

    def _host(self, path):
        for prefix, host in SERVICE_MAP.items():
            if path.startswith(prefix):
                return host
        return "https://event-service.outpoll.com"

    def _sign(self, method, path, body=""):
        timestamp = str(int(time.time()))
        message = timestamp + method + path + body
        padded = self.api_secret + "=" * (4 - len(self.api_secret) % 4) if len(self.api_secret) % 4 else self.api_secret
        secret_bytes = base64.urlsafe_b64decode(padded)
        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 post(self, path, data=None):
        body = json.dumps(data) if data else ""
        headers = self._sign("POST", path, body)
        return requests.post(self._host(path) + path, headers=headers, data=body)


def ws_auth_msg(path):
    timestamp = str(int(time.time()))
    message = timestamp + "GET" + path
    s = API_SECRET
    s += "=" * (4 - len(s) % 4) if len(s) % 4 else ""
    secret_bytes = base64.urlsafe_b64decode(s)
    signature = base64.urlsafe_b64encode(
        hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
    ).decode().rstrip("=")
    return {"t": "AUTH", "d": {"apiKey": API_KEY, "signature": signature, "timestamp": timestamp}}


async def run():
    client = OutpollClient(API_KEY, API_SECRET)

    # Step 1 — Get USDC balance
    print("Fetching USDC balance...")
    async with websockets.connect("wss://wallet-mutator-view.outpoll.com/balance/ws") as ws:
        await ws.send(json.dumps(ws_auth_msg("/balance/ws")))
        msg = json.loads(await asyncio.wait_for(ws.recv(), timeout=10))
        usdc_balance = 0.0
        if msg.get("t") == "success":
            for b in msg["d"]["l"]:
                if b["i"] == USDC_ID:
                    usdc_balance = b["f"]
                    break

    spend_per_trade = round(usdc_balance * TRADE_FRACTION, 2)
    print(f"USDC free: ${usdc_balance:.2f}")
    print(f"Per trade: ${spend_per_trade:.2f} (5%)")

    if spend_per_trade < 1:
        print("Insufficient balance. Exiting.")
        return

    # Step 2 — We need runner info per event. Cache event data.
    event_cache = {}

    def get_event_info(event_id):
        if event_id not in event_cache:
            resp = requests.post(
                "https://event-service.outpoll.com/api/events/search",
                params={"page": 0, "size": 50},
                json={"sb": "VOLUME_24H", "sd": "DESC", "l": "en"},
            )
            for ev in resp.json()["content"]:
                event_cache[ev["i"]] = ev
        return event_cache.get(event_id)

    # Step 3 — Watch last trades, copy big ones
    print(f"\nWatching last trades (min ${MIN_TRADE_VALUE})...\n")
    trade_count = 0
    async with websockets.connect("wss://history-service.outpoll.com/last-trades/ws") as ws:
        async for raw in ws:
            data = json.loads(raw)

            # Skip heartbeats
            if data.get("t") == "PING":
                continue

            # Trade value = quantity × price
            qty = data.get("q", 0)
            price = data.get("p", 0)
            value = qty * price

            if value < MIN_TRADE_VALUE:
                continue

            side = data.get("s", "BUY")
            event_id = data.get("e", "")
            outcome_id = data.get("o", "")

            print(f"Whale trade: {side} {qty} @ ${price:.2f} = ${value:.2f}")

            # Look up event to find runner IDs
            event_info = get_event_info(event_id)
            if not event_info:
                print(f"  Event {event_id[:8]}... not found, skipping\n")
                continue

            # Find the matching market and runner
            runner_id = None
            for mkt in event_info.get("ma", []):
                for runner in mkt["r"]:
                    if runner["o"] == outcome_id or mkt["i"] == outcome_id:
                        if runner["ip"]:
                            runner_id = runner["i"]
                            break

            if not runner_id:
                print(f"  Runner not found, skipping\n")
                continue

            # Copy the trade
            order = client.post("/orders/market", {
                "e": event_id,
                "o": outcome_id,
                "ba": runner_id,
                "qa": USDC_ID,
                "s": side,
                "am": spend_per_trade,
            })
            trade_count += 1
            print(f"  Copied #{trade_count}: {order.json()}\n")


asyncio.run(run())
```

{% endcode %}

### Related pages

* [Last Trades Feed](/api/rest-api/websockets/account-and-history-streams/last-trades-feed)
* [Place Market Order](/api/rest-api/private-endpoints/orders/place-market-order)
* [Search Events](/api/rest-api/public-endpoints/search-events)


# Cross-Platform Price Monitor

Compare live best bids across Kalshi, Polymarket, and Outpoll

Use this script to compare market pricing across multiple platforms.

### What this script does

* connects to Kalshi, Polymarket, and Outpoll
* subscribes to live order book data
* shows the current best bid in one terminal view

Start with [Setup & Helpers](/api/rest-api/python-examples/setup-and-helpers) if you still need the shared prerequisites.

{% code title="cross\_platform\_price\_monitor.py" %}

```python
#!/usr/bin/env python3
"""Compare best bids across Kalshi, Polymarket, and Outpoll in real time."""
import asyncio, json, time
import websockets
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
import base64

# ─── Configuration ───────────────────────────────────────────────────
# Kalshi (requires RSA key pair — see https://docs.kalshi.com)
KALSHI_KEY_ID = "your_kalshi_api_key_id"
KALSHI_PRIVATE_KEY_PATH = "path/to/kalshi_private_key.pem"
KALSHI_MARKET_TICKER = "KXBTC-25DEC31-T100000"  # example: BTC $100k by end 2025
KALSHI_WS = "wss://api.elections.kalshi.com/trade-api/ws/v2"

# Polymarket (no auth for market data)
POLYMARKET_ASSET_ID = "12345..."  # YES token asset_id from gamma-api.polymarket.com/markets
POLYMARKET_WS = "wss://ws-subscriptions-clob.polymarket.com/ws/market"

# Outpoll (no auth for order book)
OUTPOLL_EVENT_ID = "54ccea1a-16fd-469c-8018-84b375243e8a"
OUTPOLL_OUTCOME_ID = "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"  # YES runner
OUTPOLL_WS = "wss://order-book.outpoll.com/event/ws"

# ─── Shared state ────────────────────────────────────────────────────
prices = {
    "Kalshi": None,
    "Polymarket": None,
    "Outpoll": None,
}


def display():
    """Print the current best bids."""
    print("\033[H\033[J", end="")  # clear terminal
    print("═══ Cross-Platform Best Bid ═══\n")
    for platform, bid in prices.items():
        if bid is not None:
            print(f"  {platform:12s} Best Bid: ${bid:.2f}")
        else:
            print(f"  {platform:12s} Best Bid: waiting...")
    print(f"\n  Updated: {time.strftime('%H:%M:%S')}")


# ─── Kalshi ──────────────────────────────────────────────────────────
async def watch_kalshi():
    with open(KALSHI_PRIVATE_KEY_PATH, "rb") as f:
        private_key = serialization.load_pem_private_key(f.read(), password=None)

    # Sign handshake
    ts = str(int(time.time() * 1000))
    msg_to_sign = ts + "GET" + "/trade-api/ws/v2"
    sig = private_key.sign(
        msg_to_sign.encode(),
        padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH),
        hashes.SHA256(),
    )
    headers = {
        "KALSHI-ACCESS-KEY": KALSHI_KEY_ID,
        "KALSHI-ACCESS-SIGNATURE": base64.b64encode(sig).decode(),
        "KALSHI-ACCESS-TIMESTAMP": ts,
    }

    async with websockets.connect(KALSHI_WS, additional_headers=headers) as ws:
        # Subscribe to orderbook
        await ws.send(json.dumps({
            "id": 1,
            "cmd": "subscribe",
            "params": {"channels": ["orderbook_delta"], "market_ticker": KALSHI_MARKET_TICKER},
        }))

        yes_book = {}  # price_str -> size

        async for raw in ws:
            data = json.loads(raw)
            msg_type = data.get("type")

            if msg_type == "orderbook_snapshot":
                yes_book = {p: float(s) for p, s in data["msg"].get("yes_dollars_fp", [])}
            elif msg_type == "orderbook_delta" and data["msg"]["side"] == "yes":
                p = data["msg"]["price_dollars"]
                delta = float(data["msg"]["delta_fp"])
                yes_book[p] = yes_book.get(p, 0) + delta
                if yes_book[p] <= 0:
                    yes_book.pop(p, None)

            if yes_book:
                prices["Kalshi"] = float(max(yes_book, key=float))
                display()


# ─── Polymarket ──────────────────────────────────────────────────────
async def watch_polymarket():
    async with websockets.connect(POLYMARKET_WS) as ws:
        await ws.send(json.dumps({
            "assets_ids": [POLYMARKET_ASSET_ID],
            "type": "market",
            "custom_feature_enabled": True,
        }))

        # Heartbeat
        async def heartbeat():
            while True:
                await asyncio.sleep(10)
                try:
                    await ws.send("PING")
                except Exception:
                    break

        hb = asyncio.create_task(heartbeat())

        try:
            async for raw in ws:
                if raw == "PONG":
                    continue
                events = json.loads(raw)
                if not isinstance(events, list):
                    events = [events]

                for event in events:
                    if event.get("event_type") == "book":
                        bids = event.get("bids", [])
                        if bids:
                            prices["Polymarket"] = float(max(bids, key=lambda x: float(x["price"]))["price"])
                            display()

                    elif event.get("event_type") == "price_change":
                        for pc in event.get("price_changes", []):
                            if pc.get("best_bid"):
                                prices["Polymarket"] = float(pc["best_bid"])
                                display()
        finally:
            hb.cancel()


# ─── Outpoll ─────────────────────────────────────────────────────────
async def watch_outpoll():
    async with websockets.connect(OUTPOLL_WS) as ws:
        await ws.send(json.dumps({"t": "SUBSCRIBE", "e": OUTPOLL_EVENT_ID}))

        async for raw in ws:
            data = json.loads(raw)
            if data["t"] not in ("PRICES_DATA", "PRICES_DATA_DELTA"):
                continue
            for p in data["d"]:
                if p["o"] == OUTPOLL_OUTCOME_ID:
                    prices["Outpoll"] = p["b"]
                    display()


# ─── Main ────────────────────────────────────────────────────────────
async def main():
    display()
    await asyncio.gather(
        watch_kalshi(),
        watch_polymarket(),
        watch_outpoll(),
    )

asyncio.run(main())
```

{% endcode %}

### Example output

{% code title="terminal.txt" %}

```
═══ Cross-Platform Best Bid ═══

  Kalshi       Best Bid: $0.44
  Polymarket   Best Bid: $0.43
  Outpoll      Best Bid: $0.44

  Updated: 14:32:05
```

{% endcode %}

{% hint style="info" %}
For a meaningful cross-platform comparison, Kalshi and Polymarket IDs must point to the same underlying event. Use the [Kalshi API](https://docs.kalshi.com) and [Polymarket CLOB API](https://docs.polymarket.com) to resolve matching market identifiers.
{% endhint %}

### Related pages

* [Order Book](/api/rest-api/websockets/order-book)
* [WebSocket API Reference](/api/rest-api/websockets/websocket-api-reference)


# Media Kit

On this page, you will find Outpoll logos in various styles

Logo with background

<figure><img src="/files/6m6cxo97wyUqw1uWWYKi" alt="Outpoll logo light background" width="375"><figcaption><p>Outpoll logo light background</p></figcaption></figure>

<figure><img src="/files/A1YgczVVZVmmWch2ohWw" alt="Outpoll logo dark background" width="375"><figcaption><p>Outpoll logo dark background</p></figcaption></figure>

***

PNG transparent logos (for light & dark themes)

<figure><img src="/files/XaK3F0W9uQhJwiyVezKZ" alt="Outpoll transparent png logo dark background" width="375"><figcaption><p>Outpoll logo for dark background</p></figcaption></figure>

<figure><img src="/files/QPS3X6sN7Ni4p2g3ZhpJ" alt="Outpoll transparent png logo dark background" width="375"><figcaption><p>Outpoll logo for light background</p></figcaption></figure>

***

SVG Format

<figure><img src="/files/ZDlJvp7Ynq4MUUtqwfyD" alt="Outpoll logo svg"><figcaption><p>Outpoll Logo SVG format</p></figcaption></figure>

***

Usage Notes:

* Use SVG for web and UI
* Use PNG for presentations and emails
* Always maintain clear space around the logo
* Primary blue: #005AE0
* Secondary blue: #0B71FF

\
Download all logo versions in a ZIP file:

{% file src="/files/JOWnE4KNKaN7llM37VAu" %}


# Terms of service

## **Terms of Service**

### **1. Introduction**

These **Terms of Use** ("**Terms**") govern your access and use of **Outpoll.com** ("**Site**"), operated by Outpoll Service LTD ("**we**," "**us**," or "**our**"). By accessing or using the Site, you agree to comply with these Terms, our **Privacy Policy**, and any other policies referenced herein.

**IF YOU DO NOT AGREE TO THESE TERMS, DO NOT USE THE SITE.**

### **2. Eligibility & Restrictions**

#### **2.1. Age Requirement**

You must be **at least 18 years old** to use the Site. If you are accessing the Site on behalf of an entity, you represent that you have the authority to bind that entity.

#### **2.2. Prohibited Jurisdictions**

You may **not** use the Site if you are located in, a resident of, or subject to the laws of:

* **United States**
* **United Kingdom**
* **European Union**
* **Singapore**
* **Belarus, Ukraine, Russia, Israel, Iran, Syria**
* **Other restricted regions** (as updated periodically)

**Using VPNs or other methods to bypass these restrictions is strictly prohibited.**

### **3. Description of Services**

Outpoll is a **polling and survey platform** that allows users to:

* Create and participate in polls
* Analyze survey results
* Engage with community-driven insights

We do **not** guarantee the accuracy, completeness, or reliability of any poll results.

### **4. User Responsibilities**

#### **4.1. Account Security**

* You are responsible for maintaining the confidentiality of your account credentials.
* You must **not** share your account with others.
* You agree to notify us immediately of any unauthorized access.

#### **4.2. Prohibited Conduct**

You agree **not** to:

* Violate any applicable laws.
* Post false, misleading, or harmful content.
* Use bots, scrapers, or automated tools to interact with the Site.
* Engage in spamming, phishing, or fraudulent activities.
* Attempt to disrupt or hack the Site.

### **5. Intellectual Property**

* All content, logos, and software on the Site are owned by us or our licensors.
* You may **not** copy, modify, or redistribute any part of the Site without permission.

### **6. Disclaimers & Limitation of Liability**

#### **6.1. No Warranties**

The Site is provided **"as is"** without warranties of any kind. We do **not** guarantee:

* Uninterrupted or error-free operation.
* Accuracy of poll results or third-party content.

#### **6.2. Limitation of Liability**

To the fullest extent permitted by law, we **will not** be liable for:

* Indirect, incidental, or consequential damages.
* Loss of data, profits, or business opportunities.
* Any harm resulting from your use of the Site.

### **7. Indemnification**

You agree to **indemnify** and hold us harmless from any claims, damages, or losses arising from:

* Your breach of these Terms.
* Your misuse of the Site.

### **8. Dispute Resolution**

#### **8.1. Arbitration**

Any disputes will be resolved through **binding arbitration** (not in court), with proceedings held in **UAE**.

#### **8.2. Class Action Waiver**

You **waive** the right to participate in class actions or representative lawsuits.

### **9. Modifications to Terms**

We may update these Terms at any time. Continued use of the Site after changes constitutes acceptance.

### **10. Contact Us**

For questions, contact:\
✉ **<support@outpoll.com>**

***

*By using Outpoll, you acknowledge that you have read, understood, and agreed to these Terms.*


# Privacy policy

## **Privacy Policy**

At **Outpoll**, accessible from [**https://outpoll.com**](https://outpoll.com), we prioritize the privacy of our users. This Privacy Policy outlines the types of information we collect, how we use it, and your rights regarding your personal data.

If you have questions about this policy, please contact us at **<support@outpoll.com>**.

#### **Scope of This Policy**

This Privacy Policy applies only to our online activities and governs information collected through our website. It does not cover data collected offline or via third-party platforms.

### **1. Consent**

By using **Outpoll**, you consent to this Privacy Policy and agree to its terms.

### **2. Information We Collect**

#### **Account Registration**

When you create an account, we may collect:

* Name&#x20;
* Email address
* Password
* Date of birth (if required)

#### **User Communications**

If you contact us (e.g., for support or inquiries), we may collect:

* Email address

#### **Surveys & Feedback**

If you participate in surveys, we may request:

* Demographic information
* Preferences & opinions

#### **Public Content**

Any information you post on public forums, blogs, or social media linked to Outpoll is considered **public** and not protected under this policy.

#### **Automatic Data Collection**

We may collect:

* IP address
* Browser type & device information
* Cookies & tracking technologies (see our **Cookie Policy**)
* Usage data (pages visited, time spent, interactions)

### **3. How We Use Your Information**

We use collected data to:

* Provide, maintain, and improve our services
* Personalize your experience
* Develop new features & services
* Communicate with you (support, updates, promotions)
* Analyze usage trends & optimize performance
* Prevent fraud & enhance security
* Deliver targeted advertising (where applicable)
* Comply with legal obligations

### **4. Data Sharing & Third Parties**

#### **Service Providers**

We may share data with trusted third parties for:

* Analytics (e.g., **Google Analytics**)
* Payment processing
* Marketing & advertising
* Customer support

#### **Legal Compliance**

We may disclose information if required by law (e.g., court orders, fraud investigations).

#### **Business Transfers**

If Outpoll undergoes a merger or acquisition, user data may be transferred as part of the transaction.

### **5. Cookies & Tracking Technologies**

We use **cookies** and similar technologies (e.g., pixels, web beacons) to enhance functionality. For details, see our **Cookie Policy**.

### **6. Data Security**

We implement industry-standard security measures to protect your data. However, no online transmission is 100% secure—we cannot guarantee absolute security.

### **7. Children’s Privacy**

Outpoll does not knowingly collect data from **children under 18**. If you believe a child has provided us with personal information, contact us immediately.

### **8. Changes to This Policy**

We may update this Privacy Policy periodically. Continued use of Outpoll after changes take effect constitutes acceptance of the revised policy.

### **9. Contact Us**

For questions or requests regarding your privacy, email us at:\
✉ **<support@outpoll.com>**

***


# Cookie Policy

This is the Cookie Policy for the Outpoll website, accessible from https\://outpoll.com

## Cookie Policy

### **1. What Are Cookies?**

1.1. Like most professional websites, Outpoll uses **cookies**—small text files stored on your device—to enhance your browsing experience. This policy explains what cookies we use, why we use them, and how you can manage them. Disabling cookies may affect certain website functionalities.

1.2. For general information about cookies, visit [**Wikipedia’s HTTP Cookie page**](https://en.wikipedia.org/wiki/HTTP_cookie).

### **2. How We Use Cookies**

2.1. We use cookies for various purposes, as detailed below. Since many website features rely on cookies, disabling them may limit your experience. We recommend keeping cookies enabled unless you are certain they are unnecessary.

### **3. Disabling Cookies**

3.1. You can block cookies by adjusting your browser settings (check your browser’s Help section for instructions). However, doing so may impair functionality on Outpoll and other websites. We advise against disabling cookies unless absolutely necessary.

### **4. Cookies We Set**

#### **Account-Related Cookies**

If you create an account, we use cookies to manage the signup process and user preferences. These cookies are usually deleted when you log out but may persist to remember your settings.

#### **Login-Related Cookies**

We use cookies to recognize logged-in users, so you don’t have to sign in repeatedly. These are cleared when you log out to protect restricted areas.

#### **Email & Newsletter Cookies**

If you subscribe to our emails, cookies help track your subscription status and display relevant notifications.

#### **Survey & Feedback Cookies**

When you participate in surveys, cookies may track your responses to avoid duplicate entries and ensure accurate results.

#### **Form Submission Cookies**

If you submit data (e.g., contact forms), cookies may store your details for future correspondence.

#### **Site Preference Cookies**

We use cookies to remember your settings (e.g., language, layout) for a personalized experience.

### **5. Third-Party Cookies**

5.1. We may use trusted third-party services that set their own cookies:

#### **Google Analytics**

We use **Google Analytics** to understand how visitors interact with our site, helping us improve content and usability. These cookies track metrics like visit duration and page views. Learn more at the [**Google Analytics page**](https://analytics.google.com/).

#### **Advertising & Affiliate Cookies**

* **Behavioral Advertising:** Cookies may track interests anonymously to show relevant ads.
* **Affiliate Tracking:** If you visit via a partner link, cookies help us credit referrals appropriately.

#### **Performance & Testing Cookies**

We occasionally test new features, using cookies to ensure a consistent experience and gather feedback on improvements.

### **6. More Information**

6.1. If you’re unsure about cookie settings, we recommend keeping them enabled for full functionality.

For further details on cookies, visit:\
🔗 [Wikipedia’s HTTP Cookie Guide](https://en.wikipedia.org/wiki/HTTP_cookie)

If you have additional questions, contact us at:\
✉ **<support@outpoll.com>**


