API Documentation

QuantumSignals API Documentation

Welcome to the QuantumSignals API documentation. This documentation covers all public APIs for the QuantumSignals trading signal platform.

Getting Started

New to QuantumSignals? Start here:
Python Client
Official Python client library for the easiest integration.
Python Client Guide
Quickstart Guide
Get up and running in 5 minutes with our quickstart guide.
Get started
Authentication
Learn how to authenticate with the API using API keys.
Authentication guide
API Reference
Complete API endpoint documentation with examples.
API Reference

Services

QuantumSignals is composed of four microservices, each providing specific functionality:
Streaming Service
Real-time trading signal delivery via Server-Sent Events (SSE). Signals are continuously broadcast as they become available, allowing clients to maintain a persistent connection and receive updates in real-time.

Key Features: - Real-time signal delivery using SSE - Automatic reconnection support - Low latency signal propagation - Authentication via API key
API Reference
Inference Service
Model catalog and prediction serving. Browse available models and their performance metrics.

Key Features: - Model catalog with metadata - Version management - Performance metrics
API Reference
Key Server
API key management and authentication. Create, list, and delete API keys for accessing protected endpoints.

Key Features: - OIDC-based user authentication - API key CRUD operations - TTL support for temporary keys
API Reference
Backtest Service
Historical strategy backtesting results. Query and download backtest results in multiple formats.

Key Features: - Backtest metadata retrieval - Results in JSON, CSV, or Parquet - Accuracy metrics and analysis
API Reference

API Versions

Version
v1
Status
Current
Description
Stable production API

Need Help?

What's Next?

  1. Quickstart Guide - Get your API key and make your first request
  2. Authentication - Understand authentication and security
  3. Complete API Reference - Explore all endpoints in detail
On this page

Executive Summary

Quickstart Guide

Get started with the QuantumSignals API in 5 minutes using the official Python client.

Prerequisites

  • Python 3.10+
  • Access to the QS1 workspace for client installation

Installation

First, install the SignalQ Python client:
uv sync --package signalq_client
  

Step 1: Authenticate

The easiest way to get started is using the interactive authentication flow:
from signalq.client import Client

# Create client and authenticate (opens browser for OIDC)
client = Client()
client.login()

print("Successfully authenticated!")
  
The login() method will: 1. Open your browser for OIDC authentication 2. Automatically create or retrieve an API key 3. Configure the client for immediate use
Alternative: If you already have an API key, you can skip the login flow:
client = Client(api_key="your-existing-api-key")
  

Step 2: Fetch Available Models

Get the catalog of available trading models:
# Get model catalog
models = client.get_model_catalog()

# Display available models
for model_id, model in models.items():
    print(f"Model: {model_id}")
    print(f"  Symbols: {', '.join(model.trained_on_symbols)}")
    print(f"  Training period: {model.trained_date_range[0]} to {model.trained_date_range[1]}")
  

Step 3: Stream Trading Signals

Stream real-time trading signals using a simple iterator pattern:
# Stream signals (runs indefinitely)
for signal in client.stream_signals():
    print(f"{signal.symbol}: {signal.signal}")  # -1 (sell), 0 (hold), 1 (buy)

    # Process signal
    if signal.signal == 1:
        print(f"  → BUY signal for {signal.symbol}")
    elif signal.signal == -1:
        print(f"  → SELL signal for {signal.symbol}")
  
The client handles all the complexity of Server-Sent Events (SSE) parsing and connection management.

Step 4: Fetch Backtest Results

Access historical backtest results for model evaluation:
# Get most recent backtest for a specific model/symbol
backtest = client.get_most_recent_backtest(
    model_hash="model_hash_here",
    symbol="AAPL"
)

backtest_id = backtest["id"]
print(f"Latest backtest ID: {backtest_id}")

# Get detailed results
results = client.get_backtest_results(
    backtest_id=backtest_id,
    format="json",
    page=1,
    page_size=100
)

# Display results
for row in results['data']:
    print(f"Time: {row['time']}, Position: {row['position']}, PnL: {row['pnl']:.2f}")

# Get accuracy metrics
accuracy = client.get_backtest_accuracy(
    backtest_id=backtest_id,
    format="json"
)
print(f"Model accuracy: {accuracy}")
  

Complete Example

Here's a complete, production-ready example:
import os
from signalq.client import Client, APIError

# Get API key from environment
api_key = os.getenv("SIGNALQ_API_KEY")

# For development, use interactive login
# client = Client()
# client.login()

# For production, use API key from environment
client = Client(api_key=api_key)

try:
    # 1. Get available models
    print("Available models:")
    models = client.get_model_catalog()
    for model_id, model in models.items():
        print(f"  - {model_id}: {', '.join(model.trained_on_symbols)}")

    # 2. Stream and process signals
    print("\nStreaming signals (Ctrl+C to stop)...")
    for signal in client.stream_signals():
        print(f"{signal.time} | {signal.symbol}: {signal.signal}")

        # Execute trading logic based on signal
        if signal.signal == 1:
            print(f"  → Executing BUY for {signal.symbol}")
            # execute_buy(signal.symbol)
        elif signal.signal == -1:
            print(f"  → Executing SELL for {signal.symbol}")
            # execute_sell(signal.symbol)

except APIError as e:
    print(f"API error: {e}")
except KeyboardInterrupt:
    print("\nStopped by user")
finally:
    client.close()
  
Best Practice: Use a context manager to ensure proper cleanup:
import os
from signalq.client import Client

with Client(api_key=os.getenv("SIGNALQ_API_KEY")) as client:
    models = client.get_model_catalog()
    print(f"Found {len(models)} models")

    for signal in client.stream_signals():
        process_signal(signal)
# Client automatically closes here
  

Next Steps

Advanced: Direct HTTP Integration

For users who cannot use the Python client (e.g., integrating from other languages or custom tooling), here are examples using raw HTTP calls with httpx.
Creating an API Key (httpx)
import httpx

OIDC_TOKEN = "your-oidc-token-here"
BASE_URL = "https://api.quantumsignals.com"

async def create_api_key():
    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"{BASE_URL}/v1/keys",
            headers={"Authorization": f"Bearer {OIDC_TOKEN}"},
            json={"name": "My API Key"},
        )
        response.raise_for_status()
        data = response.json()
        return data["key"]

# Save this key securely!
api_key = await create_api_key()
  
Getting Models (httpx)
import httpx

API_KEY = "your-api-key-here"
BASE_URL = "https://api.quantumsignals.com"

async def get_models():
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{BASE_URL}/v1/models",
            headers={"x-api-key": API_KEY},
        )
        response.raise_for_status()
        return response.json()

models = await get_models()
  
Streaming Signals (httpx)
import httpx
import json

async def stream_signals():
    async with httpx.AsyncClient() as client:
        async with client.stream(
            "GET",
            f"{BASE_URL}/v1/signals",
            headers={"x-api-key": API_KEY},
            timeout=None,  # No timeout for streaming
        ) as response:
            response.raise_for_status()

            async for line in response.aiter_lines():
                if line.startswith("data:"):
                    signal_data = line[5:].strip()
                    signal = json.loads(signal_data)
                    print(f"Signal: {signal}")

await stream_signals()
  
Getting Backtest Results (httpx)
import httpx

async def get_backtest_results(backtest_id: str):
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{BASE_URL}/v1/backtest/results",
            params={"backtest_id": backtest_id, "format": "json", "page": 1, "page_size": 100},
            headers={"x-api-key": API_KEY},
        )
        response.raise_for_status()
        return response.json()

results = await get_backtest_results("your-backtest-id")
  
Note: The Python client is the recommended approach for Python users as it handles authentication, connection management, error handling, and SSE parsing automatically.

Need Help?

  • Python Client Docs: Python SDK Guide
  • API Reference: https://docs.quantumsignals.com/api/v1/
  • Support: support@quantumsignals.com
  • GitHub Issues: https://github.com/Quantum-Signals/QS1/issues
On this page

Executive Summary

Authentication

QuantumSignals API uses API key authentication for most endpoints. API keys are managed through the Key Server and are passed via the x-api-key HTTP header.

Getting Started with Authentication

Using the Python Client (Recommended)
The easiest way to authenticate is using the official SignalQ Python client:
from signalq.client import Client

# Option 1: Interactive OIDC login (recommended for development)
client = Client()
client.login()  # Opens browser for authentication, automatically manages API key

# Option 2: Use existing API key (recommended for production)
client = Client(api_key="qsk_your_api_key_here")
  
The client.login() method: 1. Opens your browser for OIDC authentication via Zitadel 2. Automatically creates or retrieves an API key 3. Configures the client for immediate use
For production environments, use environment variables:
import os
from signalq.client import Client

client = Client(api_key=os.getenv("SIGNALQ_API_KEY"))
  
Managing API Keys with Python Client
from signalq.client import Client

# Authenticate with OIDC
client = Client()
client.login()

# List your API keys
keys = client.list_keys()
for key_info in keys.keys:
    print(f"{key_info.name}: {key_info.id}")

# Create a new API key
new_key = client.create_key()
print(f"New key: {new_key.key}")  # Save this securely!

# Delete a key
client.delete_key(key_id="550e8400-e29b-41d4-a716-446655440000")
  

InstallaManual Authentication Flowtion

For non-Python integrations or custom implementations:
Step 1: Authenticate with OIDC
The Key Server uses OIDC (OpenID Connect) for user authentication. You'll need to obtain an access token from your identity provider (Zitadel).
Step 2: Create an API Key
Once authenticated, create an API key using the Key Server:
POST /v1/keys
Authorization: Bearer YOUR_OIDC_TOKEN
Content-Type: application/json

{
  "name": "My Trading Bot Key",
  "ttl": 86400  # Optional: TTL in seconds (default: never expires)
}
  
Response:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "key": "qsk_1234567890abcdef",
  "name": "My Trading Bot Key",
  "created_at": "2025-01-15T10:30:00Z",
  "expires_at": null
}
Important: Save the key value securely - it will only be shown once!

Using Your API Key

Include your API key in the x-api-key header for all authenticated requests:
GET /v1/signals
x-api-key: qsk_1234567890abcdef
  

Which Endpoints Require Authentication?

Authenticated Endpoints (require x-api-key)
  • GET /v1/signals - Streaming signals
  • GET /v1/models - Model catalog
  • GET /v1/backtest - Backtest metadata
  • GET /v1/backtest/results - Backtest results
  • GET /v1/backtest/accuracy - Backtest accuracy metrics
  • GET /v1/most_recent_backtest - Most recent backtest
Public Endpoints (no authentication)
  • POST /v1/keys - Create API key (requires OIDC token)
  • GET /v1/keys - List your API keys (requires OIDC token)
  • DELETE /v1/keys - Delete an API key (requires OIDC token)
Note: The Key Server endpoints use OIDC token authentication instead of API keys.

Managing API Keys

List Your Keys
GET /v1/keys
Authorization: Bearer YOUR_OIDC_TOKEN
  
Delete a Key
DELETE /v1/keys?key_id=550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer YOUR_OIDC_TOKEN
  

Security Best Practices

  • Never commit API keys to version control
  • Rotate keys regularly - delete old keys and create new ones
  • Use environment variables to store keys in your applications
  • Use different keys for different environments (dev, staging, production)
  • Set TTL for keys used in temporary scripts or testing
  • Monitor key usage through the Key Server

Troubleshooting

401 Unauthorized
  • Check that you're including the x-api-key header
  • Verify the key is correct (no typos, whitespace)
  • Confirm the key hasn't been deleted
403 Forbidden
  • The key exists but doesn't have permission for this endpoint
  • Contact support to upgrade your access level
429 Too Many Requests
  • You've exceeded the rate limit
  • Consider implementing exponential backoff
On this page

Executive Summary

QuantumSignals Python Client

The official Python client library for the QuantumSignals API. This client provides a simple, intuitive interface for authentication, streaming signals, managing API keys, and accessing backtest results.

Installation

The SignalQ client is part of the QS1 workspace. Install with:
uv sync --package signalq_client
  

Quick Start

Get started in 30 seconds:
from signalq.client import Client

# Create client and authenticate (opens browser for OIDC login)
client = Client()
client.login()

# Stream real-time trading signals
for signal in client.stream_signals():
    print(f"{signal.time} | {signal.symbol}: {signal.signal}")
  

Authentication

Interactive Login (Recommended for Development)
The easiest way to authenticate is using the interactive OIDC login flow:
from signalq.client import Client

client = Client()

# Opens browser for authentication and automatically creates/retrieves an API key
client.login()

# Client is now authenticated and ready to use
models = client.get_model_catalog()
  
Using an Existing API Key (Recommended for Production)
If you already have an API key, skip the login flow:
from signalq.client import Client

# Pass API key directly
client = Client(api_key="qsk_your_api_key_here")

# Client is ready to use
models = client.get_model_catalog()
  

Core Features

Model Catalog
Get information about available trading models:
# Get catalog as dict[model_id -> ModelDescription]
models = client.get_model_catalog()

for model_id, model in models.items():
    print(f"Model: {model_id}")
    print(f"  Trained on: {', '.join(model.trained_on_symbols)}")
    print(f"  Date range: {model.trained_date_range[0]} to {model.trained_date_range[1]}")
    print(f"  Hash: {model.hash}")
  
Signal Streaming
Stream real-time trading signals via Server-Sent Events:
# Stream indefinitely
for signal in client.stream_signals():
    print(f"Time: {signal.time}")
    print(f"Symbol: {signal.symbol}")
    print(f"Signal: {signal.signal}")  # 0 (down), 1 (stable), 2 (up)
    print(f"Model: {signal.model}")

    # Process signal
    if signal.signal == 1:
        execute_buy(signal.symbol)
    elif signal.signal == 0:
        execute_sell(signal.symbol)
  
Signal Data Model: - time: int - Unix timestamp - symbol: str - Trading symbol (e.g., "AAPL") - signal: int - Signal value: -1 (sell), 0 (hold), 1 (buy) - model: str - Model identifier
Backtest Operations
Access historical backtest results for model evaluation:

Get Most Recent Backtest
# Get the most recent backtest for a model/symbol combination.  Use the model
# hash from the model catalog for the model you wish to retrieve

backtest = client.get_most_recent_backtest(
    model_hash="abc123def456",
    symbol="AAPL"
)

print(f"Backtest ID: {backtest['id']}")
print(f"Status: {backtest['status']}")
  
Get Backtest Metadata
from signalq.client import BacktestMetadata

metadata: BacktestMetadata = client.get_backtest_metadata(
    backtest_id="550e8400-e29b-41d4-a716-446655440000"
)

print(f"Model: {metadata.model_id}")
print(f"Symbol: {metadata.symbol}")
print(f"Date range: {metadata.backtest_from_date} to {metadata.backtest_to_date}")
print(f"Status: {metadata.status}")
  
Get Backtest Results
# Get results in JSON format (paginated)
results = client.get_backtest_results(
    backtest_id="550e8400-e29b-41d4-a716-446655440000",
    format="json",
    page=1,
    page_size=100
)

for row in results['data']:
    print(f"{row['time']}: Position={row['position']}, PnL={row['pnl']}")

# Get results in CSV format -- this returns bytes which must be written
# to a file
csv_data = client.get_backtest_results(
    backtest_id="550e8400-e29b-41d4-a716-446655440000",
    format="csv"
)
with open("test.csv", "wb") as f:
    f.write(csv_data)

# Get results in Parquet format; this returns bytes which must be written
# to a file
parquet_data = client.get_backtest_results(
    backtest_id="550e8400-e29b-41d4-a716-446655440000",
    format="parquet"
)
with open("test.parquet", "wb") as f:
    f.write(parquet_data)
  
Result Data Model: - time: int - Unix timestamp - signal: int - Signal at that time - position: int - Portfolio position - pnl: float - Profit/loss for the period - cumulative_pnl: float - Cumulative profit/loss
Get Backtest Accuracy Metrics
# Get accuracy metrics in JSON
accuracy = client.get_backtest_accuracy(
    backtest_id="550e8400-e29b-41d4-a716-446655440000",
    format="json"
)

print(f"Accuracy: {accuracy['accuracy']:.2%}")
print(f"Precision: {accuracy['precision']:.2%}")
print(f"Recall: {accuracy['recall']:.2%}")

# Filter by date range
accuracy = client.get_backtest_accuracy(
    backtest_id="550e8400-e29b-41d4-a716-446655440000",
    format="json", # this returns a dict
    from_date="2024-01-01", # optional
    to_date="2024-12-31" # optional
)
  

API Key Management

Manage your API keys programmatically (requires OIDC authentication):
# Authenticate with OIDC first
client = Client()
client.login()

# List all your API keys
keys_response = client.list_keys()
for key_info in keys_response.keys:
    print(f"Key ID: {key_info.id}")
    print(f"Name: {key_info.name}")
    print(f"Created: {key_info.created_at}")

# Create a new API key
new_key = client.create_key()
print(f"New key: {new_key.key}")  # Save this - it's only shown once!

# Delete a key by ID
client.delete_key(key_id="550e8400-e29b-41d4-a716-446655440000")

# Or delete by key value
client.delete_key(key="qsk_your_key_here")
  

Best Practices

Use Context Managers
Use Context ManagersProperly close HTTP connections using context managers:
from signalq.client import Client

with Client() as client:
    client.login()
    models = client.get_model_catalog()
    # Client automatically closes on exit
  
Handle Exceptions
The client provides specific exceptions for error handling:
from signalq.client import Client, AuthenticationError, APIKeyError, APIError

client = Client()

try:
    client.login()
    models = client.get_model_catalog()
except AuthenticationError as e:
    print(f"OIDC authentication failed: {e}")
except APIKeyError as e:
    print(f"API key issue: {e}")
except APIError as e:
    print(f"API request failed: {e}")
    print(f"Status code: {e.status_code}")
    print(f"Response: {e.response_body}")
  
Production Signal Streaming
For production signal streaming with error recovery:
from signalq.client import Client, APIError
import time
import os

def stream_signals_with_retry(max_retries=3):
    """Stream signals with automatic retry on failure."""
    client = Client(api_key=os.getenv("SIGNALQ_API_KEY"))
    retry_count = 0

    while retry_count < max_retries:
        try:
            for signal in client.stream_signals():
                # Process signal
                process_signal(signal)
                retry_count = 0  # Reset on successful signal

        except APIError as e:
            retry_count += 1
            wait_time = 2 ** retry_count  # Exponential backoff
            print(f"Stream error: {e}. Retrying in {wait_time}s...")
            time.sleep(wait_time)
        except KeyboardInterrupt:
            print("Stopping stream...")
            break
        finally:
            client.close()
  

Complete Example

Here's a complete production-ready example:
import os
from signalq.client import Client, APIError
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def main():
    """Main trading bot using SignalQ client."""

    # Initialize client with API key from environment
    api_key = os.getenv("SIGNALQ_API_KEY")
    if not api_key:
        raise ValueError("SIGNALQ_API_KEY environment variable required")

    with Client(api_key=api_key) as client:
        # Get available models
        logger.info("Fetching model catalog...")
        models = client.get_model_catalog()
        logger.info(f"Available models: {list(models.keys())}")

        # Stream and process signals
        logger.info("Starting signal stream...")
        try:
            for signal in client.stream_signals():
                logger.info(
                    f"Signal received: {signal.symbol} -> {signal.signal} "
                    f"(model: {signal.model}, time: {signal.time})"
                )

                # Execute trading logic
                if signal.signal == 2:
                    logger.info(f"BUY signal for {signal.symbol}")
                    # execute_buy(signal.symbol)
                elif signal.signal == 0:
                    logger.info(f"SELL signal for {signal.symbol}")
                    # execute_sell(signal.symbol)

        except APIError as e:
            logger.error(f"API error: {e}")
            raise
        except KeyboardInterrupt:
            logger.info("Shutting down...")

if __name__ == "__main__":
    main()
  

Troubleshooting

Port Already in Use
If port 8080 is in use or your system for client.login() to get OIDC authentication (for key management). you will have to clear the port on your side. Key-based authentication should work at any time!
Authentication Timeout
The login flow times out after 5 minutes. Complete authentication before the timeout.
Connection Errors During Streaming
Signal streaming is a long-lived connection. If you encounter disconnections: - Implement retry logic with exponential backoff - Check your network stability - Verify the streaming service is running

API Reference

For detailed REST API documentation and advanced HTTP integration, see: - REST API Quickstart - Streaming Service Documentation - Authentication Guide
On this page

Executive Summary