Get started
Authentication
Sign-In With Solana for the Seeryx API: the message, the token in X-Auth-Token or Bearer, and the trading session key that signs your orders.
Overview
| Calls | Needs |
|---|---|
| Market data: markets, rounds, books, trades, events, profiles | Nothing |
| Your account: cash, positions, orders, activity | A sign-in token in X-Auth-Token |
| Orders, cancels, splits, merges | The token and an Idempotency-Key; on seeryx.com every order, split and merge also carries signed, made by your trading session key |
| Deposits, withdrawals, the session itself | The token, and your wallet's signature on the transaction the server prepares |
Sign in with a wallet
Seeryx uses Sign-In With Solana: your wallet signs a short text message, which costs nothing and sends nothing on chain.
POST /api/auth/noncewith{ "address": "<your wallet>" }returns the message. It is valid for 5 minutes and works once.- Check that line 1 names this site and line 2 your address, then sign the message's exact UTF-8 bytes with the wallet's Ed25519 key and encode the 64-byte signature in base58.
POST /api/auth/verifywithaddress,messageandsignaturereturnstoken. The first sign-in creates the account.
seeryx.com wants you to sign in with your Solana account:
4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm
Sign in to Seeryx. This does not send a transaction or cost a fee.
URI: https://seeryx.com
Version: 1
Chain ID: mainnet
Nonce: a4b5fb6f571c43b4b1c5057abaa8cea2
Issued At: 2026-10-13T18:29:28.000Z
Expiration Time: 2026-10-13T18:34:28.000Z// seeryx.ts: a small client for the Seeryx API. Node 22+, npm i @solana/kit
import { readFileSync } from 'node:fs';
import { createKeyPairFromBytes, getAddressFromPublicKey, getBase58Decoder, signBytes } from '@solana/kit';
import type { Address } from '@solana/kit';
import { apiUrl } from './api-url.mjs';
export const BASE = process.env.SEERYX_URL ?? 'https://seeryx.com';
export const ACCESS = process.env.SEERYX_ACCESS ?? ''; // only on a site with access codes
let token = '';
export interface Round {
slug: string; start: number; end: number; open: number | null; close: number | null;
outcome: 'up' | 'down' | null; status: 'upcoming' | 'live' | 'settled';
}
export interface Book { bids: [price: number, shares: number][]; asks: [price: number, shares: number][] }
export interface Account {
positions: { slug: string; side: 'up' | 'down'; shares: number; cost: number }[];
orders: {
id: number; slug: string; side: 'up' | 'down'; action: 'buy' | 'sell'; shares: number; limit: number;
}[];
balance: { available: number; total: number };
}
export class ApiError extends Error {
constructor(
message: string, readonly status: number, readonly code?: string, readonly retryAfter?: number,
) {
super(message);
}
}
// One request. Writes get a fresh Idempotency-Key unless you pass the key of the attempt you are retrying.
export async function api<T>(path: string, init: { method?: string; body?: unknown; key?: string } = {}) {
const method = init.method ?? 'GET';
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (token) headers['X-Auth-Token'] = token;
if (ACCESS) headers['X-Access-Token'] = ACCESS;
if (method !== 'GET') headers['Idempotency-Key'] = init.key ?? crypto.randomUUID();
const body = init.body === undefined ? undefined : JSON.stringify(init.body);
const res = await fetch(apiUrl(BASE, path), { method, headers, body, redirect: 'error' });
const data = await res.json().catch(() => ({}));
if (!res.ok) {
const wait = Number(res.headers.get('Retry-After')) || undefined;
throw new ApiError(data.error ?? `HTTP ${res.status}`, res.status, data.code, wait);
}
return data as T;
}
export interface Wallet { pair: CryptoKeyPair; address: Address }
// A wallet file as solana-keygen writes it: a JSON array of 64 numbers.
export async function loadWallet(path = process.env.SEERYX_WALLET ?? 'wallet.json'): Promise<Wallet> {
const pair = await createKeyPairFromBytes(new Uint8Array(JSON.parse(readFileSync(path, 'utf8'))));
return { pair, address: await getAddressFromPublicKey(pair.publicKey) };
}
// Sign-In With Solana: ask for the message, check it, sign its exact bytes, trade the signature for a token.
export async function signIn(wallet: Wallet) {
const { address } = wallet;
const { message } = await api<{ message: string }>('/auth/nonce', { method: 'POST', body: { address } });
const [first, second] = message.split('\n');
const site = `${new URL(BASE).hostname} wants you to sign in with your Solana account:`;
if (first !== site || second !== address) {
throw new Error('Unexpected sign-in message: not this site or not this wallet');
}
const bytes = await signBytes(wallet.pair.privateKey, new TextEncoder().encode(message));
const signature = getBase58Decoder().decode(bytes);
const body = { address, message, signature };
({ token } = await api<{ token: string }>('/auth/verify', { method: 'POST', body }));
return token; // valid for 7 days
}Sign-in tokens
- Header
- Send the token as
X-Auth-Token: <token>orAuthorization: Bearer <token>on every signed-in request, and astokenin the WebSocket's account subscription. - Lifetime
- 7 days. Sign in again before then, or when a call answers 403 (seeryx.com) or 401 with "Invalid or expired token".
- Cookies
- The website itself signs in with an HttpOnly cookie and a CSRF token, which only pages of the site can use. API clients use the token header and need neither.
- Limits
- 30 requests a minute per IP address for each of
/auth/nonceand/auth/verify.
The trading session key
On seeryx.com every trade settles on Solana through the Seeryx program, and the program accepts only orders signed for your account. Instead of asking the wallet for each order, the wallet approves a session key once:
| Field | Type | Description |
|---|---|---|
key | public key | A new Ed25519 key your bot made. It signs orders, splits and merges. |
expiresAt | Unix seconds | When the key stops working: at most maxSessionTtl of GET /api/funds from now (24 hours). |
limit | USDC | The most the key may spend on buys and splits in all. |
maxTrade | USDC | The most one order may spend; at most limit. |
- It can trade, never withdraw. The key can only sign orders, splits and merges for your account within its limits. Withdrawals always need the wallet itself.
- One at a time. A new session replaces the old key; resting orders signed by the old key are cancelled once the new one is in use.
- Limit orders must not outlive it. A resting order is refused if it would rest past the session's end: open a new session before quoting a round that closes later (the 1-day rounds, for example).
- Free. Seeryx pays the network fee and the account rent of the session transaction. Your first deposit must have opened your account first (until then: 409 "Deposit first").
- Close it early with
POST /api/relay{ "action": "close_session" }: your resting orders are cancelled and the key stops working at once.
How to open the session and sign with it, byte by byte: Signing orders.
Signing out
POST /api/auth/logoutends the token you send;POST /api/auth/logout-allends every token of the account.- Neither ends the trading session key: close it with
close_session, or let it expire.
curl -s -X POST https://seeryx.com/api/auth/logout-all -H "X-Auth-Token: $TOKEN"
# {"success":true}Sign-in endpoints
Sign in with a Solana wallet: ask for a message, sign its exact bytes with the wallet key, exchange the signature for a token. Nothing is sent on chain and no fee is paid.
/api/auth/nonceGet the message to sign
| Field | Type | Description |
|---|---|---|
address | string | Solana wallet address (base58) |
curl -X POST "https://seeryx.com/api/auth/nonce" \
-H "Content-Type: application/json" \
-d '{"address":"4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm"}'{
"message": "seeryx.com wants you to sign in with your Solana account:\n4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm\n\nSign in to Seeryx. This does not send a transaction or cost a fee.\n\nURI: https://seeryx.com\nVersion: 1\nChain ID: mainnet\nNonce: a4b5fb6f571c43b4b1c5057abaa8cea2\nIssued At: 2026-10-13T18:29:28.000Z\nExpiration Time: 2026-10-13T18:34:28.000Z",
"nonce": "a4b5fb6f571c43b4b1c5057abaa8cea2"
}- The message is valid for 5 minutes and can be used once. Chain ID names the Solana cluster of the server.
| Status | When |
|---|---|
| 400 | Not a Solana address |
| 429 | More than 30 requests a minute from your IP address |
/api/auth/verifyExchange the signed message for a token
| Field | Type | Description |
|---|---|---|
address | string | The same wallet address |
message | string | The message exactly as received |
signature | string | Ed25519 signature of the message bytes (UTF-8), base58 |
curl -X POST "https://seeryx.com/api/auth/verify" \
-H "Content-Type: application/json" \
-d '{"address":"4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm","message":"...","signature":"5Kq3..."}'{ "token": "eyJ0eXAiOiJKV1Qi...", "user": { "id": 3, "address": "4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm", "createdAt": "2026-10-13 18:29:28" } }- The token is valid for 7 days. Send it as X-Auth-Token (or Authorization: Bearer) on every signed-in request. The first sign-in creates the account.
| Status | When |
|---|---|
| 401 | "Signature verification failed" or "Invalid or expired login message" |
| 403 | This account is blocked |
| 429 | More than 30 requests a minute from your IP address |
/api/auth/meThe signed-in account and its cash
curl "https://seeryx.com/api/auth/me" \
-H "X-Auth-Token: $TOKEN"{ "user": { "id": 3, "address": "4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm", "username": null, "avatar": null },
"balance": { "total": 474.097965, "available": 474.097965, "locked": 0, "currency": "USDC" } }/api/auth/logoutEnd this sign-in token
curl -X POST "https://seeryx.com/api/auth/logout" \
-H "X-Auth-Token: $TOKEN"{ "success": true }- Other tokens of the account keep working. It does not end the trading session key: close that with POST /api/relay close_session.
/api/auth/logout-allEnd every sign-in token of the account
curl -X POST "https://seeryx.com/api/auth/logout-all" \
-H "X-Auth-Token: $TOKEN"{ "success": true }- Every token made so far stops working, this one too; sign in again for a new one.
/api/auth/usernameSet or change the public username
| Field | Type | Description |
|---|---|---|
username | string | 2-20 letters, digits or _; unique ignoring case |
curl -X POST "https://seeryx.com/api/auth/username" \
-H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"satoshi_bot"}'{ "success": true, "username": "satoshi_bot" }- The name shows on the leaderboard and your public profile; without one they show your shortened wallet.