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
- Sign up at app.rivermarkets.com
- Go to Settings → API Keys
- Click Create API Key
- 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:
| Header | Value |
|---|
X-River-Key-Id | Your key UUID |
X-River-Timestamp | Current unix seconds (within 30s of server time) |
X-River-Signature | Base64 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.