Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.rivermarkets.com/llms.txt

Use this file to discover all available pages before exploring further.

The River Markets API uses Ed25519 request signing for programmatic access. Your private key never traverses the wire — every request is signed locally, and the server verifies the signature against the public key on file.
The official SDK rivermarkets for Python, handle signing transparently. Pass your KEY_ID and PRIVATE_KEY to the client constructor and you’re done. The raw recipes below are for anyone integrating without an SDK.

Getting an API Key

  1. Sign up at app.rivermarkets.com
  2. Go to Settings → API Keys
  3. Click Create API Key
  4. Copy the private key — it is shown only once and never stored on the server
You’ll be given a key_id (UUID) and a base64-encoded Ed25519 private_key. Keep the private key in a secret store (env var, vault, etc.) — anyone with it can act as you.

REST signing

Every REST request carries three headers:
HeaderValue
X-River-Key-IdYour key UUID
X-River-TimestampCurrent unix seconds (within 30s of server time)
X-River-SignatureBase64 Ed25519 signature over the canonical request
The canonical string is LF-joined (no trailing newline):
METHOD\n
PATH\n
SORTED_QUERY\n
TIMESTAMP\n
SHA256(body) hex
  • SORTED_QUERY is the query string with keys sorted alphabetically and values percent-encoded per RFC 3986. Empty when there is no query string.
  • SHA256(body) is the hex digest of the raw request body, or sha256("") when there is no body.
import base64, hashlib, time
from urllib.parse import urlsplit, urlencode, parse_qsl, quote
from nacl.signing import SigningKey
import requests

KEY_ID = "<uuid from Settings → API Keys>"
PRIVATE_KEY_B64 = "<base64 private key shown once at creation>"
BASE_URL = "https://api.rivermarkets.com/v1"

signing_key = SigningKey(base64.b64decode(PRIVATE_KEY_B64))


def sign(method: str, url: str, body: bytes = b"") -> dict:
    parts = urlsplit(url)
    # quote_via=quote encodes spaces as %20 to match RFC 3986 / the server.
    # urlencode's default (quote_plus) uses + and produces a different canonical string.
    sorted_q = urlencode(sorted(parse_qsl(parts.query, keep_blank_values=True)), quote_via=quote)
    ts = str(int(time.time()))
    canonical = "\n".join([
        method.upper(), parts.path, sorted_q, ts, hashlib.sha256(body).hexdigest(),
    ]).encode()
    sig = signing_key.sign(canonical).signature
    return {
        "X-River-Key-Id": KEY_ID,
        "X-River-Timestamp": ts,
        "X-River-Signature": base64.b64encode(sig).decode(),
    }


url = f"{BASE_URL}/markets/search?q=bitcoin"
response = requests.get(url, headers=sign("GET", url))
print(response.json())
For streaming endpoints, see WebSocket Authentication.
Never share your private key or commit it to source control. Use environment variables or a secret manager.