Typed HTTP client for the Revolut X Public Trading API REST API. Zero runtime dependencies — uses only Node.js built-ins (crypto, fetch).
- Installation
- Quick Start
- Configuration
- API Reference
- Authentication
- Logging
- Error Handling
- Rate Limiting
- Symbol Format
- Support
Download revolutx-api-*.tgz from the latest release, then:
npm install ./revolutx-api-*.tgzOr from source:
git clone https://github.com/revolut-engineering/revolut-x-api.git
cd revolut-x-api/api && npm installnpm run buildRequires Node.js 20+.
import { RevolutXClient } from "revolutx-api";
const client = new RevolutXClient({
apiKey: "your-api-key",
privateKeyPath: "~/.config/revolut-x/private.pem",
});
const balances = await client.getBalances();
console.log(balances);| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
— | Revolut X API key |
privateKey |
KeyObject |
— | Ed25519 private key object |
privateKeyPath |
string |
— | Path to PEM-encoded private key file |
baseUrl |
string |
https://revx.revolut.com |
API base URL |
timeout |
number |
30000 |
Request timeout in milliseconds |
maxRetries |
number |
3 |
Max retry attempts for failed requests |
autoLoadCredentials |
boolean |
true |
Auto-load credentials from config directory |
logger |
LogCallback |
— | Custom log handler — receives structured log entries |
When neither apiKey nor privateKey is provided, the client loads credentials from the platform config directory:
| Platform | Path |
|---|---|
| macOS | ~/.config/revolut-x/ |
| Linux | ~/.config/revolut-x/ |
| Windows | %APPDATA%\revolut-x\ |
Override with REVOLUTX_CONFIG_DIR environment variable.
Expected files: config.json (contains apiKey) and private.pem (Ed25519 key).
Set autoLoadCredentials: false to disable.
// Check if the client has credentials configured
client.isAuthenticated; // → boolean// Get all balances
const balances = await client.getBalances();
// → AccountBalance[] — { currency, available, reserved, total, staked? }// Get supported currencies
const currencies = await client.getCurrencies();
// → CurrencyMap — { [symbol]: { symbol, name, scale, asset_type, status } }
// Get trading pairs
const pairs = await client.getCurrencyPairs();
// → CurrencyPairMap — { [pair]: { base, quote, base_step, quote_step, min/max_order_size, status } }// Tickers (all or filtered)
const tickers = await client.getTickers();
const btc = await client.getTickers({ symbols: ["BTC-USD"] });
// → { data: Ticker[], metadata: { timestamp } }
// OHLCV candles
const candles = await client.getCandles("BTC-USD", {
interval: "1h", // "1m","5m","15m","30m","1h","4h","1d","2d","4d","1w","2w","4w" or minutes as number
startDate: 1700000000000,
endDate: 1700086400000,
});
// → { data: Candle[] }
// Order book (authenticated)
const book = await client.getOrderBook("BTC-USD", { limit: 10 }); // depth per side, 1-192, default 50
// → { data: { asks, bids }, metadata: { timestamp } }// Place limit buy
const result = await client.placeOrder({
symbol: "BTC-USD",
side: "buy",
limit: {
price: "95000",
baseSize: "0.001", // baseSize or quoteSize required
executionInstructions: ["post_only"], // optional: "allow_taker" | "post_only"
},
clientOrderId: "my-order-1", // optional, auto-generated if omitted
});
// → { data: { venue_order_id, client_order_id, state } }
// Place market sell
await client.placeOrder({
symbol: "BTC-USD",
side: "sell",
market: { baseSize: "0.001" },
});
// Active orders (with filters)
const active = await client.getActiveOrders({
symbols: ["BTC-USD"],
side: "buy", // "buy" | "sell"
orderStates: ["new", "partially_filled"], // "pending_new" | "new" | "partially_filled"
orderTypes: ["limit", "conditional", "tpsl"], // "limit" | "conditional" | "tpsl"
limit: 50,
cursor: "...", // pagination cursor
});
// Historical orders
const history = await client.getHistoricalOrders({
symbols: ["BTC-USD"],
orderStates: ["filled", "cancelled", "partially_filled"], // "filled" | "cancelled" | "rejected" | "replaced" | "partially_filled"
orderTypes: ["market", "limit"], // "market" | "limit"
startDate: 1700000000000,
endDate: 1700086400000,
limit: 100,
cursor: "...", // pagination cursor
});
// Get specific order
const order = await client.getOrder("venue-order-id");
// Cancel single order
await client.cancelOrder("venue-order-id");
// Cancel all active orders
await client.cancelAllOrders();// Order fills
const fills = await client.getOrderFills("venue-order-id");
// All trades for a pair
const trades = await client.getAllTrades("BTC-USD", {
startDate: 1700000000000,
endDate: 1700086400000,
});
// My private trades
const myTrades = await client.getPrivateTrades("BTC-USD");const transactions = await client.getTransactions({
startDate: 1700000000000,
endDate: 1700086400000,
types: ["buy", "receive"],
currencies: ["BTC", "USD"],
});
// → { data: Transaction[], metadata: { timestamp, next_cursor? } }
// A transaction may have source fields, destination fields, or both.Revolut X uses Ed25519 request signing. Each request includes three headers:
X-Revx-API-Key— your API keyX-Revx-Timestamp— epoch millisecondsX-Revx-Signature— Ed25519 signature of{timestamp}{METHOD}{path}{query}{body}
import { generateKeypair } from "revolutx-api";
const { publicKeyPem } = generateKeypair("/path/to/output/dir");
console.log("Register this public key with Revolut X:");
console.log(publicKeyPem);import {
signRequest,
buildAuthHeaders,
loadPrivateKey,
getPublicKeyPem,
createTimestamp,
} from "revolutx-api";
const key = loadPrivateKey("/path/to/private.pem");
const headers = buildAuthHeaders("api-key", key, "GET", "/api/1.0/balances");
// Derive public key PEM from an existing private key object
const publicKeyPem = getPublicKeyPem(key);
// Get a current epoch-milliseconds timestamp string
const ts = createTimestamp();Pass a logger callback to receive structured log entries from the client (requests, retries, errors, etc.):
import { LogCallback, LogLevel } from "revolutx-api";
const client = new RevolutXClient({
apiKey: "your-api-key",
privateKeyPath: "~/.config/revolut-x/private.pem",
logger: (entry) => {
console.log(`[${entry.level}] ${entry.message}`, entry.data ?? "");
},
});Each LogEntry has:
| Field | Type | Description |
|---|---|---|
level |
LogLevel |
"debug" | "info" | "warn" | "error" |
message |
string |
Human-readable description |
data |
unknown |
Optional structured payload |
All errors extend RevolutXError:
| Error | HTTP Status | When |
|---|---|---|
AuthNotConfiguredError |
— | No API key or private key configured |
AuthenticationError |
401 | Invalid API key or signature |
ForbiddenError |
403 | Access denied |
RateLimitError |
429 | Rate limit exceeded — check retryAfter (ms) |
ValidationError |
400 | Request failed schema validation |
OrderError |
400 | Invalid order parameters |
NotFoundError |
404 | Resource not found |
ConflictError |
409 | Conflicting request (e.g. timestamp skew) |
ServerError |
5xx | Server-side error — check statusCode |
NetworkError |
— | Connection or timeout failure |
import { RateLimitError, OrderError } from "revolutx-api";
try {
await client.placeOrder({ ... });
} catch (err) {
if (err instanceof RateLimitError) {
const waitMs = err.retryAfter ?? 1000;
console.log(`Rate limited. Retry after ${waitMs}ms`);
} else if (err instanceof OrderError) {
console.error("Order rejected:", err.message);
}
}The client does not throttle requests proactively. When the server responds with 429, a RateLimitError is thrown immediately. Check the retryAfter property for the server-indicated wait time in milliseconds before retrying.
Use dashes in requests: BTC-USD, ETH-USD.
The API returns symbols with slashes in responses: BTC/USD.
- Issues: GitHub Issues
- Revolut X API Docs: developer.revolut.com/docs/x-api/revolut-x-crypto-exchange-rest-api
MIT