TypeScript
A typed client generated from the server's OpenAPI document: place through cancel with no Python.
The server's OpenAPI document is the contract. Generate the types from it rather than writing them by hand, so a renamed field is a compile error instead of a runtime surprise. The routes are explicit, so every response is a real schema, not object.
synpath serve), which must be running. On the machine it runs on, the client reads its access token from ~/.synpath/servers.json; for a server elsewhere, set SYNPATH_SERVER and SYNPATH_ACCESS_TOKEN.Installation
# 1. the contract, without starting a server
synpath schema --trading --out openapi.json
# 2. the types
npx openapi-typescript openapi.json --default-non-nullable false -o src/synpath.d.ts--default-non-nullable false matters. Without it, every field with a default (time_in_force, post_only, reduce_only) is generated as required, and a plain limit order has to spell out three fields the server would fill in.Quick Start
import type { components } from "./src/synpath.js"; // generated
type Order = components["schemas"]["Order"];
type OrderRequest = components["schemas"]["OrderRequest"];
const base = "http://127.0.0.1:8000/trading"; // your self-hosted server (synpath serve)
const headers = { authorization: `Bearer ${process.env.SYNPATH_ACCESS_TOKEN}`, "content-type": "application/json" };
async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(base + path, { ...init, headers: { ...headers, ...(init.headers ?? {}) } });
if (!res.ok) {
const { error } = await res.json(); // { code, message, details }, on every route
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
return (await res.json()) as T;
}
const request: OrderRequest = {
market_id: "polymarket:2252244",
side: "buy", // buy takes YES, sell takes NO
type: "limit",
price: "0.42", // always the YES price
amount: "10",
book: "typescript",
};
const placed = await call<Order>("/orders", { method: "POST", body: JSON.stringify(request) });
console.log(`placed ${placed.id} ${placed.side} ${placed.amount} at ${placed.price} (${placed.status})`);Trading
type PageOfOrders = components["schemas"]["PageResponse_Order_"];
const open = await call<PageOfOrders>("/orders"); // list
const one = await call<Order>(`/orders/${placed.id}`); // get
const edited = await call<Order>(`/orders/${placed.id}`, { // amend
method: "PATCH", body: JSON.stringify({ price: "0.41", amount: "8" }),
});
const canceled = await call<Order>(`/orders/${placed.id}`, { method: "DELETE" }); // cancelComplex orders go to the same route with a different type and params; see Create complex order.
Portfolio
type Positions = components["schemas"]["PageResponse_Position_"];
type Fills = components["schemas"]["PageResponse_Fill_"];
const positions = await call<Positions>("/positions");
const fills = await call<Fills>("/fills?venue=polymarket");
const pnl = await call<components["schemas"]["PnlView"]>("/pnl?level=book");Market Data
Market data on your server needs no token. Generate its types from the read contract, schema --out without --trading.
const read = "http://127.0.0.1:8000";
type Market = components["schemas"]["Market"];
type Book = components["schemas"]["OrderBook"];
const page = await (await fetch(`${read}/venues/polymarket/markets?query=election&limit=10`)).json();
const market: Market = await (await fetch(`${read}/venues/polymarket/markets/polymarket:2252244`)).json();
const book: Book = await (await fetch(`${read}/venues/polymarket/markets/polymarket:2252244/book?side=no`)).json();
console.log(market.yes.quote.ask, book.best_bid?.price);Realtime (WebSockets)
The engine events socket replays from a sequence number, then stays live, so a reconnecting client misses nothing. Live market data from the venues is the Python streams' job today; see the WebSocket API reference.
import WebSocket from "ws";
const socket = new WebSocket(`ws://127.0.0.1:8000/trading/ws/events?key=${process.env.SYNPATH_ACCESS_TOKEN}&since=0`);
socket.on("message", (raw) => {
const event = JSON.parse(raw.toString());
console.log(event.seq, event.kind, event.key); // order.accepted, fill.booked, ...
});A complete round trip
clients/typescript/example.ts in the repository is the whole loop in about sixty lines: place, watch the event stream, cancel, confirm the account is flat. From that folder, against a server with a paper venue:
synpath schema --trading --out openapi.json
npm install
npm run types # openapi.json -> src/synpath.d.ts
npm run build # example.ts -> dist/example.js
npm start # reads the server's access token from ~/.synpath/servers.jsonplaced paper-2 buy 5 at 0.35 (open)
open 1 order(s): paper-2
cancel paper-2 → canceled
watched 12 events, including intent.planned, order.accepted, intent.sent, order.canceled
done the account is flat, no Python was involvedOther generators
Any OpenAPI generator works from the same document. @hey-api/openapi-ts produces a client with functions rather than types alone; the same command gives Go, Java or Rust a client too.

