Authentication
The 4rho API supports two authentication methods. All trading endpoints require authentication.
HMAC API Key (Recommended for Market Makers)
API keys are issued by 4rho administrators. Each key has:
- API Key — Public identifier (prefixed with
4rho_) - Secret — Used for HMAC signing (shown once at creation)
- Passphrase — Additional authentication factor (shown once at creation)
- Scopes — Granular permissions (e.g.,
trade:orders,stream:market) - Rate Limit Tier —
standard(10/s),market_maker(100/s), orpremium(50/s)
Required Headers
Every authenticated request must include these headers:
| Header | Description |
|---|---|
X-4RHO-API-KEY | Your API key (e.g., 4rho_abc123...) |
X-4RHO-SIGNATURE | HMAC-SHA256 signature of the request |
X-4RHO-TIMESTAMP | Unix timestamp (seconds) |
X-4RHO-PASSPHRASE | Your passphrase |
Signing Algorithm
The signature is computed in two steps:
Step 1: Derive the HMAC key
hmac_key = SHA256(raw_secret)
Step 2: Compute the signature
message = timestamp + "\n" + method + "\n" + path + "\n" + SHA256(body)
signature = HMAC-SHA256(hmac_key, message)
Where:
timestamp— Unix epoch seconds as a string (must be within 30 seconds of server time)method— HTTP method in uppercase (GET,POST,DELETE,PUT)path— Request path only, no query string (e.g.,/v1/orders)body— Request body as string (empty string""for GET/DELETE requests)SHA256(body)— Hex-encoded SHA-256 hash of the body stringSHA256(raw_secret)— Hex-encoded SHA-256 hash of your raw secret
The \n separators are literal newline characters (not the two characters \ and n).
Example: Python
import hashlib
import hmac
import time
import requests
API_KEY = "4rho_your_key_here"
SECRET = "your_raw_secret"
PASSPHRASE = "your_passphrase"
BASE_URL = "https://4rho.com/api"
def sign_request(secret: str, method: str, path: str, body: str = "") -> dict:
timestamp = str(int(time.time()))
body_hash = hashlib.sha256(body.encode()).hexdigest()
message = f"{timestamp}\n{method}\n{path}\n{body_hash}"
# HMAC key is SHA256 of the raw secret
hmac_key = hashlib.sha256(secret.encode()).hexdigest()
signature = hmac.new(
hmac_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return {
"X-4RHO-API-KEY": API_KEY,
"X-4RHO-SIGNATURE": signature,
"X-4RHO-TIMESTAMP": timestamp,
"X-4RHO-PASSPHRASE": PASSPHRASE,
}
# Example: GET request (no body)
path = "/v1/user/positions"
headers = sign_request(SECRET, "GET", path)
resp = requests.get(f"{BASE_URL}{path}", headers=headers)
print(resp.json())
# Example: POST request (with body)
import json
path = "/v1/orders"
body = json.dumps({"market_id": "...", "side": "BUY", "maker_amount": "1000000", ...})
headers = sign_request(SECRET, "POST", path, body)
headers["Content-Type"] = "application/json"
resp = requests.post(f"{BASE_URL}{path}", headers=headers, data=body)
print(resp.json())
Example: TypeScript (Node.js)
import crypto from 'crypto';
const API_KEY = '4rho_your_key_here';
const SECRET = 'your_raw_secret';
const PASSPHRASE = 'your_passphrase';
const BASE_URL = 'https://4rho.com/api';
function signRequest(
secret: string,
method: string,
path: string,
body: string = ''
): Record<string, string> {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const message = `${timestamp}\n${method}\n${path}\n${bodyHash}`;
// HMAC key is SHA256 of the raw secret
const hmacKey = crypto.createHash('sha256').update(secret).digest('hex');
const signature = crypto
.createHmac('sha256', hmacKey)
.update(message)
.digest('hex');
return {
'X-4RHO-API-KEY': API_KEY,
'X-4RHO-SIGNATURE': signature,
'X-4RHO-TIMESTAMP': timestamp,
'X-4RHO-PASSPHRASE': PASSPHRASE,
};
}
// Example: GET request
const path = '/v1/user/positions';
const headers = signRequest(SECRET, 'GET', path);
const resp = await fetch(`${BASE_URL}${path}`, { headers });
console.log(await resp.json());
// Example: POST request
const orderPath = '/v1/orders';
const orderBody = JSON.stringify({ market_id: '...', side: 'BUY', maker_amount: '1000000' });
const orderHeaders = {
...signRequest(SECRET, 'POST', orderPath, orderBody),
'Content-Type': 'application/json',
};
const orderResp = await fetch(`${BASE_URL}${orderPath}`, {
method: 'POST',
headers: orderHeaders,
body: orderBody,
});
console.log(await orderResp.json());
Example: TypeScript (Browser / Web Crypto API)
const API_KEY = '4rho_your_key_here';
const SECRET = 'your_raw_secret';
const PASSPHRASE = 'your_passphrase';
async function sha256Hex(data: string): Promise<string> {
const buffer = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(data)
);
return Array.from(new Uint8Array(buffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
async function signRequest(
secret: string,
method: string,
path: string,
body: string = ''
): Promise<Record<string, string>> {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = await sha256Hex(body);
const message = `${timestamp}\n${method}\n${path}\n${bodyHash}`;
const hmacKey = await sha256Hex(secret);
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(hmacKey),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const sigBuffer = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(message)
);
const signature = Array.from(new Uint8Array(sigBuffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
return {
'X-4RHO-API-KEY': API_KEY,
'X-4RHO-SIGNATURE': signature,
'X-4RHO-TIMESTAMP': timestamp,
'X-4RHO-PASSPHRASE': PASSPHRASE,
};
}
Clock Synchronization
Use GET /v1/time to synchronize your clock with the server. Requests with timestamps more than 30 seconds from server time are rejected.
{ "time": 1709136000 }
JWT Bearer Token
For browser-based applications, authenticate via OAuth and include the JWT in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
JWTs are obtained through the OAuth flow (/v1/auth/oauth/:provider or /v1/auth/wallet/login) and refreshed via POST /v1/auth/refresh.
Permission Scopes
API keys are assigned granular scopes:
| Scope | Description |
|---|---|
read:account | Read user orders, trades, positions, balances |
write:account | Fiat on/off-ramp, claim winnings, redeem referrals |
manage:account | Link wallets, MFA, logout, revoke sessions |
trade:orders | Place and cancel individual orders |
trade:bulk | Batch operations (place/cancel many) |
manage:strategies | Create, update, delete trading strategies |
stream:market | Subscribe to market WebSocket feeds |
stream:user | Subscribe to user WebSocket feeds |
Requests to endpoints requiring a scope that your key lacks will receive a 403 Forbidden response with the code INSUFFICIENT_SCOPE.
IP Allowlist
API keys can optionally be restricted to specific IP addresses. Requests from unlisted IPs are rejected with 403 Forbidden.