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

# Authentication

> How to authenticate with the River Markets API

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.

<Tip>
  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.
</Tip>

## Getting an API Key

1. Sign up at [app.rivermarkets.com](https://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:

| 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.

```python theme={null}
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](/ws-api-reference/authentication).

<Warning>
  Never share your private key or commit it to source control. Use environment
  variables or a secret manager.
</Warning>
