# Get started Source: https://docs.truemarkets.co/ True Markets lets you trade crypto, stablecoins and tokenized stocks from code. Which API you use depends on who trades. Using a coding agent? Give it the docs index at [llms.txt](https://docs.truemarkets.co/llms.txt). Gateway needs the [Authentication](https://docs.truemarkets.co/openapi/auth.yaml), [Accounts](https://docs.truemarkets.co/openapi/account.yaml) and [Trading REST](https://docs.truemarkets.co/openapi/gateway.yaml) specs; the Trading API needs the first and last. [Build with agents](https://docs.truemarkets.co/getting-started/build-with-agents) has the rest. ## Pick your path - [Trading API](https://docs.truemarkets.co/trading-api/quickstart): Trade your own account from code. - [Gateway](https://docs.truemarkets.co/gateway/quickstart): Let your customers trade inside your app. - [Institutional](https://docs.truemarkets.co/institutional/overview): Connect your firm to the exchange over REST or FIX. - [Market data](https://docs.truemarkets.co/market-data/overview): Read prices and candles. No account needed. > **Every account trades with real funds** > > There is no test mode. Keep your first trades small. ## Next steps - [Create an account](https://docs.truemarkets.co/getting-started/create-an-account) for the path you picked. - [Build with agents](https://docs.truemarkets.co/getting-started/build-with-agents) to give a coding assistant these docs or live prices. - Email [support@truemarkets.co](mailto:support@truemarkets.co) for help, and include the `request_id` from any error. --- # Create an account Source: https://docs.truemarkets.co/getting-started/create-an-account The account you need depends on who trades: you, your customers, or your firm. | Who trades | Create | At | Build with | | --- | --- | --- | --- | | You | A True Markets account | [app.truemarkets.co](https://app.truemarkets.co/) | [Trading API](https://docs.truemarkets.co/trading-api) | | Your customers | An organization | [console.truemarkets.co](https://console.truemarkets.co/) | [Gateway](https://docs.truemarkets.co/gateway) | | Your firm | Access set up with our team | [support@truemarkets.co](mailto:support@truemarkets.co) | [Institutional](https://docs.truemarkets.co/institutional/overview) | > **Every account trades with real funds** > > There is no test mode. Keep your first trades small. ## Trade your own account Create a True Markets account at [app.truemarkets.co](https://app.truemarkets.co/). It's the same account you use in the web app, and the app creates your wallet when you set up a passkey. When you trade it from code, these docs call you an API trader. Create your API key in the app too. That one key signs your requests and your orders. Next: [Create your API key](https://docs.truemarkets.co/trading-api/api-key) ## Build for your customers Create an organization at [console.truemarkets.co](https://console.truemarkets.co/). An organization represents your business, and its name is the only thing you provide. Sign in with a work email that isn't also a personal True Markets trading login, because a trading account can't join an organization. These docs call you a Gateway client. Your customers don't sign up with True Markets. You create a user for each of them through the API, and every user gets a wallet. Next: [Getting started with Gateway](https://docs.truemarkets.co/gateway/quickstart) ## Connect your firm Institutional clients trade directly on the True Markets exchange over REST or FIX. To start onboarding, email [support@truemarkets.co](mailto:support@truemarkets.co). We issue your credentials and set up the private network connection that FIX needs. Next: [About Institutional](https://docs.truemarkets.co/institutional/overview) --- # Build with agents Source: https://docs.truemarkets.co/getting-started/build-with-agents Give a coding agent any page as Markdown, or connect an AI assistant to our MCP servers so it can read prices and trade. The **Copy** button on every page copies it as Markdown. The API reference is generated from our OpenAPI specs, so an agent reads the same contract you do. An MCP server is a set of tools an assistant can call. Ours cover market data and trading, and they work with Claude, ChatGPT and Cursor. - [MCP servers](https://docs.truemarkets.co/mcp): Connect an assistant to market data and trading. - [CLI](https://docs.truemarkets.co/cli/ai-agents): JSON output and dry runs for agents that run commands. ## Give your agent the docs Point the agent at [llms.txt](https://docs.truemarkets.co/llms.txt). It lists every guide by product, says which API to use for which job, and links each spec. [llms-full.txt](https://docs.truemarkets.co/llms-full.txt) holds the same guides in one file. Add `.md` to any page URL to get that page as Markdown, for example `https://docs.truemarkets.co/gateway/quickstart.md`. ## Specs Download a spec to generate a client or to give an agent the exact contract. - [Authentication REST](https://docs.truemarkets.co/openapi/auth.yaml) - [Accounts REST](https://docs.truemarkets.co/openapi/account.yaml) - [Trading REST](https://docs.truemarkets.co/openapi/gateway.yaml) - [CeFi direct REST](https://docs.truemarkets.co/openapi/cefi-direct.yaml) - [Market data REST](https://docs.truemarkets.co/openapi/marketdata.yaml) - [CeFi direct WebSocket](https://docs.truemarkets.co/asyncapi/cefi-direct.yaml) (AsyncAPI) - [Market data WebSocket](https://docs.truemarkets.co/asyncapi/marketdata.yaml) (AsyncAPI) ## Add to AGENTS.md Paste this into your project's `AGENTS.md` or `CLAUDE.md` so the agent reads our docs without being told each time. AGENTS.md ```markdown ## True Markets - Docs index: https://docs.truemarkets.co/llms.txt - Trading REST spec: https://docs.truemarkets.co/openapi/gateway.yaml - Building for your customers: use Gateway. Trading your own account: use the Trading API. ``` Then add the MCP servers so the agent can read prices and trade. Add the market data and trading servers to Claude Code ```bash claude mcp add truemarkets-marketdata --transport http https://mcp.truemarkets.co/marketdata/mcp claude mcp add truemarkets-trading --transport http https://mcp.truemarkets.co/mcp ``` --- # About Gateway Source: https://docs.truemarkets.co/gateway Add trading to your app through the API. You create a user for each of your customers, and your servers place orders on their behalf from that user's own wallets. If you want to trade your own account from code, use the [Trading API](https://docs.truemarkets.co/trading-api). [Open the developer console](https://console.truemarkets.co/)[Getting started · about 15 min](https://docs.truemarkets.co/gateway/quickstart) > **Real funds** > > Every trade moves real funds on-chain. A first trade costs a few dollars of USDC, and we pay the network fees. ## Where Gateway fits Your app Your organization API key from the developer console You keep your own login and customer records. Your customers never sign in to True Markets. Your users cust\_7781SOLEVM cust\_7782SOLEVM cust\_7783SOLEVM Your own ids. Each user gets a Solana and an EVM wallet when you create it. True Markets Gateway orders · transfers balances · transactions You call it. We run the wallets and the trades. What they can trade Crypto, stablecoins and tokenized stocks The catalog lists every asset. [GET /assets](https://docs.truemarkets.co/gateway/what-you-can-trade) Every wallet transaction needs two signatures: yours, made with the signer key on that user, and ours. Neither of us can move a user's funds alone. ## How a trade works Your servers mint an organization token from your API key and send it on every call, with `TM-On-Behalf-Of` naming the user. Placing an order or transfer returns unsigned wallet transactions, called payloads. You sign each one with your signer key and post the signatures to execute. Nothing moves until you do. - TypeScript - Python - curl Place an order for a user, sign the payloads, execute ```typescript const forUser = { "TM-On-Behalf-Of": userId }; const order = await post( "/v1/gateway/orders", { asset_id: assetId, qty: "2", qty_unit: "quote", side: "buy", type: "market", }, forUser, ); const signatures = await Promise.all( order.payloads.map((p) => stamp(p.payload)), ); await post( `/v1/gateway/orders/${orderId}/execute`, { signatures, auth_type: "api_key", }, forUser, ); ``` Place an order for a user, sign the payloads, execute ```python for_user = {"TM-On-Behalf-Of": user_id} order = post( "/v1/gateway/orders", { "asset_id": asset_id, "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market", }, for_user, ) signatures = [stamp(p["payload"]) for p in order["payloads"]] post( f"/v1/gateway/orders/{order_id}/execute", { "signatures": signatures, "auth_type": "api_key", }, for_user, ) ``` Place an order for a user, sign the payloads, execute ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market" }' # $SIGNATURES is the JSON array your code signed curl -s -X POST https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID/execute \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "signatures": '"$SIGNATURES"', "auth_type": "api_key" }' ``` `post()` sends your organization token. `stamp()` signs a payload with your signer key. [Getting started](https://docs.truemarkets.co/gateway/quickstart) defines both. Your serverTrue Markets Sign inwith organization API key Sign {key\_id}.{timestamp}POST /v1/auth/api-key/token Organization tokenlasts one hour, no refresh token Place the orderwith token + TM-On-Behalf-Of Create the order for a userPOST /v1/gateway/orders Unsigned payloadsone wallet transaction each Sign and executewith signer key Stamp each payloadstays on your server Execute with the signaturesPOST /v1/gateway/orders/{id}/execute Order statuscomplete, or pending until final ## What you can build - [Trading in your app](https://docs.truemarkets.co/gateway/place-orders): Your customers buy and sell without leaving your app. - [Automated strategies](https://docs.truemarkets.co/gateway/signing-keys): Your servers place orders for each customer and sign them with one signer key you hold. - [Stablecoin transfers](https://docs.truemarkets.co/gateway/send-funds): Send funds from a user's wallet to any address on the same chain. ## Start building - [Getting started](https://docs.truemarkets.co/gateway/quickstart): Create a user, fund their wallet and place a trade for them. - [How requests and signing work](https://docs.truemarkets.co/gateway/requests-and-signing): The two keys, and what each one signs. - [Gateway FAQs](https://docs.truemarkets.co/gateway/faq): Short answers to the questions teams ask first. You can also create a test user on the console's Users page and look at its wallets before you write any code. --- # Getting started with Gateway Source: https://docs.truemarkets.co/gateway/quickstart By the end you've created a user, funded their wallet, bought a small amount of SOL for them, and found the fill in your organization's transactions. It takes about 15 minutes, plus the time your USDC transfer takes. > **This guide trades real funds** > > We don't have a fully working sandbox yet, so every trade here is a real on-chain transaction. Keep the amounts small. ## Before you begin Sign in to the [developer console](https://console.truemarkets.co/) with your work email, then create your organization and an API key. The console downloads the key file; keep it out of your repo. Use a login that isn't also a personal True Markets trading account, because a trading account can't join an organization. You also need a signer key. You hold it, and it signs your users' transactions. The script generates one, or the console's Users page can make a test one. The code needs Node.js 18 or later, or Python 3.10 or later. Each step below is a part of one script, [quickstart.ts](https://docs.truemarkets.co/examples/gateway/quickstart.ts) or [quickstart.py](https://docs.truemarkets.co/examples/gateway/quickstart.py), that you run at the end. - TypeScript - Python Install, then point at your API key file ```bash npm install tsx @turnkey/api-key-stamper @turnkey/crypto export TM_KEY_FILE=path/to/your-api-key.json ``` Install, then point at your API key file ```bash pip install requests cryptography turnkey-api-key-stamper export TM_KEY_FILE=path/to/your-api-key.json ``` ## 1\. Check you can reach the API This call needs no key. It returns every asset your users can trade. The script finds SOL on Solana in this list. - TypeScript - Python - curl Find SOL in the asset catalog, no key needed ```typescript const { data: assets } = await get("/v1/gateway/assets"); const sol = assets.find( (a: any) => a.symbol === "SOL" && a.chain === "solana", ); ``` Find SOL in the asset catalog, no key needed ```python assets = get("/v1/gateway/assets")["data"] sol = next( a for a in assets if a["symbol"] == "SOL" and a["chain"] == "solana" ) ``` The asset catalog, no key needed ```bash curl -s https://api.truemarkets.co/v1/gateway/assets ``` Response200 ```json { "data": [ { "id": "495e07ac-fa3e-4179-85b7-b1e8cece3dc4", "symbol": "SOL", "chain": "solana", "venue": "defi", "…": "…" } ] } ``` ## 2\. Mint an organization token The script signs the string `{key_id}.{timestamp}` with your API key and exchanges it for an organization token. Your servers send it in `Authorization: Bearer` on every call. It lasts an hour and there's no refresh token, so mint a new one before it expires. The token also names your organization, and the script reads `organization_id` from it. The console's Overview shows the same id. - TypeScript - Python Sign the challenge and mint the token ```typescript const { key_id, private_key } = JSON.parse( readFileSync(API_KEY_FILE, "utf8"), ); const timestamp = Math.floor(Date.now() / 1000); const signature = sign( "sha256", Buffer.from(`${key_id}.${timestamp}`), { key: createPrivateKey({ key: private_key, format: "jwk" }), // r and s concatenated, which the API expects dsaEncoding: "ieee-p1363", }, ).toString("base64url"); const minted = await post("/v1/auth/api-key/token", { key_id, timestamp, signature, }); token = minted.access_token; // The token names your organization: no id to configure. const claims = JSON.parse( Buffer.from(token.split(".")[1], "base64url").toString(), ); const organizationId = claims.tm.organization_id; ``` Sign the challenge and mint the token ```python with open(API_KEY_FILE) as f: api_key = json.load(f) jwk = api_key["private_key"] private_key = ec.EllipticCurvePrivateNumbers( b64url_int(jwk["d"]), ec.EllipticCurvePublicNumbers( b64url_int(jwk["x"]), b64url_int(jwk["y"]), ec.SECP256R1(), ), ).private_key() timestamp = int(time.time()) der = private_key.sign( f"{api_key['key_id']}.{timestamp}".encode(), ec.ECDSA(hashes.SHA256()), ) # The API expects r and s concatenated. r, s = decode_dss_signature(der) signature = ( base64.urlsafe_b64encode( r.to_bytes(32, "big") + s.to_bytes(32, "big") ) .rstrip(b"=") .decode() ) minted = post( "/v1/auth/api-key/token", { "key_id": api_key["key_id"], "timestamp": timestamp, "signature": signature, }, ) token = minted["access_token"] # The token names your organization: no id to configure. claims = json.loads( base64.urlsafe_b64decode(token.split(".")[1] + "==") ) organization_id = claims["tm"]["organization_id"] ``` Response200keep access\_token ```json { "access_token": "eyJhbGciOi…", "token_type": "Bearer", "expires_in": "2026-10-01T18:11:27Z" } ``` ## 3\. Create a user One call creates the user and their Solana and EVM wallets. `external_ref_id` is your own id for this customer, and `signer_public_key` is the public half of your signer key. Keep `user_id`: it goes in `TM-On-Behalf-Of` on every call for this user. Sending the same `external_ref_id` again returns the same user, so the script is safe to rerun. A `503` means the wallets aren't ready yet, so the script sends the same request again. This user only ever signs with the key in `signer-key.json`, so keep that file and treat this user as a test user. - TypeScript - Python Load or create your signer key ```typescript // The signer key signs every wallet transaction for the users you // register it on. Keep this file: a user can only ever sign with // the key it was created with. if (!existsSync(SIGNER_KEY_FILE)) { const { publicKey, privateKey } = generateP256KeyPair(); const file = { signer_public_key: publicKey, signer_private_key: privateKey, }; writeFileSync(SIGNER_KEY_FILE, JSON.stringify(file), { mode: 0o600, }); } const signer = JSON.parse(readFileSync(SIGNER_KEY_FILE, "utf8")); const stamper = new ApiKeyStamper({ apiPublicKey: signer.signer_public_key, apiPrivateKey: signer.signer_private_key, }); const stamp = async (payload: string) => (await stamper.stamp(payload)).stampHeaderValue; ``` Load or create your signer key ```python # The signer key signs every wallet transaction for the users you # register it on. Keep this file: a user can only ever sign with # the key it was created with. if not os.path.exists(SIGNER_KEY_FILE): key = ec.generate_private_key(ec.SECP256R1()) file = { "signer_public_key": key.public_key() .public_bytes(Encoding.X962, PublicFormat.CompressedPoint) .hex(), "signer_private_key": key.private_numbers() .private_value.to_bytes(32, "big") .hex(), } descriptor = os.open( SIGNER_KEY_FILE, os.O_WRONLY | os.O_CREAT, 0o600 ) with os.fdopen(descriptor, "w") as f: json.dump(file, f) with open(SIGNER_KEY_FILE) as f: signer = json.load(f) stamper = ApiKeyStamper( ApiKeyStamperConfig( api_public_key=signer["signer_public_key"], api_private_key=signer["signer_private_key"], ) ) def stamp(payload): return stamper.stamp(payload).stamp_header_value ``` - TypeScript - Python Create the user and their wallets ```typescript let user; for (let attempt = 1; ; attempt++) { try { user = await post( `/v1/account/organizations/${organizationId}/users`, { external_ref_id: EXTERNAL_REF_ID, signer_public_key: signer.signer_public_key, }, ); if (user.wallets.length) break; } catch (err: any) { // A 503 means the wallets aren't ready yet. The same // request finishes them. if (err.status !== 503 || attempt === 5) throw err; } await sleep(2000); } const userId = user.user_id; const forUser = { "TM-On-Behalf-Of": userId }; const solanaAddress = user.wallets.find( (w: any) => w.chain_family === "solana", ).address; ``` Create the user and their wallets ```python for attempt in range(1, 6): try: user = post( f"/v1/account/organizations/{organization_id}/users", { "external_ref_id": EXTERNAL_REF_ID, "signer_public_key": signer[ "signer_public_key" ], }, ) if user["wallets"]: break except ApiError as err: # A 503 means the wallets aren't ready yet. The same # request finishes them. if err.status != 503 or attempt == 5: raise time.sleep(2) user_id = user["user_id"] for_user = {"TM-On-Behalf-Of": user_id} solana_address = next( w["address"] for w in user["wallets"] if w["chain_family"] == "solana" ) ``` Response201keep user\_id and the Solana address ```json { "user_id": "9c1e7a52-4d3b-4f8e-a6b7-1c2d3e4f5a6b", "external_ref_id": "quickstart_user_1", "created_at": "2026-10-01T17:02:11Z", "wallets": [ { "address": "7Gk2…Qm9p", "chain_family": "solana" }, { "address": "0x4a8f…c21e", "chain_family": "evm" } ] } ``` ## 4\. Fund the Solana wallet The script prints the user's Solana address and waits. Send about 3 USDC to it on Solana. The order spends 2 of it, and we pay the network fees. > **Send it on Solana** > > USDC sent on any other network won't reach this wallet. - TypeScript - Python Wait until the wallet holds enough USDC ```typescript const usdcAvailable = async () => { const { data } = await get("/v1/gateway/balances", forUser); const usdc = data.find( (b: any) => b.symbol === "USDC" && b.chain === "solana", ); return Number(usdc?.available ?? 0); }; if ((await usdcAvailable()) < Number(ORDER_USDC)) { console.log( `… send about 3 USDC on Solana to ${solanaAddress}`, ); while ((await usdcAvailable()) < Number(ORDER_USDC)) await sleep(10_000); } ``` Wait until the wallet holds enough USDC ```python def usdc_available(): balances = get("/v1/gateway/balances", for_user)["data"] usdc = next( ( b for b in balances if b["symbol"] == "USDC" and b["chain"] == "solana" ), None, ) return float(usdc["available"]) if usdc else 0.0 if usdc_available() < float(ORDER_USDC): print( f"… send about 3 USDC on Solana to {solana_address}" ) while usdc_available() < float(ORDER_USDC): time.sleep(10) ``` ## 5\. Place the order and execute The script places a market buy for 2 USDC of SOL. The response carries the quote and the payloads, the unsigned wallet transactions to sign. The script signs each one with your signer key and executes right away. A quote lasts about two minutes, so doing it in one go keeps it from expiring. If execute still returns `quote_stale`, run the script again. Execute usually returns `complete`. A buy that first moves funds between chains returns `pending`, and the script reads the order until it's final. If `order_id` comes back empty, no order was created, and `quote.issues` says why. - TypeScript - Python Place the order, sign the payloads, execute ```typescript const order = await post( "/v1/gateway/orders", { asset_id: sol.id, qty: ORDER_USDC, qty_unit: "quote", side: "buy", type: "market", }, forUser, ); // An empty order_id means no order was created, and // quote.issues says why. if (!order.order_id) throw new Error( `no order: ${JSON.stringify(order.quote?.issues)}`, ); // Sign each payload and execute straight away, before the // quote expires. const signatures = await Promise.all( order.payloads.map((p: any) => stamp(p.payload)), ); const executed = await post( `/v1/gateway/orders/${order.order_id}/execute`, { signatures, auth_type: "api_key" }, forUser, ); ``` Place the order, sign the payloads, execute ```python order = post( "/v1/gateway/orders", { "asset_id": sol["id"], "qty": ORDER_USDC, "qty_unit": "quote", "side": "buy", "type": "market", }, for_user, ) # An empty order_id means no order was created, and # quote.issues says why. if not order.get("order_id"): raise RuntimeError( f"no order: {order.get('quote', {}).get('issues')}" ) # Sign each payload and execute straight away, before the # quote expires. signatures = [stamp(p["payload"]) for p in order["payloads"]] executed = post( f"/v1/gateway/orders/{order['order_id']}/execute", { "signatures": signatures, "auth_type": "api_key", }, for_user, ) ``` Response200from execute ```json { "status": "complete" } ``` ## 6\. See the fill The script reads the order back for the user, then finds the same fill in your organization's transactions, which list every user's orders and transfers. That call has no `TM-On-Behalf-Of`, because it already covers all your users. The row's `status` reads `completed`, the word the transactions list uses for anything finished. - TypeScript - Python Read the order, then find it in your transactions ```typescript let filled = executed; while ( !["complete", "canceled", "failed"].includes(filled.status) ) { await sleep(2000); filled = await get( `/v1/gateway/orders/${order.order_id}`, forUser, ); } console.log( `✓ order ${filled.status} ` + `${filled.executed_qty ?? ""} SOL for ${ORDER_USDC} USDC`, ); // Your organization's transactions cover every user, so this // call has no header. const feed = await get( `/v1/gateway/organizations/${organizationId}/transactions` + "?type=order", ); const row = feed.data.find((t: any) => t.id === order.order_id); ``` Read the order, then find it in your transactions ```python filled = executed while filled["status"] not in ( "complete", "canceled", "failed", ): time.sleep(2) filled = get( f"/v1/gateway/orders/{order['order_id']}", for_user ) print( f"✓ order {filled['status']} " f"{filled.get('executed_qty', '')} SOL for {ORDER_USDC} USDC" ) # Your organization's transactions cover every user, so this # call has no header. feed = get( f"/v1/gateway/organizations/{organization_id}/transactions" "?type=order" ) row = next( (t for t in feed["data"] if t["id"] == order["order_id"]), None, ) ``` ## Run it Run the script. You can run it again: it reuses the same user, and each run buys another 2 USDC of SOL while the wallet has funds. - TypeScript - Python Run the script ```bash npx tsx quickstart.ts ``` Run the script ```bash python quickstart.py ``` Example output ```text ✓ token expires 2026-10-01T18:11:27Z ✓ user 9c1e7a52-4d3b-4f8e-a6b7-1c2d3e4f5a6b solana 7Gk2…Qm9p … send about 3 USDC on Solana to 7Gk2…Qm9p ✓ funded ✓ order complete 0.0102 SOL for 2 USDC ✓ found in your organization's transactions (completed) ``` ## Where to go next - [How requests and signing work](https://docs.truemarkets.co/gateway/requests-and-signing): the two keys, what each signs, and signing in any language. - [Signer keys](https://docs.truemarkets.co/gateway/signing-keys): decide where the key lives before you create real users. - [Place orders for a user](https://docs.truemarkets.co/gateway/place-orders): limit orders, listing and canceling. Every error response includes a `request_id`. Include it when you email [support@truemarkets.co](mailto:support@truemarkets.co), so we can find the request. --- # How requests and signing work Source: https://docs.truemarkets.co/gateway/requests-and-signing Gateway uses two keys for two different jobs, and they never swap. Your organization API key proves who is calling. Your signer key approves each transaction that moves funds out of a user's wallet. Your serverTrue Markets Sign inwith organization API key Sign {key\_id}.{timestamp}POST /v1/auth/api-key/token Organization tokenlasts one hour, no refresh token Place the orderwith token + TM-On-Behalf-Of Create the order for a userPOST /v1/gateway/orders Unsigned payloadsone wallet transaction each Sign and executewith signer key Stamp each payloadstays on your server Execute with the signaturesPOST /v1/gateway/orders/{id}/execute Order statuscomplete, or pending until final ## Which key does what | | Organization API key | Signer key | | --- | --- | --- | | Proves | that the request comes from your organization | that you approve one wallet transaction | | Comes from | the [developer console](https://console.truemarkets.co/) | you generate it; the console can make a test one | | Its public half lives | on your organization | on each user, as `signer_public_key` | | Signs | the string `{key_id}.{timestamp}` | each `payload` an order, cancel or transfer returns | | Sent as | `signature` on `POST /v1/auth/api-key/token` | `signatures[]` on an execute call | A signature made with the wrong key is rejected, so keep the two apart in your code. ## Authenticate a request To sign in, sign the string `{key_id}.{timestamp}` with your organization API key and exchange the signature for an organization token at `POST /v1/auth/api-key/token`. The token lasts an hour and has no refresh token, so sign in again before it expires. 1. Use the current Unix time in seconds. It must be within 30 seconds of ours. 2. Sign with ES256, which is ECDSA over P-256 with SHA-256. 3. Send `r` and `s` concatenated, base64url-encoded, as `signature`. Send the token as `Authorization: Bearer` on every call. A call that acts for one of your users adds `TM-On-Behalf-Of` with their `user_id`. Calls about your organization as a whole, such as creating users or reading organization transactions, leave the header out. [Getting started, step 2](https://docs.truemarkets.co/gateway/quickstart#2-mint-an-organization-token) has the signing code. - TypeScript - Python - curl The helpers add both headers ```typescript const balances = await get("/v1/gateway/balances", forUser); ``` The helpers add both headers ```python balances = get("/v1/gateway/balances", for_user) ``` The helpers add both headers ```bash curl -s https://api.truemarkets.co/v1/gateway/balances \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" ``` ## Sign a wallet transaction Anything that moves funds, such as an order, a cancel on-chain or a transfer, comes back with unsigned `payloads`. Each payload is one wallet transaction, and nothing moves until you sign it and execute. You can sign with any P-256 library: 1. Take each `payload` string exactly as the response returned it. Don't parse or re-serialize it. 2. Sign it with ECDSA over P-256 and SHA-256, using your signer key. Encode the signature as DER, then as hex. 3. Put it in a JSON object with your compressed public key, as 66 hex characters, and the scheme `SIGNATURE_SCHEME_TK_API_P256`. 4. Base64url-encode that JSON. The result is one entry in `signatures[]`, in the same order as `payloads`. 5. Post `signatures` to the execute call with `auth_type: "api_key"`. The wallets run on Turnkey, which checks this signature and adds our approval before the transaction reaches the chain. [Signer keys](https://docs.truemarkets.co/gateway/signing-keys) covers where to keep the key. One signatures\[\] entry, before base64url encoding ```json { "publicKey": "02a1b2c3…7e8f90", "signature": "3045022100…", "scheme": "SIGNATURE_SCHEME_TK_API_P256" } ``` - TypeScript - Python stamp.ts: node:crypto, no dependencies ```typescript import { createECDH, createPrivateKey, sign } from "node:crypto"; import { readFileSync } from "node:fs"; // A signer key file with hex keys, the format the console's // Users page downloads. const { signer_private_key } = JSON.parse( readFileSync(process.env.SIGNER_KEY_FILE!, "utf8"), ); const ecdh = createECDH("prime256v1"); ecdh.setPrivateKey(Buffer.from(signer_private_key, "hex")); const point = ecdh.getPublicKey(); // 0x04 || x || y const key = createPrivateKey({ format: "jwk", key: { kty: "EC", crv: "P-256", d: Buffer.from(signer_private_key, "hex").toString( "base64url", ), x: point.subarray(1, 33).toString("base64url"), y: point.subarray(33).toString("base64url"), }, }); const publicKey = ecdh.getPublicKey("hex", "compressed"); export function stamp(payload: string): string { const signature = sign( "sha256", Buffer.from(payload), key, ).toString("hex"); const json = JSON.stringify({ publicKey, signature, scheme: "SIGNATURE_SCHEME_TK_API_P256", }); return Buffer.from(json).toString("base64url"); } ``` stamp.py: cryptography, no Turnkey package ```python import base64 import json import os from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec # A signer key file with hex keys, the format the console's # Users page downloads. with open(os.environ["SIGNER_KEY_FILE"]) as f: signer = json.load(f) key = ec.derive_private_key( int(signer["signer_private_key"], 16), ec.SECP256R1() ) public_key = ( key.public_key() .public_bytes( serialization.Encoding.X962, serialization.PublicFormat.CompressedPoint, ) .hex() ) def stamp(payload: str) -> str: signature = key.sign( payload.encode(), ec.ECDSA(hashes.SHA256()) ) envelope = json.dumps( { "publicKey": public_key, "signature": signature.hex(), "scheme": "SIGNATURE_SCHEME_TK_API_P256", } ) return ( base64.urlsafe_b64encode(envelope.encode()) .decode() .rstrip("=") ) ``` Download [stamp.ts](https://docs.truemarkets.co/examples/gateway/stamp.ts) or [stamp.py](https://docs.truemarkets.co/examples/gateway/stamp.py). ### Or use a Turnkey library | Language | Library | Call | | --- | --- | --- | | TypeScript | [`@turnkey/api-key-stamper`](https://www.npmjs.com/package/@turnkey/api-key-stamper) | `new ApiKeyStamper({ apiPublicKey, apiPrivateKey }).stamp(payload)` | | Go | [`github.com/tkhq/go-sdk/v2`](https://pkg.go.dev/github.com/tkhq/go-sdk/v2) | `NewAPIKeyStamper(privateKey).Stamp(ctx, payload)` | | Python | [`turnkey-api-key-stamper`](https://pypi.org/project/turnkey-api-key-stamper/) | `ApiKeyStamper(ApiKeyStamperConfig(api_public_key, api_private_key)).stamp(payload)` | | Rust | [`turnkey_api_key_stamper`](https://crates.io/crates/turnkey_api_key_stamper) | see the crate docs | Each one returns the same base64url stamp. Pass the payload string exactly as you received it. [Getting started, step 5](https://docs.truemarkets.co/gateway/quickstart#5-place-the-order-and-execute) uses the TypeScript and Python ones end to end. Next: [Signer keys](https://docs.truemarkets.co/gateway/signing-keys) --- # Signer keys Source: https://docs.truemarkets.co/gateway/signing-keys Your signer key signs every trade and transfer from a user's wallet. Where it lives decides what you can build, and you choose it before you create the user. [How requests and signing work](https://docs.truemarkets.co/gateway/requests-and-signing) covers the other key, the one that signs you in. ## What the signer key controls A signer key is a P-256 key pair whose public half you register on a user when you create them. Every trade or transfer from that user's wallet needs two approvals: a signature from the signer key, and an approval from True Markets. Neither side can move funds on its own. The wallet itself runs on Turnkey. Its private key is generated inside a secure enclave, a sealed environment that no person can read from, and it never leaves. Nobody holds that key: not Turnkey, not True Markets, not you. What you hold is signing authority. Where the signer key lives shapes the product you build. There are two common setups. One key for your organization Your servers one signer key Wallets cust\_7781 cust\_7782 cust\_7783 Orders go through without prompting the user. That one key can sign for every user. One key per user Each user's device key A key B key C Wallets cust\_7781 cust\_7782 cust\_7783 Nothing moves without that user. You build a signing step and a recovery flow. ## One key for your organization Generate one key pair, register the same public key on every user, and sign on your servers. Orders go through without prompting the user, which suits one-tap buying and automated strategies. That key can sign for any of your users, so guard it the way you guard a production database credential. ## One key per user Each user gets their own key pair, and you register its public key. Every order is signed with that user's key, so nothing moves without them, and you never hold a key that can spend their funds. You and your user decide how the key is created, stored and recovered. The trade-off is a signing step in your product, and a recovery flow you design. ## Register the signer key You choose per user, when you create them, by sending `signer_public_key`: a 33-byte compressed P-256 public key as 66 hex characters. > **Losing the signer key loses the wallet** > > There's no way to rotate a signer key yet. If you lose it, that user's wallet can never sign again, and we can't recover it. Keep the key in an HSM or a secrets manager, with a backup, and decide where it lives before you create real users. ## How the signer key signs [How requests and signing work](https://docs.truemarkets.co/gateway/requests-and-signing) shows the signature format, a version with no dependencies, and the Turnkey libraries that build it for you. Next: [User accounts](https://docs.truemarkets.co/gateway/create-a-user) --- # User accounts Source: https://docs.truemarkets.co/gateway/create-a-user A user is one of your customers, created by your organization through the API. Each user has a `user_id` and their own wallets. You send the `user_id` in the `TM-On-Behalf-Of` header for everything you do on their behalf. Before you start, you need an [organization token](https://docs.truemarkets.co/gateway/quickstart#2-mint-an-organization-token) and the public half of your signer key. The code uses the `post()` and `get()` helpers from [Getting started](https://docs.truemarkets.co/gateway/quickstart). Decide where that key lives first, because you can't change it after the user exists. [Signer keys](https://docs.truemarkets.co/gateway/signing-keys) explains the choice. ## Create the user `external_ref_id` is your own id for the customer: up to 64 letters, digits, `_` or `-`, matched case-sensitively. `signer_public_key` is the public half of your signer key: a compressed P-256 key, as 66 hex characters. Store the `user_id` we return. Sending the same `external_ref_id` again returns the existing user with `200`, so you can retry safely. The user keeps the signer key it was created with, even if the retry sends a different one. Each organization can create a limited number of users. Past the limit you get a `422`, and [support](mailto:support@truemarkets.co) can raise it for you. > **A 503 means the wallets aren't ready yet** > > Send the same request again. It finishes creating the wallets and returns the user. A user whose `wallets` array is empty is in this state. - TypeScript - Python - curl Create the user and their wallets ```typescript const user = await post( `/v1/account/organizations/${organizationId}/users`, { external_ref_id: "cust_7781", signer_public_key: signerPublicKey, }, ); ``` Create the user and their wallets ```python user = post(f"/v1/account/organizations/{organization_id}/users", { "external_ref_id": "cust_7781", "signer_public_key": signer_public_key, }) ``` Create the user and their wallets ```bash curl -s -X POST https://api.truemarkets.co/v1/account/organizations/$ORGANIZATION_ID/users \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "external_ref_id": "cust_7781", "signer_public_key": "'"$SIGNER_PUBLIC_KEY"'" }' ``` Response201200 if the reference already existed ```json { "user_id": "9c1e7a52-4d3b-4f8e-a6b7-1c2d3e4f5a6b", "external_ref_id": "cust_7781", "created_at": "2026-09-28T17:02:11Z", "wallets": [ { "address": "7Gk2…Qm9p", "chain_family": "solana" }, { "address": "0x4a8f…c21e", "chain_family": "evm" } ] } ``` ## What to store | Field | Why | | --- | --- | | `user_id` | goes in `TM-On-Behalf-Of` on every call for this customer | | `wallets[].address` | where you fund them. Each wallet has a `chain_family`: the `solana` wallet takes Solana tokens, and the `evm` wallet has one address that works on every EVM chain we support | ## Admins are not users Admins are the people on your team who sign in to the developer console. Users are your customers, and they never sign in to True Markets. If the `external_ref_id` is already in use in your organization, for example by one of your admins' records, we return `409`. ## List and read users List returns your users newest first, 50 per page by default and up to 100 with `limit`. Page with the `cursor` from `pagination.next_cursor`. Pass `external_ref_id` to look up a user by your own id. An id that matches nothing returns an empty page. Read one user by `user_id` to get their wallet addresses again. - TypeScript - Python - curl Look a user up by your own id ```typescript const page = await get( `/v1/account/organizations/${organizationId}/users?external_ref_id=cust_7781`, ); ``` Look a user up by your own id ```python page = get( f"/v1/account/organizations/{organization_id}/users?external_ref_id=cust_7781", ) ``` Look a user up by your own id ```bash curl -s -G https://api.truemarkets.co/v1/account/organizations/$ORGANIZATION_ID/users \ -H "Authorization: Bearer $ORG_TOKEN" \ -d external_ref_id=cust_7781 ``` Response200data and a cursor ```json { "data": [ { "user_id": "9c1e7a52-4d3b-4f8e-a6b7-1c2d3e4f5a6b", "external_ref_id": "cust_7781", "created_at": "2026-09-28T17:02:11Z", "wallets": ["…"] } ], "pagination": { "next_cursor": null, "limit": 50 } } ``` - TypeScript - Python - curl Read one user ```typescript const user = await get( `/v1/account/organizations/${organizationId}/users/${userId}`, ); ``` Read one user ```python user = get( f"/v1/account/organizations/{organization_id}/users/{user_id}", ) ``` Read one user ```bash curl -s https://api.truemarkets.co/v1/account/organizations/$ORGANIZATION_ID/users/$USER_ID \ -H "Authorization: Bearer $ORG_TOKEN" ``` Next: [Funding](https://docs.truemarkets.co/gateway/fund-a-wallet) --- # Fund a user's wallet Source: https://docs.truemarkets.co/gateway/fund-a-wallet Fund a user by sending tokens on-chain to their wallet address. You can send from any wallet or exchange you already use. ## Find the address Each user has one Solana address and one EVM address. You get both in `wallets[]` when you create the user, and again when you [read the user](https://docs.truemarkets.co/gateway/create-a-user#list-and-read-users). The EVM address is the same on every EVM chain we support. Every asset in [the catalog](https://docs.truemarkets.co/gateway/what-you-can-trade) has a `chain`, which is the network it settles on. Fund the wallet on that network. > **Match the network to the address** > > Tokens sent on the wrong network can be lost. ## What to send Send the stablecoin a market buy spends, on the chain the user will trade on, such as USDC on Solana. An order's quote names that asset in `quote_asset`. We pay the network fees for these wallets, so they need nothing else. ## Check the balance The balance updates once the network confirms your transfer. Read it with `TM-On-Behalf-Of`. `available` is what the user can trade with now. - TypeScript - Python - curl Read one user's balances ```typescript const balances = await get("/v1/gateway/balances", forUser); ``` Read one user's balances ```python balances = get("/v1/gateway/balances", for_user) ``` Read one user's balances ```bash curl -s https://api.truemarkets.co/v1/gateway/balances \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" ``` Response200one row per asset; available is what they can trade ```json { "data": [ { "asset_id": "b3d9e5f2-1a4c-4e7b-9f0d-2c8a6b3e5f91", "symbol": "USDC", "chain": "solana", "venue": "defi", "available": "3.000000", "total": "3.000000", "…": "…" } ] } ``` Next: [Send funds](https://docs.truemarkets.co/gateway/send-funds) --- # Send funds from a user's wallet Source: https://docs.truemarkets.co/gateway/send-funds Send tokens from a user's wallet to any address on the same chain. A transfer works like an order: you create it, sign the payloads it returns, then execute it. A payload is an unsigned transaction. ## Create the transfer Send the `asset_id` from the catalog, the amount in that asset with `qty_unit: "base"`, and the destination address in `to`. Leave out `network`; the transfer uses the asset's chain. Add `TM-On-Behalf-Of` to name the user whose wallet the funds leave. A transfer for more than the wallet holds is rejected with `403`, so read the balance first. The transfer comes back with status `awaiting_signature` and its `payloads`. Keep `id` and `payloads`. - TypeScript - Python - curl Create a transfer of 5 USDC on Solana ```typescript const transfer = await post( "/v1/gateway/transfers", { asset_id: assetId, qty: "5", qty_unit: "base", to: "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", }, forUser, ); ``` Create a transfer of 5 USDC on Solana ```python transfer = post( "/v1/gateway/transfers", { "asset_id": asset_id, "qty": "5", "qty_unit": "base", "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", }, for_user, ) ``` Create a transfer of 5 USDC on Solana ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/transfers \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "5", "qty_unit": "base", "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }' ``` Response201keep id and payloads ```json { "id": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a", "status": "awaiting_signature", "asset_symbol": "USDC", "chain": "solana", "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", "qty": "5", "payloads": [{ "digest": "8e1a…4f2b", "payload": "{\"organizationId\":\"…\",…}" }], "…": "…" } ``` ## Sign and execute Sign each payload with your signer key, the same way you sign an order, and post the signatures in the order the payloads came back. [Getting started](https://docs.truemarkets.co/gateway/quickstart) has the signing code. `auth_type: "api_key"` tells us the signer key made the signatures, not your organization API key. > **Transfers are final** > > Once the chain confirms a transfer, it can't be reversed. Check the address and the network before you execute. - TypeScript - Python - curl Sign the payloads, then execute ```typescript const signatures = await Promise.all( transfer.payloads.map((p) => stamp(p.payload)), ); const done = await post( `/v1/gateway/transfers/${transferId}/execute`, { signatures, auth_type: "api_key", }, forUser, ); ``` Sign the payloads, then execute ```python signatures = [stamp(p["payload"]) for p in transfer["payloads"]] done = post( f"/v1/gateway/transfers/{transfer_id}/execute", { "signatures": signatures, "auth_type": "api_key", }, for_user, ) ``` Sign the payloads, then execute ```bash # $SIGNATURES is the JSON array your code signed curl -s -X POST https://api.truemarkets.co/v1/gateway/transfers/$TRANSFER_ID/execute \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "signatures": '"$SIGNATURES"', "auth_type": "api_key" }' ``` Response200the transfer, finished ```json { "id": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a", "status": "completed", "sent": "5", "received": "5", "explorer_url": "https://solscan.io/tx/4Hn8…Yt3z", "…": "…" } ``` Execute returns once the transaction has landed, with status `completed`. If it fails, the transfer is marked `failed` and you create a new one. Executing the same transfer again returns `409`. To look a transfer up later, call `GET /v1/gateway/transfers/{id}` with the header. Next: [Trade for a user](https://docs.truemarkets.co/gateway/trade-for-a-user) --- # Trade for a user Source: https://docs.truemarkets.co/gateway/trade-for-a-user To trade for a user, make the request with your organization token and add `TM-On-Behalf-Of: `. The request runs against that user's wallet, and everything else about it stays the same. ## How an order runs Place the order, sign each payload with your signer key, execute, then read the order until its status is final. [Place orders for a user](https://docs.truemarkets.co/gateway/place-orders) walks through each call, and [Getting started](https://docs.truemarkets.co/gateway/quickstart#5-place-the-order-and-execute) shows the signing code. ## Send the header The value is the `user_id` you got from [User accounts](https://docs.truemarkets.co/gateway/create-a-user). Send the header once per request. In code, keep it in one object and pass it to every call for that user, as the `post()` and `get()` helpers from [Getting started](https://docs.truemarkets.co/gateway/quickstart) do. The request uses your organization's permissions and acts only on that user's wallet. - TypeScript - Python - curl An order for one of your users ```typescript const forUser = { "TM-On-Behalf-Of": userId }; const order = await post( "/v1/gateway/orders", { asset_id: assetId, qty: "2", qty_unit: "quote", side: "buy", type: "market", }, forUser, ); ``` An order for one of your users ```python for_user = {"TM-On-Behalf-Of": user_id} order = post( "/v1/gateway/orders", { "asset_id": asset_id, "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market", }, for_user, ) ``` An order for one of your users ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market" }' ``` ## Where it works These are the calls you use most. The [API reference](https://docs.truemarkets.co/api/gateway/true-markets-gateway-api) marks the header on every endpoint that accepts it. | Route | Header | | --- | --- | | `/orders` and every route under it | accepted | | `/transfers` and every route under it | accepted | | `GET /balances`, `GET /portfolio`, `GET /positions` | accepted | | `GET /transactions` | accepted | | `GET /organizations/{organization_id}/transactions` | rejected: the feed already covers every user | ## When it fails | Status | Meaning | | --- | --- | | `400` | You sent the header with an API trader's token instead of an organization token, sent it twice, or the value isn't a UUID. | | `401` | The token is missing, invalid or expired, or you called a user route with an organization token and no header. | | `403` | The user isn't in your organization, or trading that asset isn't available in the country your request comes from. `GET /assets/availability` shows what's available. | | `201` with an empty `order_id` | The wallet can't fund the buy. `quote.issues` says why, and no order was created. | | `422` with `insufficient_balance` | The wallet can't fund a sell or a perpetual order. | | `422` with `quote_stale` | The quote expired or the price moved before execute landed. Place the order again. | [Errors](https://docs.truemarkets.co/developer-resources/errors) shows the error body and which errors to retry. The [API reference](https://docs.truemarkets.co/api/gateway/true-markets-gateway-api) lists every code for each endpoint. Next: [Place orders for a user](https://docs.truemarkets.co/gateway/place-orders) --- # Place orders for a user Source: https://docs.truemarkets.co/gateway/place-orders Every order is a `POST /v1/gateway/orders` with an asset, a size and a type. This page covers the calls you make most: place an order, list and read orders, and cancel one. Every call here carries your organization token and `TM-On-Behalf-Of`, so it acts on that user’s wallet. [Getting started](https://docs.truemarkets.co/gateway/quickstart) walks through signing and executing a market buy. ## Identify the asset Send the asset's `id` from [the catalog](https://docs.truemarkets.co/gateway/what-you-can-trade) as `asset_id`. Older integrations name the asset with `base_asset` instead. Use `asset_id` for new code. ## Place a market order A market order executes now at the best available price, so it takes no `price`. Size a buy by what you spend, with `qty_unit: "quote"`. Size a sell by what you sell, with `qty_unit: "base"`. The response carries the quote and the `payloads` to sign. Nothing moves until you sign them and execute. - TypeScript - Python - curl Buy 2 USDC worth of an asset ```typescript const order = await post( "/v1/gateway/orders", { asset_id: assetId, qty: "2", qty_unit: "quote", side: "buy", type: "market", }, forUser, ); ``` Buy 2 USDC worth of an asset ```python order = post( "/v1/gateway/orders", { "asset_id": asset_id, "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market", }, for_user, ) ``` Buy 2 USDC worth of an asset ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market" }' ``` ## Limit orders A limit order rests at your `price` until it fills or you cancel it, sized with `qty_unit: "base"`. Limit orders aren't available for every asset yet; the [create order reference](https://docs.truemarkets.co/api/gateway/create-order) has the current rules. ## List and read orders `GET /v1/gateway/orders` lists the user's orders, newest first. Filter with `status` (comma-separated), and page with `limit` (up to 100) and the `cursor` from `pagination.next_cursor`. An order you created but never executed doesn't appear in the list. Read it by its id. - TypeScript - Python - curl Resting orders, then one order by its id ```typescript const orders = await get( "/v1/gateway/orders?status=active", forUser, ); const order = await get(`/v1/gateway/orders/${orderId}`, forUser); ``` Resting orders, then one order by its id ```python orders = get("/v1/gateway/orders?status=active", for_user) order = get(f"/v1/gateway/orders/{order_id}", for_user) ``` Resting orders, then one order by its id ```bash curl -s -G https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -d status=active curl -s https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" ``` ## Cancel an order Canceling a resting order is a wallet transaction too, so it works like placing one: the cancel call returns `payloads`, you sign them, and you execute the cancel. - TypeScript - Python - curl Cancel a resting order, sign, execute ```typescript const cancel = await post( `/v1/gateway/orders/${orderId}/cancel`, {}, forUser, ); const signatures = await Promise.all( cancel.payloads.map((p) => stamp(p.payload)), ); await post( `/v1/gateway/orders/${orderId}/cancel/execute`, { signatures, auth_type: "api_key", }, forUser, ); ``` Cancel a resting order, sign, execute ```python cancel = post( f"/v1/gateway/orders/{order_id}/cancel", {}, for_user, ) signatures = [stamp(p["payload"]) for p in cancel["payloads"]] post( f"/v1/gateway/orders/{order_id}/cancel/execute", { "signatures": signatures, "auth_type": "api_key", }, for_user, ) ``` Cancel a resting order, sign, execute ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID/cancel \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" # $SIGNATURES is the JSON array your code signed curl -s -X POST https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID/cancel/execute \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" \ -H "Content-Type: application/json" \ -d '{ "signatures": '"$SIGNATURES"', "auth_type": "api_key" }' ``` ## Sizing In a pair such as SOL/USDC, the base asset is the one you buy or sell (SOL), and the quote asset is the one you price it in (USDC). `qty_unit` says which one `qty` counts. Every quantity is a positive decimal string. | `qty_unit` | Meaning | Allowed on | | --- | --- | --- | | `quote` | amount of the quote asset to spend, "10 USDC of SOL" | market buy | | `base` | amount of the base asset, "0.01 SOL" | market sell, limit orders | ## Follow an order to a final state Order status, from create to a final state createinitializedsign + execute - completefilled - activea limit order, resting - pendingstill settling - failed cancelcancel\_pendingcanceled final won't changeother read the order again An order that isn't in a final state can still change, so read it again until it is. If `order_id` comes back empty, no order was created, and `quote.issues` says why. Nothing moves until you execute, so you can retry a create that timed out. ## When an order fails | Status | Meaning | | --- | --- | | `201` with an empty `order_id` | The wallet can't fund the buy. `quote.issues` says why, and no order was created. | | `422` with `insufficient_balance` | The wallet can't fund a sell or a perpetual order. | | `422` with `quote_stale` | The quote expired or the price moved before execute landed. Place the order again. | | `403` | The user isn't in your organization, or the asset isn't available in the country your request comes from. `GET /assets/availability` shows what's available. | | `409` with `already_submitted` on execute | The transaction already landed. Read the order instead of executing again. | [Errors](https://docs.truemarkets.co/developer-resources/errors) has the error body and the rest of the codes. ## Perpetuals A perpetual, or perp, is a contract that tracks an asset's price and never expires. Perpetuals aren't available for every asset; the ones that are appear in the catalog with `type: "perp"`. To open a position, send `leverage`, up to the asset's `perp.max_leverage` in the catalog. Margin is isolated. With `qty_unit: "quote"`, `qty` is the margin you commit and the position's size is `qty` times `leverage`; with `"base"`, `qty` is the position's size. To close a position, send `reduce_only: true` instead. A `trigger` object turns the order into a take-profit or stop-loss that fires when the mark price crosses `trigger.price`. The [create order reference](https://docs.truemarkets.co/api/gateway/create-order) has every rule. Next: [What you can trade](https://docs.truemarkets.co/gateway/what-you-can-trade) --- # What your users can trade Source: https://docs.truemarkets.co/gateway/what-you-can-trade The asset catalog lists everything you can trade. Each asset says what it is, where it trades and which network it settles on. You can read it without a token, so you can call it before you create a single user. Your users can trade the assets whose `venue` is `defi`. ## List assets Without filters, the catalog returns spot crypto. Perpetual futures and tokenized stocks are opt-in: pass `product_type=perp` (it filters on each asset's `type`) or `asset_class=stock` to list them, or a comma-separated list such as `asset_class=crypto,stock` to get both. Keep each asset's `id`. It's the `asset_id` you place orders with. The catalog comes back in one response, with no paging. Read chains and listings from the catalog instead of hard-coding them. New ones appear there first. - TypeScript - Python - curl The catalog, no key needed ```typescript const assets = await get("/v1/gateway/assets"); ``` The catalog, no key needed ```python assets = get("/v1/gateway/assets") ``` The catalog, no key needed ```bash curl -s https://api.truemarkets.co/v1/gateway/assets ``` Response200keep each asset's id ```json { "data": [ { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "symbol": "SOL", "chain": "solana", "venue": "defi", "type": "spot", "asset_class": "crypto", "status": "active", "…": "…" } ] } ``` - TypeScript - Python - curl Tokenized stocks ```typescript const stocks = await get("/v1/gateway/assets?asset_class=stock"); ``` Tokenized stocks ```python stocks = get("/v1/gateway/assets?asset_class=stock") ``` Tokenized stocks ```bash curl -s -G https://api.truemarkets.co/v1/gateway/assets \ -d asset_class=stock ``` Response200a stock is a spot listing with asset\_class stock ```json { "data": [ { "id": "d4f1a2b3-6c7e-4f80-9a1b-2c3d4e5f6a70", "symbol": "AAPLC", "chain": "base", "asset_class": "stock", "…": "…" } ] } ``` ## Fields that describe an asset Four fields tell you what an asset is and where it trades. | Field | Values | What it tells you | | --- | --- | --- | | `venue` | `defi`, `cefi` | where the asset trades | | `asset_class` | `crypto`, `stock` | a token, or a tokenized stock | | `type` | `spot`, `perp` | spot, or a perpetual future | | `chain` | a chain name | the network the asset settles on. Fund the wallet on that network. | Next: [Organization transactions](https://docs.truemarkets.co/gateway/activity) --- # Organization transactions Source: https://docs.truemarkets.co/gateway/activity Transactions are your users' orders and transfers in one list, newest first. Use them to match fills to your own customer records, or to show a user their history. ## Your organization's transactions Call `GET /v1/gateway/organizations/{organization_id}/transactions` with your organization token and no `TM-On-Behalf-Of` header. It already covers every user, so it rejects the header. Each row carries the `user_id` it belongs to. The row's `type` is `order` or `transfer`, and the field with that name holds the full object in the same shape its own endpoint returns. The row's `status` is one of `pending`, `completed`, `failed` or `canceled`, the same four for orders and transfers. The order's own status, such as `complete`, is inside `order`. Filter by `user_id` (up to 100, comma-separated), `type`, `status`, and a `from` and `to` time window. Pass `user_id` to narrow the feed to some of your users. `limit` defaults to 50 and goes up to 100. To get the next page, pass the `cursor` from `pagination.next_cursor`. - TypeScript - Python - curl Completed orders since 1 September ```typescript const feed = await get( `/v1/gateway/organizations/${organizationId}/transactions?type=order&status=completed&from=2026-09-01T00:00:00Z`, ); ``` Completed orders since 1 September ```python feed = get( f"/v1/gateway/organizations/{organization_id}/transactions?type=order&status=completed&from=2026-09-01T00:00:00Z", ) ``` Completed orders since 1 September ```bash curl -s -G https://api.truemarkets.co/v1/gateway/organizations/$ORGANIZATION_ID/transactions \ -H "Authorization: Bearer $ORG_TOKEN" \ -d type=order \ -d status=completed \ -d from=2026-09-01T00:00:00Z ``` Response200each row names its user\_id ```json { "data": [ { "id": "0f9d8c7b-6a5e-4d3c-b2a1-908f7e6d5c4b", "user_id": "9c1e7a52-4d3b-4f8e-a6b7-1c2d3e4f5a6b", "type": "order", "status": "completed", "asset_symbol": "SOL", "qty": "0.0102", "explorer_url": "https://solscan.io/tx/5Ub7…Xk2q", "completed_at": "2026-09-28T17:06:52Z", "order": { "…": "the same object as GET /orders/{id}" } } ], "pagination": { "next_cursor": "eyJjcmVhdGVkX2F0Ijoi…", "limit": 50 } } ``` ## One user's transactions For one user, call `GET /v1/gateway/transactions` with the `TM-On-Behalf-Of` header. The rows have the same shape, without `user_id`. This call filters by `type` only, and pages the same way. - TypeScript - Python - curl One user's transactions ```typescript const history = await get("/v1/gateway/transactions", forUser); ``` One user's transactions ```python history = get("/v1/gateway/transactions", for_user) ``` One user's transactions ```bash curl -s https://api.truemarkets.co/v1/gateway/transactions \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "TM-On-Behalf-Of: $USER_ID" ``` Next: [Go live checklist](https://docs.truemarkets.co/gateway/go-live) --- # Gateway go-live checklist Source: https://docs.truemarkets.co/gateway/go-live We don't have a fully working sandbox yet, so every order and transfer you execute against `api.truemarkets.co` moves real funds. Keep your first trades small. A real trade also proves the whole flow end to end. Work through this checklist before you raise the amounts. ## Checklist - Your organization API key file is out of your repo and out of chat tools. Anyone with it can act as you. If it leaks, revoke it on the [developer console](https://console.truemarkets.co/)'s API keys page and create a new one. - Your signer key is in an HSM or a secrets manager, with a backup. A lost signer key can't be recovered. - One wallet holds a small amount of the stablecoin you trade with. - You placed one small market buy, then sold it back with `side: "sell"` and `qty_unit: "base"`. - You handle `422` with `quote_stale` by placing the order again. - You send the same create-user request again on a `503`. The retry finishes creating the wallets. - You mint a fresh token before `expires_in` passes, because an organization has no refresh token. - You read your organization's transactions after each trade and find the fill. [Errors](https://docs.truemarkets.co/developer-resources/errors) lists every status and which ones to retry. Next: [FAQs](https://docs.truemarkets.co/gateway/faq) --- # About Trading API Source: https://docs.truemarkets.co/trading-api The Trading API lets you trade your own True Markets account from code. You create a key at [app.truemarkets.co](https://app.truemarkets.co/), and that key signs your requests and your orders. If you're building an app for your customers, read the [Gateway documentation](https://docs.truemarkets.co/gateway). [Create an API key](https://app.truemarkets.co/settings/api-keys)[Getting started · about 10 min](https://docs.truemarkets.co/trading-api/quickstart) > **Real funds** > > Every trade moves real funds. A first trade costs a few dollars of USDC, and we pay the network fees. ## How a trade works Your code mints a token from your key file and sends it on every call. Placing an order or transfer returns unsigned wallet transactions, called payloads. You sign each one with the same key and post the signatures to execute. Nothing moves until you do. - TypeScript - Python - curl Place an order, sign the payloads, execute ```typescript const order = await post("/v1/gateway/orders", { asset_id: assetId, qty: "2", qty_unit: "quote", side: "buy", type: "market", }); const signatures = await Promise.all( order.payloads.map((p) => stamp(p.payload)), ); await post(`/v1/gateway/orders/${orderId}/execute`, { signatures, auth_type: "api_key", }); ``` Place an order, sign the payloads, execute ```python order = post("/v1/gateway/orders", { "asset_id": asset_id, "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market", }) signatures = [stamp(p["payload"]) for p in order["payloads"]] post(f"/v1/gateway/orders/{order_id}/execute", { "signatures": signatures, "auth_type": "api_key", }) ``` Place an order, sign the payloads, execute ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market" }' # $SIGNATURES is the JSON array your code signed curl -s -X POST https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID/execute \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "signatures": '"$SIGNATURES"', "auth_type": "api_key" }' ``` `post()` sends your token. `stamp()` signs a payload with your key. [Getting started](https://docs.truemarkets.co/trading-api/quickstart) defines both. Your codeTrue Markets Sign inwith your API key Sign {key\_id}.{timestamp}POST /v1/auth/api-key/token Access and refresh tokensrefresh without signing again Place the orderwith your token Create the orderPOST /v1/gateway/orders Unsigned payloadsone wallet transaction each Sign and executewith the same API key Stamp each payloadstays on your machine Execute with the signaturesPOST /v1/gateway/orders/{id}/execute Order statuscomplete, or pending until final ## Trading API use cases - A script or bot that trades your account on a schedule or on a signal. - A service that reads your balances and rebalances your holdings. ## How it differs from Gateway The Trading API and Gateway share the same endpoints, documented in the [Trading REST reference](https://docs.truemarkets.co/api/gateway/true-markets-gateway-api). What changes is whose account a request acts on. - **Your token acts as you.** A Gateway client adds a header to name which of its users a request is for. You never send it. - **Your account already exists.** You sign up at app.truemarkets.co, and the app creates your wallet when you set up a passkey, so there are no users to create. - **One key does both jobs.** The same key mints your tokens and signs your orders. A Gateway client holds two keys for those two jobs. - **You can refresh tokens.** Minting a token also returns a refresh token, so you can get a new access token without signing again. ## Get started - [Getting started](https://docs.truemarkets.co/trading-api/quickstart): Mint a token, buy a small amount of SOL and find the fill. - [Create your API key](https://docs.truemarkets.co/trading-api/api-key): Create your key at app.truemarkets.co and mint your first token. ## Trade - [Funding](https://docs.truemarkets.co/trading-api/fund): Send stablecoins to your wallet, or buy with a card. - [Place orders](https://docs.truemarkets.co/trading-api/place-orders): Market and limit orders, listing, canceling and the order lifecycle. - [Send funds](https://docs.truemarkets.co/trading-api/send-funds): Move tokens from your wallet to another address. - [Transactions](https://docs.truemarkets.co/trading-api/transactions): Your orders and transfers in one list, newest first. Before you raise your amounts, work through the [Go live checklist](https://docs.truemarkets.co/trading-api/go-live). --- # Create your API key Source: https://docs.truemarkets.co/trading-api/api-key One API key does two jobs: it mints the tokens that authenticate your calls, and it signs your orders. You create it at [app.truemarkets.co](https://app.truemarkets.co/) and keep the private half in a file. ## 1\. Create the key Sign in or create an account at [app.truemarkets.co](https://app.truemarkets.co/), then create an API key there. It's the same account you use in the app. Creating a key needs a passkey on your account. If you don't have one, the app asks you to register one first. Your browser generates the key pair and registers the public half in two places. With your account, it lets you mint tokens. On your wallet, it lets you sign orders. The browser then downloads a JSON file with your `key_id` and the private key. The private key never leaves your machine. Save the file and point `TM_KEY_FILE` at it. The key file the app downloads ```bash # The file the app downloaded, wherever you saved it export TM_KEY_FILE="$HOME/.truemarkets/api-key.json" cat $TM_KEY_FILE # { # "key_id": "a1b2c3d4-…", # "private_key": { "kty": "EC", "crv": "P-256", … }, # "algorithm": "ES256" # } ``` > **Treat the file like a password** > > Anyone with the file can mint tokens as you and sign orders from your wallet. Keep it out of git and chat tools. If it leaks, revoke it at app.truemarkets.co and create a new one. ## 2\. Mint a token A token is what authenticates your API calls. To mint one, sign the string `{key_id}.{timestamp}` with the private key and post it to `POST /v1/auth/api-key/token`. The signature is ES256, with `r` and `s` concatenated and base64url-encoded, and the timestamp is Unix seconds within 30 seconds of our clock. The response has an access token, which you send in `Authorization: Bearer` on every call, and a refresh token. [Getting started, step 2](https://docs.truemarkets.co/trading-api/quickstart#2-mint-a-token) has the code. ## 3\. Refresh the token An access token lasts an hour, and `expires_in` is when it stops working. Before then, post the refresh token to get a new access token and refresh token. Refreshing needs no signature. Each refresh returns a new refresh token, so keep the latest one. - TypeScript - Python - curl Get a new token pair with the refresh token ```typescript const minted = await post("/v1/auth/token/refresh", { refresh_token: refreshToken, }); ``` Get a new token pair with the refresh token ```python minted = post("/v1/auth/token/refresh", { "refresh_token": refresh_token, }) ``` Get a new token pair with the refresh token ```bash curl -s -X POST https://api.truemarkets.co/v1/auth/token/refresh \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "'"$REFRESH_TOKEN"'" }' ``` Response200keep the new refresh\_token ```json { "access_token": "eyJhbGciOi…", "refresh_token": "eyJhbGciOi…", "expires_in": "2026-10-01T19:11:27Z", "token_type": "Bearer" } ``` ## Sign orders with the same key The same key signs your orders, in a different format from the token challenge. [How requests and signing work](https://docs.truemarkets.co/trading-api/requests-and-signing) shows both. Next: [Getting started](https://docs.truemarkets.co/trading-api/quickstart) --- # Getting started with Trading API Source: https://docs.truemarkets.co/trading-api/quickstart By the end you've minted a token, bought a small amount of SOL, and found the fill in your transactions. It takes about 10 minutes, plus the time your USDC transfer takes. > **This guide trades real funds** > > We don't have a fully working sandbox yet, so every trade here is a real on-chain transaction. Keep the amounts small. ## Before you begin You need the key file from [Create your API key](https://docs.truemarkets.co/trading-api/api-key). It does two jobs: it signs you in, and it signs your own orders. The code needs Node.js 18 or later, or Python 3.10 or later. Each step below is a part of one script, [quickstart.ts](https://docs.truemarkets.co/examples/trading-api/quickstart.ts) or [quickstart.py](https://docs.truemarkets.co/examples/trading-api/quickstart.py), that you run at the end. - TypeScript - Python Install, then point at your key file ```bash npm install tsx @turnkey/api-key-stamper export TM_KEY_FILE=path/to/your-api-key.json ``` Install, then point at your key file ```bash pip install requests cryptography turnkey-api-key-stamper export TM_KEY_FILE=path/to/your-api-key.json ``` ## 1\. Check you can reach the API This call needs no key. It returns every asset you can trade. The script finds SOL on Solana in this list. - TypeScript - Python - curl Find SOL in the asset catalog, no key needed ```typescript const { data: assets } = await get("/v1/gateway/assets"); const sol = assets.find( (a: any) => a.symbol === "SOL" && a.chain === "solana", ); ``` Find SOL in the asset catalog, no key needed ```python assets = get("/v1/gateway/assets")["data"] sol = next( a for a in assets if a["symbol"] == "SOL" and a["chain"] == "solana" ) ``` The asset catalog, no key needed ```bash curl -s https://api.truemarkets.co/v1/gateway/assets ``` Response200 ```json { "data": [ { "id": "495e07ac-fa3e-4179-85b7-b1e8cece3dc4", "symbol": "SOL", "chain": "solana", "venue": "defi", "…": "…" } ] } ``` ## 2\. Mint a token The script signs the string `{key_id}.{timestamp}` with your key file and exchanges it for a token. The timestamp is Unix seconds and must be within 30 seconds of ours. Send `access_token` in `Authorization: Bearer` on every call. It lasts an hour, and `refresh_token` gets you a new pair without signing again. The same key file signs your orders later, so the script turns it into the hex keys the Turnkey stamper takes. - TypeScript - Python Load your key file for signing ```typescript // One key file does both jobs: it signs the token challenge, and, // because the app registered it on your wallet, your payloads. const { key_id, private_key: jwk } = JSON.parse( readFileSync(KEY_FILE, "utf8"), ); const y = Buffer.from(jwk.y, "base64url"); const stamper = new ApiKeyStamper({ // The compressed public key and the private key, as hex. apiPublicKey: Buffer.concat([ Buffer.from([y[31] % 2 === 0 ? 2 : 3]), Buffer.from(jwk.x, "base64url"), ]).toString("hex"), apiPrivateKey: Buffer.from(jwk.d, "base64url").toString("hex"), }); const stamp = async (payload: string) => (await stamper.stamp(payload)).stampHeaderValue; ``` Load your key file for signing ```python # One key file does both jobs: it signs the token challenge, and, # because the app registered it on your wallet, your payloads. with open(KEY_FILE) as f: key_file = json.load(f) key_id, jwk = key_file["key_id"], key_file["private_key"] y = b64url_bytes(jwk["y"]) stamper = ApiKeyStamper( ApiKeyStamperConfig( # The compressed public key and the private key, as hex. api_public_key=( bytes([2 if y[31] % 2 == 0 else 3]) + b64url_bytes(jwk["x"]) ).hex(), api_private_key=b64url_bytes(jwk["d"]).hex(), ) ) def stamp(payload): return stamper.stamp(payload).stamp_header_value ``` - TypeScript - Python Sign the challenge and mint the token ```typescript const timestamp = Math.floor(Date.now() / 1000); const signature = sign( "sha256", Buffer.from(`${key_id}.${timestamp}`), { key: createPrivateKey({ key: jwk, format: "jwk" }), // r and s concatenated, which the API expects dsaEncoding: "ieee-p1363", }, ).toString("base64url"); const minted = await post("/v1/auth/api-key/token", { key_id, timestamp, signature, }); token = minted.access_token; // minted.refresh_token gets a new pair later, without signing. ``` Sign the challenge and mint the token ```python private_key = ec.EllipticCurvePrivateNumbers( int.from_bytes(b64url_bytes(jwk["d"]), "big"), ec.EllipticCurvePublicNumbers( int.from_bytes(b64url_bytes(jwk["x"]), "big"), int.from_bytes(b64url_bytes(jwk["y"]), "big"), ec.SECP256R1(), ), ).private_key() timestamp = int(time.time()) der = private_key.sign( f"{key_id}.{timestamp}".encode(), ec.ECDSA(hashes.SHA256()), ) # The API expects r and s concatenated. r, s = decode_dss_signature(der) raw = r.to_bytes(32, "big") + s.to_bytes(32, "big") signature = ( base64.urlsafe_b64encode(raw).rstrip(b"=").decode() ) minted = post( "/v1/auth/api-key/token", { "key_id": key_id, "timestamp": timestamp, "signature": signature, }, ) token = minted["access_token"] # minted["refresh_token"] gets a new pair later, with no # signing. ``` Response200keep both tokens ```json { "access_token": "eyJhbGciOi…", "refresh_token": "eyJhbGciOi…", "expires_in": "2026-10-01T18:11:27Z", "token_type": "Bearer" } ``` ## 3\. Fund your wallet The script reads your balances and waits until your Solana wallet holds enough USDC. Send about 3 USDC to it on Solana; [Funding](https://docs.truemarkets.co/trading-api/fund) shows where to find the address. The order spends 2 of it, and we pay the network fees. > **Send it on Solana** > > USDC sent on any other network won't reach this wallet. - TypeScript - Python Wait until your wallet holds enough USDC ```typescript const usdcAvailable = async () => { const { data } = await get("/v1/gateway/balances"); const usdc = data.find( (b: any) => b.symbol === "USDC" && b.chain === "solana", ); return Number(usdc?.available ?? 0); }; if ((await usdcAvailable()) < Number(ORDER_USDC)) { console.log("… send about 3 USDC on Solana to your wallet"); while ((await usdcAvailable()) < Number(ORDER_USDC)) { await sleep(10_000); } } ``` Wait until your wallet holds enough USDC ```python def usdc_available(): balances = get("/v1/gateway/balances")["data"] usdc = next( ( b for b in balances if b["symbol"] == "USDC" and b["chain"] == "solana" ), None, ) return float(usdc["available"]) if usdc else 0.0 if usdc_available() < float(ORDER_USDC): print("… send about 3 USDC on Solana to your wallet") while usdc_available() < float(ORDER_USDC): time.sleep(10) ``` ## 4\. Place the order and execute The script places a market buy for 2 USDC of SOL. The response carries the quote and the payloads, the unsigned wallet transactions to sign. The script signs each one with your key and executes right away. `auth_type: "api_key"` says your key made the signatures, rather than a passkey. A quote lasts about two minutes, so doing it in one go keeps it from expiring. If execute still returns `quote_stale`, run the script again. Execute usually returns `complete`. A buy that first moves funds between chains returns `pending`, and the script reads the order until it's final. If `order_id` comes back empty, no order was created, and `quote.issues` says why. - TypeScript - Python Place the order, sign the payloads, execute ```typescript const order = await post("/v1/gateway/orders", { asset_id: sol.id, qty: ORDER_USDC, qty_unit: "quote", side: "buy", type: "market", }); // An empty order_id means no order was created, and // quote.issues says why. if (!order.order_id) { throw new Error( `no order: ${JSON.stringify(order.quote?.issues)}`, ); } // Sign each payload and execute straight away, before the // quote expires. const signatures = await Promise.all( order.payloads.map((p: any) => stamp(p.payload)), ); const executed = await post( `/v1/gateway/orders/${order.order_id}/execute`, { signatures, auth_type: "api_key" }, ); ``` Place the order, sign the payloads, execute ```python order = post( "/v1/gateway/orders", { "asset_id": sol["id"], "qty": ORDER_USDC, "qty_unit": "quote", "side": "buy", "type": "market", }, ) # An empty order_id means no order was created, and # quote.issues says why. if not order.get("order_id"): issues = order.get("quote", {}).get("issues") raise RuntimeError(f"no order: {issues}") # Sign each payload and execute straight away, before the # quote expires. signatures = [stamp(p["payload"]) for p in order["payloads"]] executed = post( f"/v1/gateway/orders/{order['order_id']}/execute", {"signatures": signatures, "auth_type": "api_key"}, ) ``` Response200from execute ```json { "status": "complete" } ``` ## 5\. See the fill The script reads the order back, then finds the same fill in your transactions. The row's `status` reads `completed`, the word the transactions list uses for anything finished. - TypeScript - Python Read the order, then find it in your transactions ```typescript let filled = executed; while ( !["complete", "canceled", "failed"].includes(filled.status) ) { await sleep(2000); filled = await get(`/v1/gateway/orders/${order.order_id}`); } console.log( `✓ order ${filled.status} ` + `${filled.executed_qty ?? ""} SOL for ${ORDER_USDC} USDC`, ); const feed = await get("/v1/gateway/transactions?type=order"); const row = feed.data.find((t: any) => t.id === order.order_id); ``` Read the order, then find it in your transactions ```python filled = executed while filled["status"] not in ( "complete", "canceled", "failed", ): time.sleep(2) filled = get(f"/v1/gateway/orders/{order['order_id']}") print( f"✓ order {filled['status']} " f"{filled.get('executed_qty', '')} SOL " f"for {ORDER_USDC} USDC" ) feed = get("/v1/gateway/transactions?type=order") row = next( (t for t in feed["data"] if t["id"] == order["order_id"]), None, ) ``` ## Run it Run the script. You can run it again: each run buys another 2 USDC of SOL while your wallet has funds. - TypeScript - Python Run the script ```bash npx tsx quickstart.ts ``` Run the script ```bash python quickstart.py ``` Example output ```text ✓ token expires 2026-10-01T18:11:27Z … send about 3 USDC on Solana to your wallet ✓ funded ✓ order complete 0.0102 SOL for 2 USDC ✓ in your transactions (completed) ``` ## Where to go next - [How requests and signing work](https://docs.truemarkets.co/trading-api/requests-and-signing): the two signature formats your key makes. - [Place orders](https://docs.truemarkets.co/trading-api/place-orders): limit orders, listing, canceling and changing orders. - [Your transactions](https://docs.truemarkets.co/trading-api/transactions): everything you've traded and sent, in one list. Every error response includes a `request_id`. Include it when you email [support@truemarkets.co](mailto:support@truemarkets.co), so we can find the request. --- # How requests and signing work Source: https://docs.truemarkets.co/trading-api/requests-and-signing Your API key does two jobs. It signs a short challenge to get you a token, and it signs each transaction that moves funds out of your wallet. The two signatures use different formats, so this page shows each one. Your codeTrue Markets Sign inwith your API key Sign {key\_id}.{timestamp}POST /v1/auth/api-key/token Access and refresh tokensrefresh without signing again Place the orderwith your token Create the orderPOST /v1/gateway/orders Unsigned payloadsone wallet transaction each Sign and executewith the same API key Stamp each payloadstays on your machine Execute with the signaturesPOST /v1/gateway/orders/{id}/execute Order statuscomplete, or pending until final ## Authenticate a request To sign in, sign the string `{key_id}.{timestamp}` with your API key and exchange the signature for tokens at `POST /v1/auth/api-key/token`. The access token lasts an hour, and the refresh token gets you a new pair without signing again. 1. Use the current Unix time in seconds. It must be within 30 seconds of ours. 2. Sign with ES256, which is ECDSA over P-256 with SHA-256. 3. Send `r` and `s` concatenated, base64url-encoded, as `signature`. Send the access token as `Authorization: Bearer` on every call. You never send `TM-On-Behalf-Of`; that header is for businesses acting for their users. [Getting started, step 2](https://docs.truemarkets.co/trading-api/quickstart#2-mint-a-token) has the signing code. - TypeScript - Python - curl The helper adds your token ```typescript const balances = await get("/v1/gateway/balances"); ``` The helper adds your token ```python balances = get("/v1/gateway/balances") ``` The helper adds your token ```bash curl -s https://api.truemarkets.co/v1/gateway/balances \ -H "Authorization: Bearer $TOKEN" ``` ## Sign a wallet transaction Anything that moves funds, such as an order, a cancel on-chain or a transfer, comes back with unsigned `payloads`. Each payload is one wallet transaction, and nothing moves until you sign it and execute. You can sign with any P-256 library: 1. Take each `payload` string exactly as the response returned it. Don't parse or re-serialize it. 2. Sign it with ECDSA over P-256 and SHA-256, using your API key. Encode the signature as DER, then as hex. 3. Put it in a JSON object with your compressed public key, as 66 hex characters, and the scheme `SIGNATURE_SCHEME_TK_API_P256`. 4. Base64url-encode that JSON. The result is one entry in `signatures[]`, in the same order as `payloads`. 5. Post `signatures` to the execute call with `auth_type: "api_key"`. When you created the key, the app registered it on your wallet, which is why the same key works here. Your key file holds it as a JWK; the hex values the libraries below take are its `d` field and its compressed public key. One signatures\[\] entry, before base64url encoding ```json { "publicKey": "02a1b2c3…7e8f90", "signature": "3045022100…", "scheme": "SIGNATURE_SCHEME_TK_API_P256" } ``` - TypeScript - Python Turn your key file into the stamper's hex keys ```typescript // One key file does both jobs: it signs the token challenge, and, // because the app registered it on your wallet, your payloads. const { key_id, private_key: jwk } = JSON.parse( readFileSync(KEY_FILE, "utf8"), ); const y = Buffer.from(jwk.y, "base64url"); const stamper = new ApiKeyStamper({ // The compressed public key and the private key, as hex. apiPublicKey: Buffer.concat([ Buffer.from([y[31] % 2 === 0 ? 2 : 3]), Buffer.from(jwk.x, "base64url"), ]).toString("hex"), apiPrivateKey: Buffer.from(jwk.d, "base64url").toString("hex"), }); const stamp = async (payload: string) => (await stamper.stamp(payload)).stampHeaderValue; ``` Turn your key file into the stamper's hex keys ```python # One key file does both jobs: it signs the token challenge, and, # because the app registered it on your wallet, your payloads. with open(KEY_FILE) as f: key_file = json.load(f) key_id, jwk = key_file["key_id"], key_file["private_key"] y = b64url_bytes(jwk["y"]) stamper = ApiKeyStamper( ApiKeyStamperConfig( # The compressed public key and the private key, as hex. api_public_key=( bytes([2 if y[31] % 2 == 0 else 3]) + b64url_bytes(jwk["x"]) ).hex(), api_private_key=b64url_bytes(jwk["d"]).hex(), ) ) def stamp(payload): return stamper.stamp(payload).stamp_header_value ``` ### Or use a Turnkey library | Language | Library | Call | | --- | --- | --- | | TypeScript | [`@turnkey/api-key-stamper`](https://www.npmjs.com/package/@turnkey/api-key-stamper) | `new ApiKeyStamper({ apiPublicKey, apiPrivateKey }).stamp(payload)` | | Go | [`github.com/tkhq/go-sdk/v2`](https://pkg.go.dev/github.com/tkhq/go-sdk/v2) | `NewAPIKeyStamper(privateKey).Stamp(ctx, payload)` | | Python | [`turnkey-api-key-stamper`](https://pypi.org/project/turnkey-api-key-stamper/) | `ApiKeyStamper(ApiKeyStamperConfig(api_public_key, api_private_key)).stamp(payload)` | | Rust | [`turnkey_api_key_stamper`](https://crates.io/crates/turnkey_api_key_stamper) | see the crate docs | Each one returns the same base64url stamp. Pass the payload string exactly as you received it. [Getting started, step 4](https://docs.truemarkets.co/trading-api/quickstart#4-place-the-order-and-execute) converts your key file and uses the TypeScript one end to end. Next: [Funding](https://docs.truemarkets.co/trading-api/fund) --- # Fund your wallet Source: https://docs.truemarkets.co/trading-api/fund You trade from what your wallet holds. The app creates the wallet when you set up a passkey. Send tokens to it on-chain, or buy with a card at [app.truemarkets.co](https://app.truemarkets.co/). Then check your balance before you trade. ## Send tokens to your wallet Your wallet has one Solana address and one EVM address. The EVM address is the same on every EVM chain we support. Find both at [app.truemarkets.co](https://app.truemarkets.co/). Send the stablecoin a market buy spends, on the chain you want to trade on, such as USDC on Solana. An order's quote names that asset in `quote_asset`, and each asset in [the catalog](https://docs.truemarkets.co/trading-api/what-you-can-trade) has a `chain` field that tells you which network to fund. We pay the network fees. > **Match the network to the address** > > Tokens sent on the wrong network can be lost. ## Check the balance Your balance updates once the network confirms the transfer. `available` is what you can trade with now. The code uses the `get()` helper from [Getting started](https://docs.truemarkets.co/trading-api/quickstart). - TypeScript - Python - curl Your balances ```typescript const balances = await get("/v1/gateway/balances"); ``` Your balances ```python balances = get("/v1/gateway/balances") ``` Your balances ```bash curl -s https://api.truemarkets.co/v1/gateway/balances \ -H "Authorization: Bearer $TOKEN" ``` Response200available is what you can trade ```json { "data": [ { "symbol": "USDC", "chain": "solana", "available": "3.000000", "total": "3.000000", "…": "…" } ] } ``` Next: [Send funds](https://docs.truemarkets.co/trading-api/send-funds) --- # Send funds Source: https://docs.truemarkets.co/trading-api/send-funds Send tokens from your wallet to any address on the same chain. A transfer works like an order: you create it, sign the payloads it returns, then execute it. A payload is an unsigned transaction. ## Create the transfer Send the `asset_id` from the catalog, the amount in that asset with `qty_unit: "base"`, and the destination address in `to`. Leave out `network`; the transfer uses the asset's chain. The transfer comes back with status `awaiting_signature` and its `payloads`. Keep `id` and `payloads`. - TypeScript - Python - curl Create a transfer of 5 USDC on Solana ```typescript const transfer = await post("/v1/gateway/transfers", { asset_id: assetId, qty: "5", qty_unit: "base", to: "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", }); ``` Create a transfer of 5 USDC on Solana ```python transfer = post("/v1/gateway/transfers", { "asset_id": asset_id, "qty": "5", "qty_unit": "base", "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", }) ``` Create a transfer of 5 USDC on Solana ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/transfers \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "5", "qty_unit": "base", "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }' ``` Response201keep id and payloads ```json { "id": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a", "status": "awaiting_signature", "asset_symbol": "USDC", "chain": "solana", "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", "qty": "5", "payloads": [{ "digest": "8e1a…4f2b", "payload": "{\"organizationId\":\"…\",…}" }], "…": "…" } ``` ## Sign and execute Sign each payload with your API key, the same way you sign an order, and post the signatures in the order the payloads came back. [Getting started](https://docs.truemarkets.co/trading-api/quickstart) has the signing code. `auth_type: "api_key"` tells us your API key made the signatures. > **Transfers are final** > > Once the chain confirms a transfer, it can't be reversed. Check the address and the network before you execute. - TypeScript - Python - curl Sign the payloads, then execute ```typescript const signatures = await Promise.all( transfer.payloads.map((p) => stamp(p.payload)), ); const done = await post( `/v1/gateway/transfers/${transferId}/execute`, { signatures, auth_type: "api_key", }, ); ``` Sign the payloads, then execute ```python signatures = [stamp(p["payload"]) for p in transfer["payloads"]] done = post(f"/v1/gateway/transfers/{transfer_id}/execute", { "signatures": signatures, "auth_type": "api_key", }) ``` Sign the payloads, then execute ```bash # $SIGNATURES is the JSON array your code signed curl -s -X POST https://api.truemarkets.co/v1/gateway/transfers/$TRANSFER_ID/execute \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "signatures": '"$SIGNATURES"', "auth_type": "api_key" }' ``` Response200the transfer, finished ```json { "id": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a", "status": "completed", "sent": "5", "received": "5", "explorer_url": "https://solscan.io/tx/4Hn8…Yt3z", "…": "…" } ``` Execute returns once the transaction has landed, with status `completed`. If it fails, the transfer is marked `failed` and you create a new one. Executing the same transfer again returns `409`. To look a transfer up later, call `GET /v1/gateway/transfers/{id}`. Next: [Place orders](https://docs.truemarkets.co/trading-api/place-orders) --- # Place orders Source: https://docs.truemarkets.co/trading-api/place-orders Every order is a `POST /v1/gateway/orders` with an asset, a size and a type. This page covers the calls you make most: place an order, list and read orders, and cancel one. Every call here uses your own token. [Getting started](https://docs.truemarkets.co/trading-api/quickstart) walks through signing and executing a market buy. ## Identify the asset Send the asset's `id` from [the catalog](https://docs.truemarkets.co/trading-api/what-you-can-trade) as `asset_id`. Older integrations name the asset with `base_asset` instead. Use `asset_id` for new code. ## Place a market order A market order executes now at the best available price, so it takes no `price`. Size a buy by what you spend, with `qty_unit: "quote"`. Size a sell by what you sell, with `qty_unit: "base"`. The response carries the quote and the `payloads` to sign. Nothing moves until you sign them and execute. - TypeScript - Python - curl Buy 2 USDC worth of an asset ```typescript const order = await post("/v1/gateway/orders", { asset_id: assetId, qty: "2", qty_unit: "quote", side: "buy", type: "market", }); ``` Buy 2 USDC worth of an asset ```python order = post("/v1/gateway/orders", { "asset_id": asset_id, "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market", }) ``` Buy 2 USDC worth of an asset ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "'"$ASSET_ID"'", "qty": "2", "qty_unit": "quote", "side": "buy", "type": "market" }' ``` ## Limit orders A limit order rests at your `price` until it fills or you cancel it, sized with `qty_unit: "base"`. Limit orders aren't available for every asset yet; the [create order reference](https://docs.truemarkets.co/api/gateway/create-order) has the current rules. ## List and read orders `GET /v1/gateway/orders` lists your orders, newest first. Filter with `status` (comma-separated), and page with `limit` (up to 100) and the `cursor` from `pagination.next_cursor`. An order you created but never executed doesn't appear in the list. Read it by its id. - TypeScript - Python - curl Resting orders, then one order by its id ```typescript const orders = await get("/v1/gateway/orders?status=active"); const order = await get(`/v1/gateway/orders/${orderId}`); ``` Resting orders, then one order by its id ```python orders = get("/v1/gateway/orders?status=active") order = get(f"/v1/gateway/orders/{order_id}") ``` Resting orders, then one order by its id ```bash curl -s -G https://api.truemarkets.co/v1/gateway/orders \ -H "Authorization: Bearer $TOKEN" \ -d status=active curl -s https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID \ -H "Authorization: Bearer $TOKEN" ``` ## Cancel an order Canceling a resting order is a wallet transaction too, so it works like placing one: the cancel call returns `payloads`, you sign them, and you execute the cancel. - TypeScript - Python - curl Cancel a resting order, sign, execute ```typescript const cancel = await post( `/v1/gateway/orders/${orderId}/cancel`, {}, ); const signatures = await Promise.all( cancel.payloads.map((p) => stamp(p.payload)), ); await post(`/v1/gateway/orders/${orderId}/cancel/execute`, { signatures, auth_type: "api_key", }); ``` Cancel a resting order, sign, execute ```python cancel = post(f"/v1/gateway/orders/{order_id}/cancel", {}) signatures = [stamp(p["payload"]) for p in cancel["payloads"]] post(f"/v1/gateway/orders/{order_id}/cancel/execute", { "signatures": signatures, "auth_type": "api_key", }) ``` Cancel a resting order, sign, execute ```bash curl -s -X POST https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID/cancel \ -H "Authorization: Bearer $TOKEN" # $SIGNATURES is the JSON array your code signed curl -s -X POST https://api.truemarkets.co/v1/gateway/orders/$ORDER_ID/cancel/execute \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "signatures": '"$SIGNATURES"', "auth_type": "api_key" }' ``` ## Sizing In a pair such as SOL/USDC, the base asset is the one you buy or sell (SOL), and the quote asset is the one you price it in (USDC). `qty_unit` says which one `qty` counts. Every quantity is a positive decimal string. | `qty_unit` | Meaning | Allowed on | | --- | --- | --- | | `quote` | amount of the quote asset to spend, "10 USDC of SOL" | market buy | | `base` | amount of the base asset, "0.01 SOL" | market sell, limit orders | ## Follow an order to a final state Order status, from create to a final state createinitializedsign + execute - completefilled - activea limit order, resting - pendingstill settling - failed cancelcancel\_pendingcanceled final won't changeother read the order again An order that isn't in a final state can still change, so read it again until it is. If `order_id` comes back empty, no order was created, and `quote.issues` says why. Nothing moves until you execute, so you can retry a create that timed out. ## When an order fails | Status | Meaning | | --- | --- | | `201` with an empty `order_id` | The wallet can't fund the buy. `quote.issues` says why, and no order was created. | | `422` with `insufficient_balance` | The wallet can't fund a sell or a perpetual order. | | `422` with `quote_stale` | The quote expired or the price moved before execute landed. Place the order again. | | `403` | The asset isn't available in your country. `GET /assets/availability` shows what's available. | | `409` with `already_submitted` on execute | The transaction already landed. Read the order instead of executing again. | [Errors](https://docs.truemarkets.co/developer-resources/errors) has the error body and the rest of the codes. ## Perpetuals A perpetual, or perp, is a contract that tracks an asset's price and never expires. Perpetuals aren't available for every asset; the ones that are appear in the catalog with `type: "perp"`. To open a position, send `leverage`, up to the asset's `perp.max_leverage` in the catalog. Margin is isolated. With `qty_unit: "quote"`, `qty` is the margin you commit and the position's size is `qty` times `leverage`; with `"base"`, `qty` is the position's size. To close a position, send `reduce_only: true` instead. A `trigger` object turns the order into a take-profit or stop-loss that fires when the mark price crosses `trigger.price`. The [create order reference](https://docs.truemarkets.co/api/gateway/create-order) has every rule. Next: [What you can trade](https://docs.truemarkets.co/trading-api/what-you-can-trade) --- # What you can trade Source: https://docs.truemarkets.co/trading-api/what-you-can-trade The asset catalog lists everything you can trade. Each asset says what it is, where it trades and which network it settles on. You can read it without a token, so you can call it before you mint your first token. ## List assets Without filters, the catalog returns spot crypto. Perpetual futures and tokenized stocks are opt-in: pass `product_type=perp` (it filters on each asset's `type`) or `asset_class=stock` to list them, or a comma-separated list such as `asset_class=crypto,stock` to get both. Keep each asset's `id`. It's the `asset_id` you place orders with. The catalog comes back in one response, with no paging. Read chains and listings from the catalog instead of hard-coding them. New ones appear there first. - TypeScript - Python - curl The catalog, no key needed ```typescript const assets = await get("/v1/gateway/assets"); ``` The catalog, no key needed ```python assets = get("/v1/gateway/assets") ``` The catalog, no key needed ```bash curl -s https://api.truemarkets.co/v1/gateway/assets ``` Response200keep each asset's id ```json { "data": [ { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "symbol": "SOL", "chain": "solana", "venue": "defi", "type": "spot", "asset_class": "crypto", "status": "active", "…": "…" } ] } ``` - TypeScript - Python - curl Tokenized stocks ```typescript const stocks = await get("/v1/gateway/assets?asset_class=stock"); ``` Tokenized stocks ```python stocks = get("/v1/gateway/assets?asset_class=stock") ``` Tokenized stocks ```bash curl -s -G https://api.truemarkets.co/v1/gateway/assets \ -d asset_class=stock ``` Response200a stock is a spot listing with asset\_class stock ```json { "data": [ { "id": "d4f1a2b3-6c7e-4f80-9a1b-2c3d4e5f6a70", "symbol": "AAPLC", "chain": "base", "asset_class": "stock", "…": "…" } ] } ``` ## Fields that describe an asset Four fields tell you what an asset is and where it trades. | Field | Values | What it tells you | | --- | --- | --- | | `venue` | `defi`, `cefi` | where the asset trades | | `asset_class` | `crypto`, `stock` | a token, or a tokenized stock | | `type` | `spot`, `perp` | spot, or a perpetual future | | `chain` | a chain name | the network the asset settles on. Fund the wallet on that network. | Next: [Transactions](https://docs.truemarkets.co/trading-api/transactions) --- # Your transactions Source: https://docs.truemarkets.co/trading-api/transactions Transactions are your orders, transfers and card purchases in one list, newest first. Use them to see what filled, what moved, and when. The list covers what you do through True Markets. Tokens you receive from outside, such as a deposit to your wallet, aren't in it, so read your [balances](https://docs.truemarkets.co/trading-api/fund#check-the-balance) for those. Orders and transfers you never executed aren't listed here or in `GET /v1/gateway/orders`. Read them by id. ## Your transactions Call `GET /v1/gateway/transactions` with your token. Each row's `type` says which object it holds, `order`, `transfer` or `ramp` (a card purchase), and the field with that name holds the full object in the same shape its own endpoint returns. Each row's `status` is one of `pending`, `completed`, `failed` or `canceled`. The object's own status, such as an order's `complete`, stays inside it. Pass `type` to keep one kind, for example `type=order`. `limit` defaults to 50 and goes up to 100. To get the next page, pass the `cursor` from `pagination.next_cursor`. - TypeScript - Python - curl Your orders, newest first ```typescript const feed = await get("/v1/gateway/transactions?type=order"); ``` Your orders, newest first ```python feed = get("/v1/gateway/transactions?type=order") ``` Your orders, newest first ```bash curl -s -G https://api.truemarkets.co/v1/gateway/transactions \ -H "Authorization: Bearer $TOKEN" \ -d type=order ``` Response200one row per order ```json { "data": [ { "type": "order", "status": "completed", "asset_symbol": "SOL", "qty": "0.0102", "order": { "…": "the same object as GET /orders/{id}" } } ], "pagination": { "next_cursor": null, "limit": 50 } } ``` Next: [Go live checklist](https://docs.truemarkets.co/trading-api/go-live) --- # Trading API go-live checklist Source: https://docs.truemarkets.co/trading-api/go-live We don't have a fully working sandbox yet, so every order and transfer you execute against `api.truemarkets.co` moves real funds. Keep your first trades small. A real trade also proves the whole flow end to end. Work through this checklist before you raise the amounts. ## Checklist - Your API key file is out of your repo and out of chat tools. Anyone with it can act as you. If it leaks, revoke it at [app.truemarkets.co](https://app.truemarkets.co/) and create a new one. - One wallet holds a small amount of the stablecoin you trade with. - You placed one small market buy, then sold it back with `side: "sell"` and `qty_unit: "base"`. - You handle `422` with `quote_stale` by placing the order again. - You mint a fresh token before `expires_in` passes, or refresh it. - You read your transactions after each trade and find the fill. [Errors](https://docs.truemarkets.co/developer-resources/errors) lists every status and which ones to retry. Next: [FAQs](https://docs.truemarkets.co/trading-api/faq) --- # About Institutional Source: https://docs.truemarkets.co/institutional/overview Institutional access connects your firm directly to the True Markets exchange, our central limit order book. You place orders and read market data over REST, WebSocket or FIX, and your firm's accounts are set up with our team during onboarding. If you want to trade your own True Markets account from code, read the [Trading API](https://docs.truemarkets.co/trading-api) documentation. If you're building an app where your customers trade, read [Gateway](https://docs.truemarkets.co/gateway). ## Who it's for - Trading firms and market makers that trade their own book. - Asset managers and introducing firms that trade for their clients. Any firm that needs direct market access can apply. We verify your firm with KYB (Know Your Business) before we issue credentials. ## How you connect REST and WebSocket are reachable over the internet. You sign each request with HMAC credentials that we issue to your firm. FIX is the lowest-latency path. A FIX session is a long-lived connection that you log on to once and keep open. FIX runs over a private network connection, which we set up with your firm during onboarding. ## Get set up 1. [Access and credentials](https://docs.truemarkets.co/institutional/access-and-credentials): apply, connect and receive credentials. 2. [Account models](https://docs.truemarkets.co/institutional/account-models): how your accounts and clients are structured. 3. [Manage clients](https://docs.truemarkets.co/institutional/manage-clients): read your clients and set their self-trade prevention. 4. Trade over [FIX](https://docs.truemarkets.co/institutional/trade-over-fix) or [REST](https://docs.truemarkets.co/institutional/trade-over-rest). Both cover the same trading actions. ## Reference - [CeFi direct REST](https://docs.truemarkets.co/api/institutional/true-markets-cefi-rest) - [CeFi direct FIX](https://docs.truemarkets.co/api/fix/overview) - [CeFi direct WebSocket](https://docs.truemarkets.co/api/websocket/institutional) - [Exchange documentation](https://docs.truemarkets.co/documentation/exchange-overview): trading rules, fees, price bands and increment sizes. Next: [Access and credentials](https://docs.truemarkets.co/institutional/access-and-credentials) --- # Access and credentials Source: https://docs.truemarkets.co/institutional/access-and-credentials Before your first order, your firm needs an approved application, a way to reach the exchange and credentials for each protocol you use. The steps are the same for every firm, market makers included. ## Apply for access KYB (Know Your Business) verifies your firm rather than an individual. Email [support@truemarkets.co](mailto:support@truemarkets.co) to apply. You can read about the exchange at [truemarkets.co/exchange](https://truemarkets.co/exchange). If you're an asset manager or an introducing firm, KYB covers your firm. How we verify your underlying clients depends on your [account model](https://docs.truemarkets.co/institutional/account-models). ## Connect to the exchange REST and WebSocket are reachable over the internet. The base URLs are in the [CeFi direct REST reference](https://docs.truemarkets.co/api/institutional/true-markets-cefi-rest) and the [CeFi direct WebSocket reference](https://docs.truemarkets.co/api/websocket/institutional). FIX runs over a private network connection between your firm and the exchange. Once your firm is approved, email [support@truemarkets.co](mailto:support@truemarkets.co) and we set it up with you. ## Receive your credentials We issue credentials for each protocol you use: - **REST and WebSocket:** an API key and a secret. You sign each request with them using HMAC-SHA256. The key identifies your firm, so you never send an organization id. [Trade over REST](https://docs.truemarkets.co/institutional/trade-over-rest) shows how to sign. - **FIX:** a `TargetCompID` we assign, plus the API key and secret you sign each `Logon` with. [Administrative messages](https://docs.truemarkets.co/api/fix/admin) lists the Logon fields. Keep the secret out of source control. Anyone with your key and secret can place orders as your firm. ## Test your FIX sessions before you trade Before you trade over FIX, run your order entry, market data and drop copy sessions with our team against the [`TrueX_FIX50SP2.xml`](https://github.com/true-markets/specification/blob/develop/TrueX_FIX50SP2.xml) specification. The [FIX overview](https://docs.truemarkets.co/api/fix/overview) explains each session. ## Fees The exchange charges a maker fee or a taker fee on each executed order, in basis points of the executed notional. A maker order rests on the book before it fills; a taker order fills against a resting order. The [fee schedule](https://docs.truemarkets.co/documentation/fee-schedule) has the current rates. Next: [Account models](https://docs.truemarkets.co/institutional/account-models) --- # Account models Source: https://docs.truemarkets.co/institutional/account-models Your account model sets how your accounts and clients are structured on the exchange. The model you need depends on whose funds you trade: your firm's own, or your clients'. We set up your model with you during onboarding, based on your firm's structure and who you trade for. Every model uses the same KYB, the same credentials and the same exchange. | Model | Who you trade for | Verification | Allocation | | --- | --- | --- | --- | | [Principal](https://docs.truemarkets.co/institutional/account-models#principal) | Your firm's own book, as one client | KYB on your firm | None | | [Omnibus agency](https://docs.truemarkets.co/institutional/account-models#omnibus-agency) | Many clients, through one pooled account | KYB on your firm only | On your side, after the trade, from drop copy fills | | [Fully disclosed](https://docs.truemarkets.co/institutional/account-models#fully-disclosed) | Clients you disclose to us one by one | KYC on each client | Each order is booked to one client | ## Principal Your firm trades its own book, such as a proprietary or treasury book, as a single entity. You have one client, your firm, and nothing to allocate. Market makers use this model. ## Omnibus agency You trade for many clients through one pooled account, often called an omnibus account. We run KYB on your firm only. Each trade books to the pooled account, and you allocate the fills to your clients on your side afterwards. Drop copy is a read-only FIX feed of every execution on your account. It's the record you allocate from. See [Drop copy](https://docs.truemarkets.co/api/fix/drop-copy). ## Fully disclosed You disclose each client to us, and each one goes through KYC. Every client has its own client record and its own funding, and you book each order to a specific client. Next: [Manage clients](https://docs.truemarkets.co/institutional/manage-clients) --- # Manage clients Source: https://docs.truemarkets.co/institutional/manage-clients A client is an account we set up under your firm for one of the parties you trade for. Every order names a client, so you need your client ids even on a principal account, which has one client for your firm. Read your clients and change their self-trade prevention setting here. We create your clients during onboarding. Through the API you can read them and change one setting, self-trade prevention. There is no public endpoint to create a client. Self-trade prevention (STP) stops an order from filling against another order from the same client. Depending on the setting, the exchange cancels the incoming order or both orders. - [Get clients](https://docs.truemarkets.co/api/institutional/get-clients-cefi) - [Update client](https://docs.truemarkets.co/api/institutional/patch-clients-cefi) ## How you use clients Each client has an `id`. Every order carries one in `client_id`, and REST requests other than Get clients carry one in the `x-truex-auth-userid` header. How you choose it depends on your [account model](https://docs.truemarkets.co/institutional/account-models): - **Principal:** you have one client, your firm, and every order uses its id. - **Fully disclosed:** each disclosed client has its own id, and you book each order to one of them. - **Omnibus agency:** orders book to your pooled account's client, and you allocate fills afterwards from your [drop copy](https://docs.truemarkets.co/api/fix/drop-copy) feed. Next: [Trade over FIX](https://docs.truemarkets.co/institutional/trade-over-fix) --- # Trade over FIX Source: https://docs.truemarkets.co/institutional/trade-over-fix FIX is the lowest-latency way to trade on the exchange, and the protocol most market makers use. You need a private network connection and FIX credentials first. [Access and credentials](https://docs.truemarkets.co/institutional/access-and-credentials) covers both. ## Sessions A FIX session is a long-lived connection that you log on to once and keep open with heartbeats. You run a separate session for each job: - **Order entry:** place, amend and cancel orders with `NewOrderSingle`, `OrderCancelReplaceRequest` and `OrderCancelRequest`. The exchange answers with an `ExecutionReport` or an `OrderCancelReject`. See [Order entry](https://docs.truemarkets.co/api/fix/order-entry). - **Market data:** read the instrument list with `SecurityListRequest` and subscribe to the order book with `MarketDataRequest`. See [Market data](https://docs.truemarkets.co/api/fix/market-data). - **Drop copy:** a read-only copy of every execution on your account, for allocation, compliance and audit. See [Drop copy](https://docs.truemarkets.co/api/fix/drop-copy). Every session starts with a signed `Logon`. [Administrative](https://docs.truemarkets.co/api/fix/admin) lists the fields and how to compute the signature. ## Order handling - To reprice a resting order, send `OrderCancelReplaceRequest` instead of a cancel followed by a new order. - FIX has no mass cancel. To cancel every open order, call [`DELETE /v1/cefi/orders/all`](https://docs.truemarkets.co/api/institutional/cancel-all-orders-cefi) over REST. - On an omnibus agency account, allocate fills to your clients from the drop copy session. See [Account models](https://docs.truemarkets.co/institutional/account-models#omnibus-agency). Next: [Trade over REST](https://docs.truemarkets.co/institutional/trade-over-rest) --- # Trade over REST Source: https://docs.truemarkets.co/institutional/trade-over-rest CeFi direct REST covers the same trading actions as FIX, over HTTPS. It's reachable over the internet, so you don't need the private network connection that FIX uses. ## Sign a request Sign every request with the API key and secret we issued to your firm. The signature is an HMAC-SHA256 of four strings joined with no separator: the timestamp, the HTTP method, the path without its query string, and the request body. An empty body adds nothing. The timestamp is Unix seconds. We reject a request whose timestamp is more than 15 seconds old or more than 1 second in the future, so keep your clock in sync. Send the result in four headers, plus the schema version: | Header | Value | | --- | --- | | `x-truex-auth-token` | your API key | | `x-truex-auth-timestamp` | the timestamp you signed | | `x-truex-auth-signature` | the signature, base64 | | `x-truex-auth-userid` | the `id` of the client the request acts for | | `X-Truex-Version` | `v2026_01_23` | The key identifies your firm, so you never send an organization id. The client id goes on every call except [Get clients](https://docs.truemarkets.co/api/institutional/get-clients-cefi), so call that first to find yours. A principal account has one client, your firm. `X-Truex-Version` picks the response format. Without it you get `v2024_01_01`, which stops being served on November 1, 2026, so send `v2026_01_23` now. Its list responses come wrapped in `data` and `pagination`. Send every request to `https://api.truex.co`. sign.ts: sign one request with your secret ```typescript import { createHmac } from "node:crypto"; const [method, path, body = ""] = process.argv.slice(2); const timestamp = String(Math.floor(Date.now() / 1000)); const payload = timestamp + method + path.split("?")[0] + body; const secret = process.env.CEFI_SECRET!; const signature = createHmac("sha256", secret) .update(payload) .digest("base64"); console.log(`${timestamp} ${signature}`); ``` Read your balances ```bash read TS SIG <<< \ "$(npx tsx sign.ts GET /v1/cefi/balances)" curl -s "https://api.truex.co/v1/cefi/balances" \ -H "x-truex-auth-token: $CEFI_API_KEY" \ -H "x-truex-auth-timestamp: $TS" \ -H "x-truex-auth-signature: $SIG" \ -H "x-truex-auth-userid: $CEFI_CLIENT_ID" \ -H "X-Truex-Version: v2026_01_23" ``` ## Endpoints **Orders** - [Create order](https://docs.truemarkets.co/api/institutional/add-orders-cefi) - [Modify order](https://docs.truemarkets.co/api/institutional/modify-orders-cefi) - [Cancel order](https://docs.truemarkets.co/api/institutional/cancel-orders-cefi) - [Cancel all orders](https://docs.truemarkets.co/api/institutional/cancel-all-orders-cefi) - [Active orders](https://docs.truemarkets.co/api/institutional/get-orders-active-cefi) - [Order status](https://docs.truemarkets.co/api/institutional/get-orders-status-cefi) **Balances and transfers** - [Get balances](https://docs.truemarkets.co/api/institutional/get-balances-cefi) - [Balance activity](https://docs.truemarkets.co/api/institutional/get-balance-activity-cefi) - [Initiate transfer](https://docs.truemarkets.co/api/institutional/add-transfers-plural-cefi) - [Get transfers](https://docs.truemarkets.co/api/institutional/get-transfers-plural-cefi) Next: [CeFi direct REST reference](https://docs.truemarkets.co/api/institutional/true-markets-cefi-rest) --- # Migrate to the /v1/cefi paths Source: https://docs.truemarkets.co/institutional/migrate-to-cefi-paths **CeFi REST v1.0.6** removes the legacy `/api/v1/` path prefix. Every operation is now served only under `/v1/cefi/`. This release is a **path move only**. Response bodies, status codes, error formats, pagination behaviour and authentication are byte-for-byte what they were — update your base path and you are done. A second, separate change lands on **November 1, 2026**: schema version `v2024_01_01` reaches end of life and is **removed**. `v2026_01_23` becomes the only supported version, and it changes response shapes. Every client must be on it by that date — see [Upgrading to `v2026_01_23`](https://docs.truemarkets.co/institutional/migrate-to-cefi-paths#upgrading-to-v2026_01_23) below. ## Are you affected? You are affected if any request you send has a path beginning `/api/v1/`. Those paths now return **404**; there is no redirect and no grace period. You are **not** affected if you were already calling `/v1/cefi/` — the prefix has been available since v1.0.5 (May 2026) and is unchanged. ## Step 1 — change your base path | | | | --- | --- | | Production | `https://api.truex.co/v1/cefi/` | | UAT (sandbox) | `https://api.uat.truex.co/v1/cefi/` | Most clients hold this in one constant. If yours does, that is the whole change. ## Step 2 — re-check your HMAC signing This is the step that is easy to miss. The signature covers the **path**: ```text signature = base64( HMAC_SHA256( secret, METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY ) ) ``` If your client builds the signature from a hard-coded path string rather than from the path it actually sends, changing the base URL alone will leave you signing `/api/v1/orders` while requesting `/v1/cefi/orders`. That fails as `401 Unauthorized`, not `404`, which sends people looking in the wrong place. Sign the request path you send. JWT bearer clients (retail) have nothing to do here — there is no per-request signing. ## Step 3 — map your paths Every removed path and its replacement: ### Service | Removed | Use instead | | --- | --- | | `/api/v1/health` | `/v1/cefi/health` | | `/api/v1/versions` | `/v1/cefi/versions` | ### Assets and instruments | Removed | Use instead | | --- | --- | | `/api/v1/assets` | `/v1/cefi/assets` | | `/api/v1/asset` | `/v1/cefi/assets` | | `/api/v1/instruments` | `/v1/cefi/instruments` | | `/api/v1/instrument` | `/v1/cefi/instruments` | ### Market data | Removed | Use instead | | --- | --- | | `/api/v1/market/quote` | `/v1/cefi/market/quote` | | `/api/v1/markets/quote` | `/v1/cefi/markets/quote` | ### Clients | Removed | Use instead | | --- | --- | | `/api/v1/clients` | `/v1/cefi/clients` | | `/api/v1/client` | `/v1/cefi/clients` | ### Orders | Removed | Use instead | | --- | --- | | `/api/v1/orders` | `/v1/cefi/orders` | | `/api/v1/order` | `/v1/cefi/orders` | | `/api/v1/orders/{ref_id}` | `/v1/cefi/orders/{ref_id}` | | `/api/v1/order/{ref_id}` | `/v1/cefi/orders/{ref_id}` | | `/api/v1/orders/all` | `/v1/cefi/orders/all` | | `/api/v1/orders/active` | `/v1/cefi/orders/active` | | `/api/v1/order/active` | `/v1/cefi/orders/active` | | `/api/v1/orders/trade` | `/v1/cefi/orders/trade` | | `/api/v1/order/trade` | `/v1/cefi/orders/trade` | | `/api/v1/orders/activity` | `/v1/cefi/orders/activity` | | `/api/v1/order/activity` | `/v1/cefi/orders/activity` | | `/api/v1/orders/status/{ref_id}` | `/v1/cefi/orders/status/{ref_id}` | | `/api/v1/order/status/{ref_id}` | `/v1/cefi/orders/status/{ref_id}` | ### Balances | Removed | Use instead | | --- | --- | | `/api/v1/balances` | `/v1/cefi/balances` | | `/api/v1/balance` | `/v1/cefi/balances` | | `/api/v1/balances/activity` | `/v1/cefi/balances/activity` | ### Transfers | Removed | Use instead | | --- | --- | | `/api/v1/transfers` | `/v1/cefi/transfers` | | `/api/v1/transfer` | `/v1/cefi/transfers` | | `/api/v1/transfers/active` | `/v1/cefi/transfers/active` | | `/api/v1/transfer/active` | `/v1/cefi/transfers/active` | ### Singular forms are gone The singular aliases (`/asset`, `/instrument`, `/client`, `/order`, `/balance`, `/transfer`) were deprecated in v1.0.3 and are not carried over to `/v1/cefi/`. Use the plural form — it is the same operation with the same response. The one exception is market quotes, where `/v1/cefi/market/quote` and `/v1/cefi/markets/quote` are both real endpoints with different behaviour. Keep whichever one you were calling. ## Step 4 — verify ```bash curl -s https://api.truex.co/v1/cefi/health ``` Then grep your codebase for any remaining `api/v1` — including config files, deployment health checks, monitoring probes and integration test fixtures, not just the client library. ## What did _not_ change in v1.0.6 Worth stating explicitly, because it is the most common wrong assumption about this release: - **The default schema version is still `v2024_01_01`.** A request with no `X-Truex-Version` header gets exactly the response it got before. - **Response bodies are unchanged.** Raw arrays and objects, not envelopes. - **Pagination is unchanged.** List reads that were unpaginated stay unpaginated. - **Error bodies are unchanged.** RFC 7807 Problem Details, unwrapped. - **Authentication is unchanged**, apart from signing the new path. ## Upgrading to `v2026_01_23` On **November 1, 2026**, schema version `v2024_01_01` is **removed**. From that date: - `v2026_01_23` becomes the default, so a request sending no `X-Truex-Version` header gets the new response shapes. - A request that explicitly sends `X-Truex-Version: v2024_01_01` is **rejected**. This is a hard cutoff, not a default change you can sit out. `v2024_01_01` is already past its August 5, 2026 sunset. > **There is no opt-out** > > Pinning `X-Truex-Version: v2024_01_01` does **not** extend your deadline. Today it holds the old format; on November 1 that same header starts failing your requests outright. > > If you pin it as a stopgap, set a reminder to remove it — a pinned `v2024_01_01` is the one configuration that turns this migration into an outage rather than a change in response shape. The work below is independent of the path move above, and you can start it today. Budget for it now: every client must be reading the envelope, following the cursor, and unwrapping errors before November 1. ### Step 1 — adopt the new version ```text X-Truex-Version: v2026_01_23 ``` Send this header and you get the new format immediately, on whatever date suits you, regardless of what the current default is. That is the whole opt-in — the rest of this section is the client-side work it requires. The version may also be supplied as a query parameter if a header is inconvenient. > **tip** > > Pin `v2026_01_23` explicitly rather than waiting to inherit it as the default on November 1. An explicit header means you choose the date you absorb the change, you can roll it out one environment at a time, and nothing about your client changes when the default moves underneath it. ### Step 2 — unwrap the envelope Every response body is wrapped in `data`: ```jsonc // v2024_01_01 [ { "...": "..." }, { "...": "..." } ] // v2026_01_23 { "data": [ { "...": "..." }, { "...": "..." } ], "pagination": { "size": 10, "next_cursor": "1761955200000000000" } } ``` If you want a decoder that works on both versions during the transition, unwrap only when both keys are present — that is a no-op on `v2024_01_01`: ```python def unwrap(body): if isinstance(body, dict) and "data" in body and "pagination" in body: return body["data"] return body ``` ### Step 3 — follow the cursor This is the step that silently loses data if you skip it. Under `v2024_01_01` a list read returned everything. Under `v2026_01_23` it returns **at most `size` records**, and `size` defaults to **10**. A client that issues one request and treats the result as the complete set will read 10 orders and believe that is all of them. No error is raised. | | | | --- | --- | | Page size parameter | `?size=` | | Default | `10` | | Maximum | `100` (larger values are clamped, not rejected) | | Cursor returned as | `pagination.next_cursor` | | Cursor sent back as | `?timestamp=` | | End of results | `next_cursor` is `null` | Note the asymmetry: the cursor comes back to you as **`next_cursor`** but goes out as **`timestamp`**. It is a nanosecond-precision timestamp, and paging walks backwards from it, so passing it through as an opaque string is correct — do not parse, round, or reformat it. ```python def get_all(session, path, page_size=100): results, cursor = [], None while True: params = {"size": page_size} if cursor: params["timestamp"] = cursor body = session.get(path, params=params).json() page = body["data"] results.extend(page) cursor = body["pagination"]["next_cursor"] # An empty page alongside a cursor would loop forever — treat it as the end. if not cursor or not page: break return results ``` > **warning** > > Keep the `not page` guard. An empty page returned together with a non-null cursor is the one shape that turns this loop into an infinite one. ### Step 4 — unwrap errors The RFC 7807 Problem Details body is nested under an `error` key. The fields inside it are unchanged. ```jsonc // v2024_01_01 { "type": "...", "title": "...", "status": 400, "detail": "..." } // v2026_01_23 { "error": { "type": "...", "title": "...", "status": 400, "detail": "..." } } ``` Check your error handling for code that reads `body["title"]` or `body["detail"]` directly — those move to `body["error"]["title"]` and `body["error"]["detail"]`. HTTP status codes are unchanged, so any handling keyed on the status code alone keeps working. ### Your HMAC signature is not affected Pagination adds query parameters, which raises a reasonable worry after [Step 2 of the path migration](https://docs.truemarkets.co/institutional/migrate-to-cefi-paths#step-2--re-check-your-hmac-signing). It is not a problem: **the signature covers the path only, not the query string.** Adding `?size=100×tamp=...` does not change what you sign. Sign `/v1/cefi/orders` whether you request `/v1/cefi/orders` or `/v1/cefi/orders?size=100×tamp=1761955200000000000`. ### Step 5 — verify `GET /v1/cefi/versions` reports the supported versions and the current default, so you can assert on it from a test instead of tracking this page. A good end-to-end check: create more than 10 of something, then read the list back and confirm you get all of them. That single test catches the pagination mistake, which is the one that fails quietly. Responses for a deprecated version carry RFC 8594 `Deprecation` and `Sunset` headers — worth logging so an expiring version shows up in your own telemetry before it starts failing requests. ## Reference - [CeFi REST API reference](https://docs.truemarkets.co/api/institutional/true-markets-cefi-rest) - [API Change Log](https://docs.truemarkets.co/documentation/changelog) - Questions: [support@truemarkets.co](mailto:support@truemarkets.co) --- # Exchange overview Source: https://docs.truemarkets.co/documentation/exchange-overview The True Markets exchange is a central limit order book. Its matching engine fills each incoming order against resting orders in price-time priority: the best price first, and the earliest order at that price first. The exchange settles in one designated stablecoin. Fiat and other stablecoins are converted to it on deposit, and every trading pair is quoted in it. ## Order types A **limit order** buys or sells a quantity at a price you set or better. Use it when the price matters more than filling right away. A **market order** buys or sells a quantity at the best prices on the book. Use it when filling right away matters more than the price. ## Time in force Time in force (TIF) sets how long an order stays on the book. - **Good Till Canceled (GTC):** the order rests until it fills completely or you cancel it. - **Immediate Or Cancel (IOC):** the order fills what it can against the book right away, and the exchange cancels the rest. A market order never rests on the book, whatever its time in force. ## Order checks - **Price bands:** the exchange rejects an incoming order priced too far from the current market. See [Price bands](https://docs.truemarkets.co/documentation/price-bands). - **Increment sizes:** every price and quantity must be a multiple of the minimum increment for its price range. See [Increment sizes](https://docs.truemarkets.co/documentation/increment-sizes). The [Rulebook](https://docs.truemarkets.co/documentation/rulebook) has the full trading rules. ## Protocols You reach the exchange over REST, WebSocket or FIX. REST and WebSocket are reachable over the internet. FIX runs over a private network connection that we set up with your firm. | Protocol | Use it for | Authentication | Reference | | --- | --- | --- | --- | | REST | orders, balances, transfers, trade history and market data over HTTPS | an HMAC-SHA256 signature in request headers | [CeFi direct REST](https://docs.truemarkets.co/api/institutional/true-markets-cefi-rest) | | WebSocket | streaming order books, trades and instrument updates | an HMAC-SHA256 or ES256 signature in the subscription message; public channels need none | [CeFi direct WebSocket](https://docs.truemarkets.co/api/websocket/institutional) | | FIX | low-latency order entry, market data and drop copy | a signed `Logon` (35=A) that opens the session | [CeFi direct FIX](https://docs.truemarkets.co/api/fix/overview) | FIX (Financial Information eXchange) is the standard protocol for institutional order flow. Drop copy is a read-only FIX session that mirrors every execution on your account, for allocation, compliance and audit. We issue credentials during onboarding. [Access and credentials](https://docs.truemarkets.co/institutional/access-and-credentials) explains how to apply. ## Support - Discord: [discord.gg/SC92xRUZqw](https://discord.gg/SC92xRUZqw), for general and integration questions. - Email: [support@truemarkets.co](mailto:support@truemarkets.co), for onboarding, accounts and network access. --- # Rule Book Source: https://docs.truemarkets.co/documentation/rulebook Work In Progress This Rule Book is provided for informational purposes only and remains a work in progress. The contents are subject to review, revision, and amendment at any time without prior notice. Until formally adopted and communicated as final, no provision herein should be relied upon as definitive or binding. The Exchange reserves the right to update, modify, or withdraw any portion of this Rule Book at its sole discretion and without obligation to provide advance notice. ## Rule Book: Introduction This document outlines the rules and regulations governing the use of the True Markets CeFi exchange ("The Exchange"). The Exchange, operated by True Markets, is a central limit order book platform designed for the efficient trading of crypto and stablecoins. ### 1\. Rules of Fair Practice - **1.1** Authority - **1.1.1** This Rule Book is adopted by True Markets CeFi exchange (“the Exchange”) and governs all activity by Participants. By connecting to the Exchange, each Participant agrees to comply with these Rules. - **1.2** Applicability - **1.2.1** The Rules apply to all orders, trades, and related activity conducted via the Exchange’s trading systems, APIs, and approved DeFi ingress channels (see [Appendix A](https://docs.truemarkets.co/documentation/rulebook#appendix-a)). - **1.3** Amendments - **1.3.1** The Exchange may amend this Rule Book at any time. Except in cases of emergency or legal compulsion (including but not limited to third party custody breach, regulatory order) amendments will be published at least 14 calendar days prior to effectiveness. - **1.4** Business Conduct - **1.4.1** All participants, including exchange employees, shall observe the highest standards of commercial honor and just and equitable principles of trade when conducting their business on or with The Exchange. - **1.5** Violations Prohibited - **1.5.1** No participants shall engage in conduct in violation of the result and regulations thereunder, the by-laws, exchange rules, or any policy or written interpretation of The Exchange by-laws or exchange rules. - **1.6** Fraudulent Devices - **1.6.1** No participant shall effect any transaction in, or induce the purchase or sale of, any asset by means of any manipulative, deceptive or other fraudulent device or contrivance. - **1.7** Offers at Stated Prices - **1.7.1** No participant shall make an offer to buy from or sell to any other participant any asset at a stated price unless such participant is prepared to purchase or sell, as the case may be, at such price and under such conditions as are stated at the time of such offer to buy or sell. - **1.8** Customer’s Assets or Funds - **1.8.1** The Exchange or exchange partners shall not make improper use of a customer’s assets or funds. - **1.9** Prohibition Against Guarantees - **1.9.1** The Exchange shall not guarantee, directly or indirectly, a customer against loss in any asset translation effected on The Exchange for the customer. ### 2\. Trading Rules - **2.1** Trading Hours and Trading Days - **2.1.1** Orders may be entered into the system 24 hours a day 7 days a week as long the asset is eligible for trading as outlined in [section 2.3](https://docs.truemarkets.co/documentation/rulebook#2.3) and as long as the order type is eligible to be entered as outlined in [section 2.6](https://docs.truemarkets.co/documentation/rulebook#2.6). - **2.1.2** The Exchange reserves the right to halt, suspend, or disable trading in any and all assets traded on The Exchange. The duration of any such halt, suspension or disablement is entirely at the discretion of The Exchange. - **2.1.3** The Exchange does not observe any holidays. - **2.2** Exchange Participants - **2.2.1** A Participant is an institution, individual or application that has been approved for trading by The Exchange. The participant will be assigned a unique identifier by The Exchange. - **2.2.2** The Exchange reserves the right to restrict, limit, or deny access to its systems, services, or platforms at its sole discretion based on the geographic location of a participant or the originating IP address. Such restrictions may be imposed to comply with applicable laws, regulations, or sanctions enforced by the government or regulatory authorities of the jurisdiction in which The Exchange is domiciled or operates. - **2.2.3** Access may also be limited or denied in response to changes in legal, security, or compliance obligations, including but not limited to international sanctions, embargoes, or other legal restrictions. Participants are solely responsible for ensuring that their use of The Exchange’s services complies with all applicable local laws and regulations in their jurisdiction. - **2.2.4** Participant Eligibility - **2.2.4.1** All Participants must complete KYC/AML checks, maintain good standing, and satisfy financial/technical requirements. - **2.2.5** Access - **2.2.5.1** Participants may access the exchange via REST, FIX, and WebSocket APIs. - **2.2.5.1.1** The Exchange may enforce rate limts on access to the system including but not limited to: each access point, paricipants identifier or API key(s). - **2.2.5.2** Participants must use designated API keys. Inactivity or abnormal usage may result in access suspension. - **2.3** Assets Eligible for Trading - **2.3.1** The Exchange shall designate which asset pairs are eligible for trading. - **2.4** Asset Listing - **2.4.1** Assets eligible for trading are listed in asset pairs, a base asset and a quoting asset - **2.4.1.1** Base Asset the asset being traded on the order book. - **2.4.2** Quote Asset the asset in which trading is denominated on the order book. - **2.4.3** Naming convention - **2.4.3.1** Asset pairs shall be named format defined in [table 2.4-1](https://docs.truemarkets.co/documentation/rulebook#table-2.4-1); the base asset should be listed first, a dash (-) delimiter, followed by the quote asset. | Table 2.4-1 | | | --- | --- | | Trading Pairs Name Format | Example | | `` | BTC-PYUSD | - **2.5** Designated Stablecoin - **2.5.1** The Exchange shall designate one USD based stablecoin to be used as the quote asset and the asset used to collect fees. - **2.5.2** The Exchange will automatically convert USD and other supported stablecoins to this asset upon fund allocation to The Exchange. - **2.6** Order Type - **2.6.1** Limit Orders - **2.6.1.1** An order to buy or sell a stated quantity of an asset at a specific price or better. - **2.6.1.1.1** Marketable Limit Order, an order with a specified price when buying is equal to or greater than The Exchange’s current best offer. When selling the order price is less than or equal to The Exchange’s current best bid. - **2.6.1.1.2** Post Only Limit Order, an order with a specified price and stated quantity that will be executed pursuant to The Exchange’s rules outlined in [section 2.14](https://docs.truemarkets.co/documentation/rulebook#2.14) except the order will not remove liquidity from the book. An order that would remove liquidity from The Exchange’s book is instead canceled. - **2.6.2** Market Orders - **2.6.2.1** An order to buy or sell a stated quantity of an asset at the best available price in The Exchange’s book. Market orders entered may be converted to “Marketable Limit Orders" with the price set by one of the rules outlined below. In the event the order is not fully filled the remaining quantity will be canceled back to the participant. - **2.6.2.2** Marketable Limit Order, an order with its limit price set at equal to the current contra-side best bid or best offer, such that the order is eligible for immediate execution in whole or in part upon entry into The Exchange's order book. - **2.6.2.3** Aggressive Market Order, a Marketable Limit Order with its limit price set to the maximum (for buy orders) or minimum (for sell orders) permitted by the instrument’s prevailing price bands, as follows: ```text midpoint = (best_bid + best_offer) / 2 (or the Exchange reference price if no two-sided market exists) Buy: midpoint + (midpoint * instrument.price_band_percent) (the upper price band) Sell: midpoint - (midpoint * instrument.price_band_percent) (the lower price band) ``` - **2.6.2.3.1** The participant must opt into this market order type on a per order basis. - **2.6.2.4** The Exchange provides no guarantee of execution price and shall bear no liability for adverse outcomes associated with Market Orders. - **2.6.2.5** Market orders are not eligible for execution during the instrument opening / auction phase. See [section 2.12.3.3](https://docs.truemarkets.co/documentation/rulebook#2.12.3.3). - **2.7** Order Time In Force - **2.7.1** Immediate or Cancel (IOC) - **2.7.1.1** A Limit Order as defined in [section 2.6.1](https://docs.truemarkets.co/documentation/rulebook#2.6.1) that is to be executed in whole or in part as soon as such order is received. The portion not executed immediately on The Exchange is treated as canceled and is not posted to the book. - **2.7.2** Good til Cancel (GTC) - **2.7.2.1** A Limit Order as defined in [section 2.6.1](https://docs.truemarkets.co/documentation/rulebook#2.6.1) that will remain active in the book until it is fully executed or manually canceled by the participant. - **2.7.2.1.1** Automatic cancellation, The Exchange will typically cancel GTC orders after 28 days. - **2.8** Units of Trading - **2.8.1** The Exchange may establish minimum execution quantity increments for all listed instruments. - **2.8.2** The Exchange reserves the right to modify the minimum execution quantity for a listed instrument at any time. The Exchange will provide a best effort to notify participants before a change occurs. - **2.8.3** Quantity variation changes may result in automatic cancellation of participant’s orders. - **2.8.4** Orders entered with an invalid order quantity will be rejected by The Exchange. - **2.8.5** The Exchange will apply the minimum execution quantity for all instruments as outlined in [table 2.8-1](https://docs.truemarkets.co/documentation/rulebook#table-2.8-1). | Table 2.8-1 | | | | --- | --- | --- | | Instrument | Trade Price | Minimum Execution Qty | | ALL | < 0.00001 | 100,000 | | | < 0.001 | 1,000 | | | < 0.1 | 10 | | | < 10.00 | 0.1 | | | < 1,000.00 | 0.001 | | | < 10,000.00 | 0.0001 | | | < 100,000.00 | 0.00001 | | | < 500,000.00 | 0.000001 | | | \>=500,000.00 | 0.0000001 | - **2.9** Order Price Variations - **2.9.1** The Exchange may establish minimum execution price increments for all listed instruments. - **2.9.2** The Exchange reserves the right to modify the minimum execution increments for a listed asset at any time. The Exchange will provide a best effort to notify participants before a change occurs. - **2.9.3** Price variation changes may result in automatic cancellation of participant’s orders. - **2.9.4** Orders entered with an invalid price increment will be rejected by The Exchange. - **2.9.5** The Exchange will apply the minimum execution increments for all instruments as outlined in [table 2.9-1](https://docs.truemarkets.co/documentation/rulebook#table-2.9-1). | Table 2.9-1 | | | | --- | --- | --- | | Instrument | Trade Price | Minimum Execution Price Increment | | ALL | < 0.00001 | 0.0000000001 | | | < 0.001 | 0.00000001 | | | < 0.1 | 0.000001 | | | < 10.00 | 0.0001 | | | < 1,000.00 | 0.001 | | | < 10,000.00 | 0.01 | | | < 100,000.00 | 0.10 | | | < 500,000.00 | 0.50 | | | \>= 500,000.00 | 1.00 | - **2.10** Minimum Order Value - **2.10.1** The Exchange reserves the right, at its sole and absolute discretion, to determine, impose, and amend from time to time the minimum order value applicable to any order submitted on The Exchange. The minimum notional value shall represent the minimum monetary amount required for order acceptance, calculated as the product of the order’s price and quantity. - **2.10.2** All orders submitted to The Exchange shall be subject to the prevailing minimum notional value requirement in effect at the time of submission. Orders that do not meet the applicable minimum notional value may, at The Exchange’s discretion, be rejected, canceled, or otherwise not accepted for execution. - **2.10.3** Each participant bears full responsibility for ensuring that any order submitted to The Exchange satisfies the applicable minimum notional value requirement in effect at the time of submission. The Exchange shall bear no liability for any loss, delay, or inconvenience arising from the rejection, cancellation, or non-acceptance of any order that fails to comply with this requirement. - **2.10.4** The determination of compliance with the Minimum Order Value requirement shall vary by order type, as follows: - **2.10.4.1** Market Orders - **2.10.4.1.1** The Exchange shall determine the order value using the current best bid or best offer available at the time of submission and the order’s specified quantity. If, based on prevailing market conditions, the calculated order value does not meet the Minimum Order Value threshold, or if insufficient liquidity exists to satisfy the requested size, The Exchange may, at its discretion, reject or cancel the order in whole or in part. - **2.10.4.2** Limit Orders - **2.10.4.2.1** The Exchange shall calculate the order value using the limit price specified by the participant multiplied by the order quantity. If the resulting value falls below the Minimum Order Value threshold, The Exchange may reject the order upon submission. Limit Orders modified after acceptance such that their order value no longer meets the requirement may also be subject to automatic cancellation. - **2.10.4.3** Value Limit Orders - **2.10.4.3.1** The Exchange shall determine the implied quantity based on the best available price at the time of order creation. If the calculated order value is below the applicable threshold, The Exchange may, at its discretion, reject or adjust the order prior to entry into the matching engine. - **2.10.5** The Exchange will make reasonable efforts to publish and maintain information regarding the applicable Minimum Order Value thresholds, including any subsequent modifications, through official communication channels, public notices, or the Exchange’s application programming interface (“API”) documentation. - **2.11** Self Trade Prevention - **2.11.1** The Exchange may establish self trade protection (STP) mechanisms that are defaulted to all order books and are not overridable by the end participant. - **2.11.1.1** The Exchange may also establish additional STP mechanisms for specific or individual order books. These mechanisms are not overridable by end participants. - **2.11.1.2** The defaulted self trade protection mechanism applied to all order books shall be as outlined in [section 2.11.1.2.2](https://docs.truemarkets.co/documentation/rulebook#2.11.1.2.2). - **2.11.1.2.1** The Exchange's default self trade protection mechanism will take precedence over any participant level or order level option supplied. If the default STP mechanism permits participant-level or order-level selections, then order-level selections take precedence over participant-level selections. - **2.11.1.2.2** Each book shall have no self trade prevention enabled by default. - **2.11.1.2.3** Orders may request either no self trade prevention, cancel aggressive or cancel both self trade protections - **2.11.1.2.4** Participants may request either, no self trade prevention, cancel aggressive or cancel both self trade protections. This will impact all orders entered via any API used to interact with The Exchange by the participant, unless overridden at the order level. - **2.12** Opening Cross - **2.12.1** The Exchange may establish a mechanism for opening newly listed assets or assets that have previously been halted and are returning to normal trading. - **2.12.2** The duration of the opening cross will be no shorter than 5 minutes. - **2.12.3** Processing of the Opening Cross - **2.12.3.1** When an asset enters the opening rotation The Exchange will broadcast a messaging indicating the start of the opening rotation - **2.12.3.2** Participants may submit limit orders, order cancels, and order modifications during this time. All orders entered are subject to The Exchange’s Price Band protections as outlined in [section 3.10.2](https://docs.truemarkets.co/documentation/rulebook#3.10.2). - **2.12.3.3** Participants may NOT submit market orders during the opening cross. - **2.12.3.4** During the opening cross The Exchange will publish at specific intervals, messages to participants containing but not limited to: the indicative opening price, the aggregated bid and offer quantities at the indicative opening price, and the current imbalance information - **2.12.4** Price Determination and Order Matching - **2.12.4.1** The opening cross price is the single price that maximizes executable volume and minimizes imbalance, subject to tie-breaking rules: - **2.12.4.1.1** Price that maximizes traded volume. - **2.12.4.1.2** Price that minimizes imbalance. - **2.12.4.1.3** Price closest to the reference price as determined by The Exchange. - **2.12.4.2** Limit orders at or better than the opening cross price are executed first. - **2.12.4.3** Remaining unexecuted limit orders rest in the order book for continuous trading. - **2.12.4.4** Buy orders above the opening price and outside of the opening price bands will be canceled back to the participant. Sell orders below the opening price and outside the opening price bands will be canceled back to the participant. - **2.12.5** Failure to Determine Price - **2.12.5.1** In the event The Exchange is unable to determine an opening price for the asset The Exchange may take one of the following actions: - **2.12.5.1.1** Extend the Opening cross. - **2.12.5.1.2** Default to the opening price to The Exchange’s reference price. - **2.13** Priority of Orders - **2.13.1** Orders of participants shall be ranked and maintained in The Exchange’s book according to the following schema: - **2.13.1.1** Price - **2.13.1.1.1** The highest priced order to buy or the lowest priced order to sell shall have priority over all other orders to be or sell in all cases. - **2.13.1.2** Time - **2.13.1.2.1** Subject to the order execution process described in [section 2.14](https://docs.truemarkets.co/documentation/rulebook#2.14), the order which has clearly established as the first entered order into the book at a particular price shall have precedence over other orders entered at the same time. The order shall have precedence over other orders up to the quantity specified by the order. - **2.13.1.3** Order Parameters - **2.13.1.3.1** Order parameters that modify order behaviors shall be ranked in the following order: - **2.13.1.3.1.1** Displayed Limit Orders - **2.13.1.3.1.2** Reserved - **2.13.1.3.2** Order Modifications - **2.13.1.3.2.1** An order which is modified will maintain its priority as long as the following conditions are met: - **2.13.1.3.2.1.1** Modification involves a decrease in the quantity of the order less than or equal to the current requested quantity on the order. - **2.13.1.3.2.2** An order which is modified will lose its priority if the modification changes the price in any way, increases the quantity requested greater than the current requested quantity, or changes the price type of the original order. - **2.14** Order Execution - **2.14.1** Orders shall be matched for execution and routed in accordance with the following conditions. - **2.14.1.1** Buy Orders - **2.14.1.1.1** An executable buy order will automatically match against the price(s) of the lowest priced order(s) to sell which have priority in the book. If the buy order has remaining quantity that cannot be filled by the book, the buy order will be posted to the book in accordance with [section 2.14](https://docs.truemarkets.co/documentation/rulebook#2.14). - **2.14.1.2** Sell Orders - **2.14.1.2.1** An executable sell order will automatically match against the price(s) of the highest priced order(s) to buy which have priority in the book. If the sell order has remaining quantity that cannot be filled by the book, the sell order will be posted to the book in accordance with [section 2.14](https://docs.truemarkets.co/documentation/rulebook#2.14). - **2.14.1.3** Multiple executions, an order (to buy or sell) can be executed against multiple contra orders on the opposite side of the book, up to the price/quantity requested by the incoming order. - **2.15** Algorithmic Trading - **2.15.1** The Exchange permits the use of automated trading algorithms. - **2.15.2** Automated trading algorithms must adhere to the rules and regulations of The Exchange (See [Section 3](https://docs.truemarkets.co/documentation/rulebook#3)). - **2.16** Settlement - **2.16.1** The Exchange utilizes a settlement process for trades and other financial obligations that may be executed either on an _instant_ or _batched_ basis. The timing and structure of settlement (instantaneous or batched) is determined at the sole discretion of The Exchange and may be subject to change based on business needs, risk policies, operational considerations, or third-party constraints. - **2.16.2** Settlement is facilitated through third-party custodians. The Exchange makes reasonable efforts to ensure such providers are reputable and operate in accordance with industry standards. However, The Exchange does not warrant the performance, timeliness, or availability of any third-party service, and participants agree to bear the associated risks of such reliance. - **2.16.3** At the time of settlement—regardless of whether it is instant or batched—The Exchange will automatically calculate and deduct any applicable fees, charges, or costs owed by the participant. These fees may include, but are not limited to: - **2.16.3.1** Transactional fees, including maker or taker fees. - **2.16.3.2** Regulatory or clearing surcharges. - **2.16.3.3** Penalties or adjustments. - **2.16.4** Participants are responsible for maintaining sufficient balances to satisfy all fees at settlement. Failure to do so may result in trade cancellation, account restriction, or other remedial actions as deemed necessary by The Exchange. - **2.16.5** If a settlement cannot be completed due to insufficient funds, technical error, counterparty failure, or external constraints, The Exchange reserves the right to: - **2.16.5.1** Reattempt the settlement at a later time. - **2.16.5.2** Reverse or cancel the affected transaction(s). - **2.16.5.3** Impose penalties or suspend the relevant account. - **2.16.5.4** Seek recovery of owed amounts through other legal or contractual means. - **2.16.5.5** All trades settle in the desginated stablecoin. - **2.16.6** The Exchange reserves the right to amend the settlement process, including timing, fees, methods, and counterparties, with or without prior notice. Material changes will be communicated via The Exchange standard communication channels (e.g., website, client portal, API changelog). - **2.17** Changes to Trading Software - **2.17.1** The Exchange reserves the right to modify, enhance, update, or otherwise change its software, systems, interfaces, or services at any time, including changes that may affect how external applications interface with the platform. These changes may include, but are not limited to, alterations to APIs, data formats, response structures, authentication methods, or other integration points. - **2.17.2** The Trading will make reasonable efforts to notify participants of material updates in advance and may, at its discretion, provide access to testing environments, release notes, or other resources to facilitate integration and compatibility testing. - **2.17.3** It is the sole responsibility of participants to ensure that their systems and applications are designed and maintained in a manner that can accommodate such changes. The Exchange shall not be liable for any disruption, incompatibility, or loss arising from a participant’s failure to update or test their systems in accordance with provided notices or guidelines. - **2.18** Dispute Resolution - **2.18.1** All disputes or complaints relating to The Exchange must be submitted in writing, either by email to The Exchange’s designated dispute resolution address or by mail to The Exchange’s registered office. Each submission shall include: - **2.18.1.1** The full name and contact information of the submitting party. - **2.18.1.2** A description of the issue and the events giving rise to the dispute. - **2.18.1.3** The outcome or remedy requested. - **2.18.1.4** Supporting documentation. - **2.18.2** The Exchange shall acknowledge receipt of any dispute within 3–5 business days. A formal written response shall be provided within 45 business days after acknowledgement, depending on the complexity of the matter. - **2.18.3** All disputes shall be reviewed in good faith. The Exchange may request additional details or documentation as required. Failure to provide requested information within a reasonable timeframe may result in dismissal of the dispute. - **2.18.4** Following review, The Exchange shall issue a written decision. Such decision shall be considered final and binding under these Rules, subject to applicable law. ### 3\. Market Integrity - **3.1** Trade Executions and Errors - **3.1.1** All trades on The Exchange are final and cannot be reversed unless required by law, regulation, or to address, in The Exchange's sole discretionm serious technical errors as outlined in [section 3.15](https://docs.truemarkets.co/documentation/rulebook#3.15). - **3.2** Market Manipulation - **3.2.1** No participant shall execute or cause to be executed or participate in an account for which there are executed purchases of any asset at successively higher prices, or sales of any asset at successively lower prices, for the purpose of creating or inducing a false, misleading or artificial appearance of activity in such security on The Exchange or for the purpose of unduly or improperly influencing the market price for such asset or for the purpose of establishing a price which does not reflect the true state of the market in such assets. - **3.2.2** If a participant engages in market manipulation or prohibited trading, The Exchange may without notice suspend or terminate the participant’s access to The Exchange. - **3.3** Fictitious Transactions - **3.3.1** No participant, for the purpose of creating or inducing a false or misleading appearance of activity in an asset traded on The Exchange or creating or inducing a false or misleading appearance with respect to the market in such security shall: - **3.3.1.1** Execute any translation in an asset which involves no change in the beneficial ownership thereof. - **3.3.1.2** Enter any order or orders for the purchase or sale of a security with the knowledge that such order or orders will, or are reasonably likely to, result in a trade against a specific counterparty or counterparties, where such knowledge arises from prior agreement, arrangement, or understanding with the counterparty or any related party. - **3.4** Manipulative Transactions - **3.4.1** No participant shall participate or have any interest, directly or indirectly, in the profits of a manipulative operation or knowingly manage or finance a manipulative operation. - **3.4.2** Any joint account organized or used intentionally for the purpose of unfairly influencing the market price of a security shall be deemed to be a manipulative operation. - **3.5** Excessive Sales - **3.5.1** No participant shall execute purchases or sales in any asset traded on The Exchange for any account in which such participant is directly or indirectly interested, which purchases or sales are excessive in view of the participant’s financial resources or in view of the market for such asset. - **3.6** Prohibition Against Trading Ahead of Customer Orders - **3.6.1** A participant (or its associated persons) that has Knowledge of an unexecuted Customer Order must not execute a proprietary trade in the same asset, on the same side, at a price that would satisfy or improve the Customer Order, unless the Customer Order is executed first or receives at least equivalent or better execution immediately thereafter. - **3.6.2** A participant must not enter or alter a proprietary order, or recommend/solicit such activity, in anticipation of a known imminent execution or public dissemination of a Customer Order or negotiation of a customer block that could materially affect price (“front-running”). - **3.6.3** Participants must maintain policies, procedures, and information barriers reasonably designed to prevent misuse of Customer Order information and inappropriate dissemination across trading, market-making, and sales functions. - **3.7** Account Take Over - **3.7.1** The Exchange reserves the right to suspend, restrict, or deactivate any participant account, without prior notice, if there is a reasonable suspicion of unauthorized access, account takeover, or compromise of login credentials. Measures may be taken to protect the integrity of The Exchange, its participants, and the broader platform ecosystem. During a suspension or investigation period, access to the account and its associated assets or features may be temporarily limited. - **3.7.1.1** Indicators of a suspected account takeover may include, but are not limited to: - **3.7.1.1.1** Unusual login behavior or access patterns. - **3.7.1.1.2** Login attempts originating from locations not associated with the account. - **3.7.1.1.3** Attempts to alter critical account settings or security controls. - **3.7.1.1.4** Initiation of unauthorized transactions. - **3.7.1.1.5** Reports from the account owner or third parties regarding suspicious activity - **3.7.2** The Company will make reasonable efforts to verify the identity of the rightful account holder and restore access when appropriate. However, The Exchange disclaims liability for losses, delays, or restrictions resulting from such protective measures. - **3.8** Disruptive Quoting - **3.8.1** No participant shall engage in or facilitate disruptive quoting and trading activity on The Exchange, including acting in concert with other persons to effect such activity - **3.8.1.1** Disruptive Quoting shall be defined as: - **3.8.1.1.1** Excessively entering non-bonafide orders. - **3.8.1.1.2** Excessively entering and then canceling bonafide or non-bonafide orders. - **3.8.1.1.3** Entering multiple orders on one side of the market with the intent to create a false impression of supply or demand, thereby inducing trades on the opposite side of the market that result in the execution of their own orders.contra side of the market. - **3.8.1.1.4** Entering order(s) to narrow the market spread with the intent to induce other participants to trade at the new price, followed by submitting an opposite-side order that exploits the artificially narrowed spread. - **3.9** Rate Limiting - **3.9.1** To maintain the stability, performance, and security of the platform, The Exchange reserves the right to enforce rate limits and other usage restrictions on participant access to its systems, services, or APIs. These limitations may include, but are not limited to: - **3.9.1.1** Maximum number of requests per second, minute, or hour. - **3.9.1.2** Bandwidth consumption thresholds. - **3.9.1.3** Restrictions on simultaneous connections or sessions. - **3.9.1.4** Query complexity or payload size controls. - **3.9.2** Participants are expected to design their systems to comply with published rate limits and to use available sandbox or testing environments when appropriate. The Exchange may provide public documentation or headers detailing rate limits, but actual thresholds are subject to change at The Exchange’s sole discretion. - **3.9.3** Violations of rate limits or sustained bandwidth abuse may result in temporary throttling, access suspension, or permanent revocation of access credentials. The Exchange disclaims any liability for interruptions or losses resulting from rate limiting enforcement. - **3.10** Circuit Breakers - **3.10.1** Price Limits - **3.10.1.1** The Exchange shall limit the maximum price range of an asset over a specific period of time. - **3.10.1.2** When a maximum price range is reached The Exchange may take the following actions: - **3.10.1.2.1** Temporarily halt the market. - **3.10.1.2.2** Permanently halt the market. - **3.10.2** Price Bands - **3.10.2.1** The Exchange shall subject all orders entered to price validation. If an order does not meet the pricing requirements it may be rejected by The Exchange. Price bands are calculated dynamically for each asset based on the mid-point price of the current market plus/minus a fixed percentage band value. If no market exists a reference price designated by The Exchange. - **3.11** Asset Reference Price - **3.11.1** The Exchange may designate a Reference Price for each listed asset for use in, among other things, opening auctions, price bands, and error checks. The Reference Price may be determined by automated procedures or by authorized Exchange staff. - **3.11.2** The Exchange may designate, update, or restate a Reference Price at any time as reasonably necessary to ensure fair and orderly markets, including prior to the opening auction, following a trading halt, or after a material action. - **3.12** Interruptions - **3.12.1** The Exchange strives to maintain uninterrupted service but may experience interruptions. In such cases, efforts will be made to minimize disruptions to trading. During interruptions, The Exchange may take the following actions: - **3.12.1.1** Cancel open orders to prevent unintended execution. - **3.12.1.2** Disable the ability to place new orders temporarily. - **3.12.1.3** Disable access to The Exchange via the web interface and API until normal operations resume. - **3.13** Market Stability - **3.13.1** Cancellation of open orders - **3.13.1.1** True Markets reserves the right to cancel Open Orders for any reason, including but not limited to: - **3.13.1.1.1** Abusive use of the platform e.g. market manipulation in accordance with [section 3.2](https://docs.truemarkets.co/documentation/rulebook#3.2). - **3.13.1.1.2** Clearly erroneous transactions. - **3.13.1.1.3** Legal or regulatory. - **3.14** Clock Synchronization - **3.14.1** Unless otherwise noted, The Exchange ensures the clocks that it uses on all system logs and any other reportable events are time stamped in UTC and synced against an available time synchronization service as chosen by The Exchange. - **3.15** Serious Technical Error - **3.15.1** The Exchange has sole discretion to determine whether an event constitutes a Technical Error for purposes of order handling, trade validity, and market operations. The Exchange may cancel, adjust, or restate trades, reject or purge orders, or take other remedial action it deems necessary to maintain fair and orderly markets. - **3.15.2** A Technical Error is any malfunction, disruption, or condition - whether internal to The Exchange or external but affecting its orderly operation—that materially impacts the ability of The Exchange to receive, process, execute, or report orders and trades as intended. Including but not limited to: - **3.15.2.1** Hardware, software, or network outages or degradation within The Exchange’s matching engine, gateways, or market data systems. - **3.15.2.2** Erroneous, missing, or corrupt market data, reference prices, or order book states. - **3.15.2.3** Loss or instability of communication between participants and The Exchange or among Exchange subsystems. - **3.15.2.4** Orders that generate excessive, inconsistent, or looping messages due to participant or Exchange error. - **3.15.2.5** Material errors in critical external feeds (e.g., consolidated market data, clearing/settlement systems, regulatory halts) that impair Exchange operations. - **3.15.2.6** Any other condition which, in the judgment of The Exchange, compromises the fairness, accuracy, or integrity of trading. - **3.16** Force Majeure Events - **3.16.1** The Exchange shall not be liable for any inaccuracy, error, delay in, or omission of any information or the transmission or delivery of information, or the loss or damage arising from any event beyond The Exchange’s reasonable control, including but not limited to flood, extraordinary weather conditions, earthquake, or other act of god, fire, war, insurrection, riot, labor dispute, accident, action of government, communications failure, power failure, internet failure, or equipment or software malfunction. - **3.16.2** For the avoidance of doubt, a Force Majeure Event includes an event orchestrated by a third-party to disrupt The Exchange’s ability to offer services, including a breach, DDOS attack, or other cyber events that may require The Exchange’s services to be discontinued or temporarily halted, whether out of effect, or out of necessary caution, or any other reason in The Exchange’s sole discretion. - **3.17** Clawback of Funds - **3.17.1** The Exchange may also exercise Clawback in the event of clearly erroneous trades, settlement failures, regulatory directives, or extraordinary events beyond the reasonable control of the Exchange that materially impact market integrity or proper settlement. - **3.17.1.1** Malicious or Prohibited Conduct. Where a Participant engages in fraud, manipulation, abuse, or other activity in violation of these Rules or applicable law, including conduct intended to disrupt orderly market operations or obtain unfair advantage. - **3.17.1.2** Serious Technical or Operational Error. Where a material systems malfunction, operational error, or failure of the Exchange results in executions or positions inconsistent with the intended operation of the market. - **3.17.1.3** Clearly Erroneous Transactions. Where trades occur at prices or quantities substantially inconsistent with prevailing market conditions, or where such trades are the result of obvious error in order entry or system processing. - **3.17.1.4** Risk Control Violations. Where executions occur in contravention of the Exchange’s risk parameters, including but not limited to credit limits, position limits, kill switches, or other protective controls. - **3.17.1.5** Settlement Failures. Where a Participant fails to make or receive payment or delivery as required, and reversal or adjustment is necessary to preserve market integrity. - **3.17.1.6** Regulatory or Legal Requirement. Where reversal or adjustment is mandated by applicable law, regulation, court order, or direction of a competent authority. - **3.17.1.7** Extraordinary Events. Where circumstances beyond the reasonable control of the Exchange, including but not limited to natural disasters, cyberattacks, or other force majeure events, materially impair the fair and orderly operation of the market. - **3.17.2** In exercising a Clawback, the Exchange may debit or credit Participant accounts, cancel or amend trades, or otherwise take such actions as it deems necessary to restore a fair and orderly market. Participants acknowledge and agree that the Exchange shall not be liable for any losses, costs, or damages arising from a Clawback undertaken pursuant to this Section, except as may be expressly required by applicable law. ### 4\. Trading Fees - **4.1** Reference Asset - **4.1.1** The Exchange will settle all fees in the reference asset. - **4.1.1.1** Participants must have an available balance of the reference asset in order to place a buy trade as outlined in [section 2.14.1.1](https://docs.truemarkets.co/documentation/rulebook#2.14.1.1) of the Trading Rules. Participants with insufficient balance will not be able to place orders on The Exchange. - **4.1.1.2** Participants do not need to have an available balance of the reference asset in order to place a sell trade as outlined in [section 2.14.1.2](https://docs.truemarkets.co/documentation/rulebook#2.14.1.2). Fees will be deducted from the resulting trade balance. Participants may therefore be credited or debited an amount less than the gross trade result in an amount equal to the net proceeds after fees. - **4.1.1.3** The amount of reference asset held in escrow is calculated as a percentage of the order quantity times the order price. This is known as the fee hold. - **4.1.1.4** Market Orders Fee Calculation - **4.1.1.4.1** The amount of reference asset held in escrow for market orders will be calculated on order entry based on the current exchange best bid or best offer. If the participant does not have enough available balance the market order will be rejected. - **4.1.2** The Exchange will hold in escrow the worst possible fee outcome for each order placed onto The Exchange. Should that order execute, The Exchange will calculate the exact fee as outlined in [section 4.6](https://docs.truemarkets.co/documentation/rulebook#4.6) and return any remaining balance to the participant. - **4.2** Fee Schedule Changes - **4.2.1** The Exchange reserves the right, at its sole discretion, to change the fee schedule for any order book at any time. The Exchange will make a best effort to alert participants to the change before implementing the fee schedule change. - **4.3** Volume Based Fee Discounts - **4.3.1** Reserved - **4.4** Fee Refunds - **4.4.1** All fees are non-refundable - **4.4.2** The Exchange reserves the right to refund fees at its discretion for any reason. - **4.5** Inactivity / Maintenance Fee - **4.5.1** The Exchange does not charge fees for account inactivity or for account maintenance. - **4.6** Fee Schedule - **4.6.1** The Exchange fee schedule is published separately from this document. - **4.6.2** Notice of Changes - **4.6.2.1** The Exchange will provide at least 1 day's notice before implementing changes to the fee schedule. The Exchange will provide, at its best effort, notices to participants regarding the fee schedule changes. ### Definitions - **Asset -** A crypto or fiat currency supported by The Exchange. - **Stablecoin -** A type of cryptocurrency designed to maintain a stable value relative to a reference asset. - **Self Trade Protection -** A mechanism implemented by The Exchange to prevent orders placed by the same participant (or beneficial owner) from matching against each other. This helps to stop “self trades,” which can create misleading volume, distort market prices, or potentially be used for manipulative practices. - **Cancel Aggressive** - Two orders from the same participant would trade with each other, the incoming (aggressive) order is canceled. This is also known as “cancel newest”. - **Cancel Both -** Two orders from the same participant would trade with each other, both the incoming and resting orders are canceled. - **Beneficial Ownership -** A person or entity that ultimately owns or controls an asset, such as a company, property, or financial instrument, and benefits from it, even if the asset is held in someone else's name (e.g., through nominees, trusts, or other intermediaries). - **Indicative Opening Price** - The potential opening price disseminated during the opening rotation period (IOP). - **Opening Rotation** - The period during which orders may be entered, canceled, or modified prior to the auction match. - **Opening Cross** - Price at which the asset is opened and orders are executed. Normal trading follows immediately. - **Fee Hold** - Worst case possible fee held by the exchange in escrow. ### Appendix A - Decentralized Order Ingress - This Appendix governs the receipt and execution of orders submitted to the Exchange through decentralized finance (“DeFi”) sources, including but not limited to on-chain wallets, smart contracts, and approved cross-chain bridge mechanisms. These provisions apply in addition to the general Exchange Rules and are binding on all Participants utilizing DeFi ingress channels. - Eligibility - Each wallet, smart contract, or bridge endpoint used for ingress must be explicitly associated with a verified Participant entity. Association shall be demonstrated through cryptographic proof of ownership or control (e.g., signed messages). Orders submitted from unaffiliated or unverified addresses shall not be accepted. The Exchange reserves the right to suspend or revoke eligibility for any address or contract that fails ongoing verification or monitoring requirements. - Order Execution - Orders are deemed received when recorded by the Exchange’s secure gateway. Each order shall be time-stamped to the nanosecond and prioritized accordingly. Blockchain mempool timestamps, gas-fee bidding, or network propagation times are not considered for purposes of sequencing or priority on the Exchange. - Trading Fees - Orders submitted via DeFi ingress shall be subject to the same maker/taker fee schedule as API-sourced orders, unless otherwise published by the Exchange. - Aggregators and routers must disclose to their users any fee-sharing or rebate arrangements that affect effective execution costs. - Asset Conversion Policy - The Exchange designates certain stablecoins as eligible base assets for trading and settlement. Orders submitted via DeFi ingress that reference a non-supported stablecoin, or a digital asset marketed as a stablecoin but not recognized by the Exchange, may be automatically converted into the Exchange’s designated stablecoin at prevailing market rates prior to execution. - Conversion shall be conducted through Exchange-approved liquidity pools or market counterparties. The conversion rate applied shall reflect the best available price reasonably obtainable by the Exchange at the time of processing. - This policy applies both to order submission and to settlement balances. Assets received in unsupported stablecoins may be converted upon receipt, and proceeds of completed trades may be settled in the designated stablecoin. - All conversion events will be reflected in the Participant’s transaction records and account statements. - The Exchange assumes no liability for slippage, depegging, or valuation losses associated with conversion of unsupported stablecoins. Participants remain responsible for ensuring that deposits and orders are submitted in recognized formats. ### Appendix B - Changelog | Version | Date | Notes | | --- | --- | --- | | v1.0.2 | 2026-07-22 | Add per-item section numbering and linkable anchors; repair drifted cross-references; align aggressive market-order pricing with implementation. | | v1.0.1 | 2025-10-24 | Add minimum value order ruleset. | | v1.0.0 | 2025-10-16 | Initial Release. | --- # Increment sizes Source: https://docs.truemarkets.co/documentation/increment-sizes Every order on the exchange must use a price and a quantity that fit the increments for its price bucket. A bucket is a price range, and each bucket sets its own minimum price step and quantity step. The exchange rejects an order that doesn't fit. ## Minimum increments | Bucket | Lower Price ($) | Upper Price ($) | Tick Size ($) - Quote Increment | Lot Size - Base Increment | Minimum Tradable Notional | | --- | --- | --- | --- | --- | --- | | 1 | 500,000.00000 | ∞ | 1 | 0.0000001 | 1.00 | | 2 | 100,000.00000 | <= 499,999.5000000000 | 0.5 | 0.000001 | 1.00 | | 3 | 10,000.00000 | <= 99,999.9000000000 | 0.1 | 0.00001 | 1.00 | | 4 | 1,000.00000 | <= 9,999.9900000000 | 0.01 | 0.0001 | 1.00 | | 5 | 10.00000 | <= 999.9990000000 | 0.001 | 0.001 | 1.00 | | 6 | 0.10000 | <= 9.9999000000 | 0.0001 | 0.1 | 1.00 | | 7 | 0.00100 | <= 0.0999990000 | 0.000001 | 10 | 1.00 | | 8 | 0.00001 | <= 0.0000999900 | 0.00000001 | 1,000 | 1.00 | | 9 | 0.00000 | <= 0.0000099999 | 0.0000000001 | 100,000 | 1.00 | - **Bucket**: the pricing tier for a range of prices. - **Lower Price ($)**: the minimum price for the bucket, in the quote asset of the instrument pair. - **Upper Price ($)**: the maximum price for the bucket, in the quote asset of the instrument pair. ∞ means no upper bound. - **Tick Size ($) - Quote Increment**: the smallest allowed change in the quoted price within the bucket. - **Lot Size - Base Increment**: the smallest quantity change in the base asset that can be traded in the bucket. - **Minimum Tradable Notional**: the smallest notional of an instrument that can be traded, set by the exchange. ## Quote-sized order behavior When you size an order in the quote asset instead of the base asset, the exchange converts it to the largest executable base quantity that fits that notional. That quantity still respects the base increment at the final price level consumed from the book. So a quote-sized order can fill slightly less than the amount you asked for. The exchange doesn't loosen your limit price to fill the remainder. The remainder stays unfilled because it is smaller than one base increment at that price. In schema `v2026_01_23`, the `POST /v1/cefi/markets/quote` response also reports: - `notional`: the expected quote amount that would execute from visible book liquidity. - `notional_residual`: the portion of the requested quote amount left over after increment rounding. ## Next steps - Preview a quote-sized order with [Compute market quote](https://docs.truemarkets.co/api/institutional/post-markets-quote-cefi). - See [Price bands](https://docs.truemarkets.co/documentation/price-bands) for the price check that runs alongside the increment check. --- # Price bands Source: https://docs.truemarkets.co/documentation/price-bands The exchange rejects an incoming order priced too far from the current mid-point. This reduces erroneous trades and sudden price dislocations. ## How price bands work The exchange calculates a band for each asset around its current mid-point. - The mid-point is the average of the current best bid and best ask. - The permitted range for incoming orders is the mid-point ± a percentage, typically 2.5%. - The percentage can change per asset or with market conditions. The value on this page is illustrative only. If an asset has no active market (no bids or asks), the exchange uses a reference price it designates to set the band. ## Order validation When you submit an order, the exchange checks its price against the current band and rejects it if it falls outside. Passive orders (those that rest on the book without executing immediately) are not constrained by the bands. The band is recalculated whenever the mid-point changes, so it moves with the market. ## Reach liquidity outside the band The bands center on the mid-point rather than the best bid or ask. Your order can be rejected when it tries to interact with a resting order that lies outside the current band. To reach that liquidity, "walk" your order: move the price toward the resting order in steps until it falls inside the band. ## Next steps - [Increment sizes](https://docs.truemarkets.co/documentation/increment-sizes): the increment check that runs alongside the band check. - [Rulebook](https://docs.truemarkets.co/documentation/rulebook): the full trading rules. --- # Fee schedule Source: https://docs.truemarkets.co/documentation/fee-schedule What the CeFi exchange charges per executed order, by client type and side. True Markets' CeFi exchange (the "Exchange") charges trading fees per executed order according to the tiered schedule below. All fees are shown in basis points (bps) of the executed notional: 1 bp = 0.01%, so 4 bps = 0.04%. Negative values are rebates paid to the participant. Per Rulebook Section 4, the Exchange provides at least one day's notice before implementing changes to the fee schedule. This page reflects the schedule currently in effect; advance notices of upcoming changes are communicated separately and do not appear here. The machine-readable source for this page is `GET /v1/cefi/operations/fees/schedule`. ## Institutions | Tier Band | Maker Fee (bps) | Taker Fee (bps) | | --- | --- | --- | | 0 | 0 | 0 | ## Individuals | Tier Band | Maker Fee (bps) | Taker Fee (bps) | | --- | --- | --- | | 0 | 0 | 0 | ## Applications | Tier Band | Maker Fee (bps) | Taker Fee (bps) | | --- | --- | --- | | 0 | 0 | 0 | - **Tier Band**: display ordinal of the fee band; not an account tier identifier. - Fees are in basis points (bps); 1 bp = 0.01% of the executed notional. - A dash means no fee is currently in effect for that side. - Negative values are rebates paid to the participant. The fee on each fill is reported in the misc fees group of the FIX [ExecutionReport](https://docs.truemarkets.co/api/fix/order-entry). The notice period for changes is in the [Rulebook](https://docs.truemarkets.co/documentation/rulebook). --- # About Market data Source: https://docs.truemarkets.co/market-data/overview Market data lets you read prices, candles and order books for the assets True Markets lists. The calls on this page need no account and no token, so you can make them before you build anything else. Gateway, the Trading API and institutional clients all read the same data. ## Base URL REST calls go to `https://api.truemarkets.co` with no `Authorization` header. ## Get candles A candle is the open, high, low and close price for one time bucket. Ask for an asset's candles over a lookback `window`, one candle per `resolution`. Both take the form `{value}{unit}`: `window` accepts `m`, `h`, `d` and `M`, and `resolution` accepts `s`, `m`, `h` and `d`. `asset_id` is the asset's id in the asset catalog. [What you can trade](https://docs.truemarkets.co/trading-api/what-you-can-trade) shows how to list the catalog and find it. Hourly candles for the last day ```bash curl -s -G \ https://api.truemarkets.co/v1/defi/market/prices/candles \ -d asset_id=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d \ -d window=1d \ -d resolution=1h ``` 200: candles come back oldest first, prices as strings ```json { "asset_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "symbol": "SOL", "window": "1d", "resolution": "1h", "candles": [ { "t": "2026-09-28T16:00:00Z", "open": "195.10", "high": "196.02", "low": "194.80", "close": "195.31" } ] } ``` Prices are strings, so you keep full decimal precision. Candles need an asset priced from trade-by-trade data. An asset priced another way returns `400`, and an unknown asset or one with no price data returns `404`. Every error body has a `type` to branch on and a `request_id` to include when you contact support. ## REST and WebSocket Use REST to read current and historical prices on demand. Use WebSocket to receive updates as they happen, without polling. Market data covers both venues. A venue is where an asset trades: `defi` assets trade on-chain, and `cefi` assets trade on the True Markets exchange. **REST** - [Market data REST](https://docs.truemarkets.co/api/market-data/market-data-service-api): every endpoint, schema and error code. - [Get OHLC candles for a symbol](https://docs.truemarkets.co/api/market-data/get-ohlc-candles-for-a-symbol): the candles call above. - [Get price candles by symbol](https://docs.truemarkets.co/api/market-data/get-price-candles-by-symbol) - [Get CeFi instrument price history](https://docs.truemarkets.co/api/market-data/get-ce-fi-instrument-price-history) **WebSocket** - [Market data WebSocket](https://docs.truemarkets.co/api/websocket/market-data): streams for `defi` assets. - [CeFi direct WebSocket](https://docs.truemarkets.co/api/websocket/institutional): order book and trade streams from the exchange. Public channels need only a current timestamp and publish batched updates, typically every second. --- # Developer resources Source: https://docs.truemarkets.co/developer-resources Keys, tokens and errors work the same way in Gateway and the Trading API. Reference for keys, tokens and errors, shared by Gateway and the Trading API. ## Essentials - [API keys](https://docs.truemarkets.co/developer-resources/api-keys): Which keys you hold, what each one signs, and how to protect them. - [Authentication](https://docs.truemarkets.co/developer-resources/authentication): Mint a token from your key, send it, and replace it when it expires. - [Errors](https://docs.truemarkets.co/developer-resources/errors): The error body, what each status means, and what to retry. ## Tools - [SDKs](https://docs.truemarkets.co/sdks/overview): TypeScript and Python clients that sign for you. - [CLI](https://docs.truemarkets.co/cli/overview): Trade from your terminal, with JSON output and dry runs. - [MCP servers](https://docs.truemarkets.co/mcp): Market data and trading tools for AI assistants. ## Reference and changes - [API reference](https://docs.truemarkets.co/api): Every endpoint, field and error code, generated from our specs. - [Changelog](https://docs.truemarkets.co/documentation/changelog): What changed in each API, newest first. --- # API keys Source: https://docs.truemarkets.co/developer-resources/api-keys The keys you hold depend on who trades. An API trader holds one API key. A Gateway client holds an organization API key and a signer key. Every key is an EC P-256 key pair. You keep the private half, and only the public half ever reaches us. ## What each key does A key signs one of two things: - **A sign-in challenge.** You sign it to mint a token, and the token authenticates your calls. [Authentication](https://docs.truemarkets.co/developer-resources/authentication) shows the call. - **A payload.** An order or transfer returns unsigned payloads, and nothing moves until you sign them and execute. | Key | Who holds it | Where it comes from | Signs | | --- | --- | --- | --- | | API key | an API trader | [app.truemarkets.co](https://app.truemarkets.co/) | the sign-in challenge and your own payloads | | organization API key | a Gateway client | the [developer console](https://console.truemarkets.co/) | the sign-in challenge only | | signer key | a Gateway client | you generate it | your users' payloads | An API trader's key does both jobs because the app registers its public half on your account and on your wallet. A Gateway client needs two keys because the jobs belong to different owners. The organization key signs in as your business. The signer key is registered on each user when you create them, and there's no way to rotate it yet. [Signer keys](https://docs.truemarkets.co/gateway/signing-keys) covers where to keep it. ## How each key signs Signing in and signing a wallet transaction use different formats. Each product has a walkthrough with a diagram and runnable code: [Gateway](https://docs.truemarkets.co/gateway/requests-and-signing) for an organization's two keys, and [Trading API](https://docs.truemarkets.co/trading-api/requests-and-signing) for an individual's one key. ## Protect your keys Anyone with your private key can mint tokens as you and sign from every wallet the key is registered on. - Keep key files out of your repo and out of chat tools. - If a key leaks, revoke it where you created it, at [app.truemarkets.co](https://app.truemarkets.co/) or on the [developer console](https://console.truemarkets.co/)'s API keys page, and create a new one. Tokens it already minted keep working until they expire. - If you lose a signer key, the wallets it's registered on can never sign again, and we can't recover them. Keep it in an HSM or a secrets manager, with a backup. - A signer key registered on many users can sign for all of them. Guard it the way you guard a production database credential. - Mint tokens from the key when you need them, rather than storing tokens long term. Next: [Authentication](https://docs.truemarkets.co/developer-resources/authentication) --- # Authenticate requests Source: https://docs.truemarkets.co/developer-resources/authentication Every authenticated call carries a token. You mint the token by signing a short challenge with your API key, and you replace it when it expires. ## Mint a token The challenge is the string `{key_id}.{timestamp}`, where `timestamp` is the current Unix time in seconds. It must be within 30 seconds of our clock. Sign it with ES256, concatenate `r` and `s`, base64url-encode the result, and post it with the key id and timestamp. The response depends on the key. An API trader's key returns an access token and a refresh token. A Gateway client's organization key returns an access token only. The signing code is in TypeScript in each quickstart: [Trading API](https://docs.truemarkets.co/trading-api/quickstart#2-mint-a-token) and [Gateway](https://docs.truemarkets.co/gateway/quickstart#2-mint-an-organization-token). Exchange a signature for a token ```bash curl -s -X POST \ https://api.truemarkets.co/v1/auth/api-key/token \ -H "Content-Type: application/json" \ -d '{ "key_id": "a1b2c3d4-…", "timestamp": 1790620800, "signature": "q3Zf0vN8kT2L…" }' ``` 200 for an API trader's key ```json { "access_token": "eyJhbGciOiJFUzI1NiIs…", "refresh_token": "eyJhbGciOiJFUzI1NiIs…", "expires_in": "2026-09-28T18:11:27Z", "token_type": "Bearer" } ``` 200 for an organization key ```json { "access_token": "eyJhbGciOiJFUzI1NiIs…", "token_type": "Bearer", "expires_in": "2026-09-28T18:11:27Z" } ``` ## Send the token Put `Authorization: Bearer ` on every authenticated call. When a Gateway client acts for one of its users, the request also carries `TM-On-Behalf-Of: `. [Trade for a user](https://docs.truemarkets.co/gateway/trade-for-a-user) covers that header. ## Replace an expired token An access token lasts an hour, and `expires_in` is the time it expires, not a number of seconds. Before then, an API trader exchanges the refresh token for a new pair without signing again. A refresh token lasts 30 days, and each refresh returns a new one, so keep the latest. An organization has no refresh token, so a Gateway client mints a new token from the key. Revoking a key stops its refresh tokens, but access tokens already minted keep working until they expire. A `401` on any call means the token is missing, invalid or expired, or that an organization token called a user route without `TM-On-Behalf-Of`. Get a new token or add the header, then retry the request. Refresh a token, API traders only ```bash curl -s -X POST \ https://api.truemarkets.co/v1/auth/token/refresh \ -H "Content-Type: application/json" \ -d "{\"refresh_token\": \"$REFRESH_TOKEN\"}" ``` Next: [Errors](https://docs.truemarkets.co/developer-resources/errors) --- # Errors Source: https://docs.truemarkets.co/developer-resources/errors Every error returns the same JSON body. Branch on `code` when you recognize it, fall back to `type` when you don't, and handle the cases in [Handle common errors](https://docs.truemarkets.co/developer-resources/errors#handle-common-errors). ## The error body An error body ```json { "type": "failed_precondition", "code": "quote_stale", "message": "quote expired before it landed; request a new quote", "request_id": "7b1f0c2e-5a9d-4e36-8f10-2d4c6b8a9e31" } ``` | Field | Meaning | | --- | --- | | `type` | The category of error. There is a fixed set of types, and each one maps to one HTTP status. | | `code` | The specific condition, such as `quote_stale`. We add codes over time, so handle the ones you know and fall back to `type` for the rest. Omitted when no specific condition applies. | | `message` | A description for people. Don't branch on it, because the wording can change. | | `request_id` | Our id for the request. Include it when you write to support. Send your own `X-Request-Id` header and we use that instead. | | `field_violations` | On `invalid_argument`, the fields that failed validation and why. | | `metadata` | Extra string values for the `code`. Which keys appear depends on the code. | ## Status for each type | `type` | Status | | --- | --- | | `invalid_argument` | 400 | | `unauthenticated` | 401 | | `permission_denied` | 403 | | `not_found` | 404 | | `method_not_allowed` | 405 | | `conflict` | 409 | | `failed_precondition` | 422 | | `resource_exhausted` | 429 | | `internal` | 500 | | `unavailable` | 503 | ## Handle common errors | You see | Do this | | --- | --- | | `401` | Check the token and, for a Gateway call, the `TM-On-Behalf-Of` header. Mint or refresh the token once; retrying without a change fails the same way. | | `422` with `quote_stale` | The quote expired. Place the order again and execute right away. | | `201` with an empty `order_id` | No order was created, and `quote.issues` says why. Usually the wallet can't fund the buy: fund it and place the order again. | | `422` with `insufficient_balance` | The wallet can't fund a sell or perpetual order. Fund it, then place the order again. | | `409` with `already_submitted` on execute | The transaction already landed. Don't resubmit; read the order. | | `429` | You sent too many requests. Back off, then retry. Your token is still valid. | | `503` when you create a user | Repeat the same request. It finishes creating the wallets. | | `500` or `503` on other calls | Retry with backoff. If it keeps failing, send us the `request_id`. | Next: the [API reference](https://docs.truemarkets.co/api) has the responses for each endpoint. --- # Clients and SDKs Source: https://docs.truemarkets.co/sdks/overview Call the Trading API from TypeScript or Python. The SDKs mint and refresh tokens for you from the key file that `TM_KEY_FILE` points at. | Language | Package | Requires | | --- | --- | --- | | TypeScript | `@truemarkets/sdk` | Node 18+ or Bun | | Python | `truemarkets` | Python 3.10+ | Methods are generated from the OpenAPI spec and named after its `operationId`s: `listAssets`/`list_assets`, `createOrder`/`create_order`, `getOrderStatus`/`get_order_status`, and so on. They cover the core trading calls; for an endpoint an SDK doesn't wrap yet, call it over HTTP. Each SDK's README covers the DeFi flow and the Turnkey signing helpers. Both SDKs send usage telemetry unless you set `TM_TELEMETRY=0`. > **Same venue, different credentials** > > The SDKs wrap the [Trading API](https://docs.truemarkets.co/trading-api). The [CLI](https://docs.truemarkets.co/cli/overview) trades the same account from a terminal. The credentials differ: the SDKs use your API key file, while the CLI logs you in with an emailed code and keeps your keys on your device. Pick the CLI when you want to trade from a terminal without writing code. ## Set up an SDK 1. **[Install](https://docs.truemarkets.co/sdks/installation)**: npm, bun, or pip. 2. **[Authenticate](https://docs.truemarkets.co/sdks/authentication)**: point `TM_KEY_FILE` at your key file. 3. **[List assets](https://docs.truemarkets.co/sdks/quickstart)**: the shortest useful call. ## Related - **[API reference](https://docs.truemarkets.co/api/gateway/true-markets-gateway-api)**: every endpoint, schema, and error code. - **[Trading API quickstart](https://docs.truemarkets.co/trading-api/quickstart)**: the same flow in raw HTTP, if you would rather see the wire format. - **[Discord](https://discord.gg/SC92xRUZqw)**: SDK questions and integration chat. * * * **[Next: Installation](https://docs.truemarkets.co/sdks/installation)** --- # Install the SDK Source: https://docs.truemarkets.co/sdks/installation Add the SDK to your project. The TypeScript SDK requires Node 18+ or Bun. The Python SDK requires Python 3.10+. - npm - bun - pip Install the TypeScript SDK with npm ```bash npm install @truemarkets/sdk ``` Install the TypeScript SDK with bun ```bash bun add @truemarkets/sdk ``` Install the Python SDK with pip ```bash pip install truemarkets ``` * * * **[Next: Authentication](https://docs.truemarkets.co/sdks/authentication)** --- # Authenticate the SDK Source: https://docs.truemarkets.co/sdks/authentication Point `TM_KEY_FILE` at your key file and the SDK mints and refreshes tokens for you. Both SDKs read it from your environment. The Python SDK also loads a `.env` file; with TypeScript, run Node or Bun with `--env-file=.env`. If you don't have a key file yet, see [Create your API key](https://docs.truemarkets.co/trading-api/api-key). .env: the two variables the SDKs read ```sh # .env TM_ENV=prod TM_KEY_FILE=/path/to/your-api-key.json ``` | Variable | Purpose | | --- | --- | | `TM_KEY_FILE` | Path to the downloaded API key file (JSON, contains `key_id` and the private JWK) | | `TM_ENV` | Which environment to target, e.g. `prod` | > **Treat the key file like a password** > > Anyone with the key file can mint tokens as you. Keep it out of git and out of shared environments. If it leaks, revoke the key and create a new one at [app.truemarkets.co](https://app.truemarkets.co/). * * * **[Next: Quickstart](https://docs.truemarkets.co/sdks/quickstart)** --- # Quickstart Source: https://docs.truemarkets.co/sdks/quickstart List the asset catalog and find one asset. You need the SDK [installed](https://docs.truemarkets.co/sdks/installation) and `TM_KEY_FILE` set as in [Authentication](https://docs.truemarkets.co/sdks/authentication); the client mints tokens for you from it. - TypeScript - Python Find SOL on Solana and keep its id ```typescript import { Client } from "@truemarkets/sdk"; const client = new Client(); const { data } = await client.gateway.listAssets(); const sol = data.data.find((a) => a.symbol === "SOL" && a.chain === "solana"); console.log(sol.id, sol.type, sol.asset_class); ``` Find SOL on Solana and keep its id ```python from truemarkets import Client c = Client() assets = c.gateway.list_assets() sol = next(a for a in assets.data if a.symbol == "SOL" and a.chain == "solana") print(sol.id, sol.type, sol.asset_class) ``` Keep the `id`. It's the `asset_id` you place orders with. ## Place an order To place an order, create it, sign the payloads, execute it, then read its status. The [Trading API quickstart](https://docs.truemarkets.co/trading-api/quickstart) walks the whole sequence, and the SDK method names map straight onto it (`createOrder`, `executeOrder`, `getOrderStatus`). ## Next steps - **[CLI](https://docs.truemarkets.co/cli/overview)**: trade the same venue from your terminal, no code required. - **[API reference](https://docs.truemarkets.co/api/gateway/true-markets-gateway-api)**: every endpoint, schema, and error code. - **[Discord](https://discord.gg/SC92xRUZqw)**: SDK questions, MCP setup help, and integration chat. --- # Overview Source: https://docs.truemarkets.co/cli/overview `tm` lets you buy, sell, transfer and check balances from your terminal. It's built for developers, strategy builders and AI agents. Your wallet's private key stays in Turnkey's secure enclave, and the key the CLI signs with never leaves your device. - **The fee is in the quote.** A dry run shows it before you confirm. - **The CLI trades the assets in the catalog** and resolves the chain from the token symbol. See [What you can trade](https://docs.truemarkets.co/trading-api/what-you-can-trade). - **Every command prints JSON** with `-o json`, so it composes with `jq` and scripts. - **Dry run first.** Simulate any trade or transfer before it spends anything. > **Same venue, different credentials** > > The CLI trades the same DeFi assets as the [Trading API](https://docs.truemarkets.co/trading-api). The credentials differ: the CLI logs you in with an emailed code and keeps your keys on your device, while the Trading API uses an API key file. The [institutional CeFi venue](https://docs.truemarkets.co/institutional/overview) has its own credentials and account model, so a login there does not carry over. ## Set up the CLI 1. **[Install](https://docs.truemarkets.co/cli/installation)**: one-liner, Homebrew, or a pre-built binary. 2. **[Authenticate](https://docs.truemarkets.co/cli/authentication)**: `tm signup` or `tm login`, verified by emailed code. 3. **[Run a command](https://docs.truemarkets.co/cli/commands)**: `tm assets`, `tm balances`, `tm buy`. 4. **[Wire it to an agent](https://docs.truemarkets.co/cli/ai-agents)**: skills for local agents, MCP for hosted clients. ## Make your first trade `--dry-run` prices the trade and shows the fee without executing. Dry run: the quote and fee, nothing spent ```console $ tm buy SOL 100 --dry-run Chain: Solana Asset: SOL Amount: $100.00 Price: $142.38 Qty: 0.7024 SOL Fee: $0.20 ``` When the quote looks right, run it again with `--force` to skip the confirmation prompt. Buy for real, without the prompt ```console $ tm buy SOL 100 --force ✓ Order filled: 0.7024 SOL @ $142.38 ``` Add `-o json` to get output you can pipe. Balances as JSON for jq and scripts ```console $ tm balances -o json [{"asset":"SOL","balance":"0.7024","chain":"solana"}, ...] ``` ## Next steps - **[Install the CLI](https://docs.truemarkets.co/cli/installation)**: one-liner, Homebrew, or a pre-built binary. - **[MCP](https://docs.truemarkets.co/mcp)**: per-client setup for the hosted MCP server. - **[SDKs](https://docs.truemarkets.co/sdks/overview)**: typed TypeScript and Python clients for the Trading API. --- # Install the CLI Source: https://docs.truemarkets.co/cli/installation Put the `tm` binary on your path with the install script, Homebrew, or a download. The CLI ships as a single static binary, so you need no Go toolchain. ## Install with the script (macOS and Linux) Install with the script ```bash curl -sSfL https://raw.githubusercontent.com/true-markets/cli/main/install.sh | sh ``` The script detects your OS and architecture, including when an AI agent runs it. ## Install with Homebrew (macOS) Install with Homebrew ```bash brew install true-markets/tap/tm ``` ## Download a pre-built binary We publish binaries for macOS (arm64/amd64), Linux (arm64/amd64), and Windows (amd64) on [GitHub Releases](https://github.com/true-markets/cli/releases). ## Verify the install Confirm the binary runs ```bash tm --help ``` Then list the assets you can trade. List the assets you can trade ```bash tm assets ``` * * * **[Next: Authentication](https://docs.truemarkets.co/cli/authentication)** --- # Log in to the CLI Source: https://docs.truemarkets.co/cli/authentication Sign up or log in with a code we email you. You don't need an API key to get started. Your wallet's private key stays in Turnkey's secure enclave, and the key the CLI signs with never leaves your device. ## Create an account Sign up with your email ```bash tm signup you@example.com ``` The CLI creates the account and verifies it with an emailed code. The email argument is optional; omit it and the CLI prompts. ## Log in Log in to an existing account ```bash tm login you@example.com ``` Once you enter the emailed code, the CLI stores your tokens locally. ## Check who you are Show the account a script will trade on ```bash tm whoami ``` Shows your account and wallet info. Run it to confirm which account a script is about to trade on. ## Log out Clear stored tokens ```bash tm logout ``` ## Run without an interactive login | Variable | Purpose | | --- | --- | | `TM_API_KEY` | API key, for non-interactive use (CI, scripted agents) | | `TM_AUTH_TOKEN` | Auth token, used in place of the stored login | Set one of these in CI rather than baking a login into a pipeline. ## Configuration Show and set configuration; secrets stay masked ```bash tm config show # current configuration; secrets are masked tm config set api_key # set a value ``` `tm config show` masks the stored `api_key` rather than printing it. * * * **[Next: Commands](https://docs.truemarkets.co/cli/commands)** --- # Commands Source: https://docs.truemarkets.co/cli/commands Every `tm` command, its flags and what it prints. Run `tm --help` for the authoritative flags on any command. ## Global flags | Flag | Default | Description | | --- | --- | --- | | `-o`, `--output` | `table` | Output format: `json` or `table` | Pass `-o json` to any command to script against its output. ## Trading ### `tm buy ` · `tm sell ` The CLI resolves the chain from the token symbol unless you pass `--chain`. | Flag | Default | Description | | --- | --- | --- | | `--chain` | `solana` | Blockchain network: `solana` or `base` | | `--qty-unit` | `quote` | Whether `` is in `base` (token) or `quote` (USD) units | | `--dry-run` | `false` | Print the quote without executing | | `--force` | `false` | Execute without the confirmation prompt | Dry run first, then buy without the prompt ```console $ tm buy SOL 100 --dry-run Chain: Solana Asset: SOL Amount: $100.00 Price: $142.38 Qty: 0.7024 SOL Fee: $0.20 $ tm buy SOL 100 --force ✓ Order filled: 0.7024 SOL @ $142.38 ``` `--qty-unit base` reads `` as a token quantity instead of dollars, so `tm buy SOL 0.5 --qty-unit base` buys half a SOL. > **`--force` skips confirmation** > > Without `--force` the CLI prompts before spending. Scripts and agents that pass `--force` execute immediately, so pair it with `--dry-run` on a first pass. ### `tm transfer ` Transfer tokens to an external address. | Flag | Default | Description | | --- | --- | --- | | `--chain` | `solana` | Blockchain network: `solana` or `base` | | `--qty-unit` | `base` | `base` (token) or `quote` (USD) units. This defaults to `base`, unlike buy and sell | | `--dry-run` | `false` | Print transfer details without executing | | `--force` | `false` | Execute without the confirmation prompt | ## Account ### `tm balances` Show your token balances. Balances as JSON ```console $ tm balances -o json [{"asset":"SOL","balance":"0.7024","chain":"solana"}, ...] ``` ### `tm whoami` Show your account and wallet info. ## Discovery ### `tm assets` List the assets in the catalog. See [What you can trade](https://docs.truemarkets.co/trading-api/what-you-can-trade). | Flag | Default | Description | | --- | --- | --- | | `--chain` | _(all)_ | Filter by chain: `solana` or `base` | ## Auth and config | Command | Description | | --- | --- | | `tm signup [email]` | Create an account with email verification | | `tm login [email]` | Log in with an email verification code | | `tm logout` | Log out and clear stored tokens | | `tm config show` | Show current configuration (secrets masked) | | `tm config set ` | Set a configuration value | [Authentication](https://docs.truemarkets.co/cli/authentication) covers the login flow and environment variables. * * * **[Next: AI agents](https://docs.truemarkets.co/cli/ai-agents)** --- # AI agents Source: https://docs.truemarkets.co/cli/ai-agents An agent can trade by running the `tm` binary locally through skills, or by connecting to the hosted MCP server. The choice depends on where the agent runs and whether you want to install anything. | | Skills | MCP | | --- | --- | --- | | Runs | The `tm` binary, locally | Hosted server, nothing installed | | Install | `npx skills install …` | Add a server URL to your client | | Clients | Claude Code, Codex, Cursor, any skills-protocol agent | Claude Desktop, Claude Code, Cursor, any MCP client | ## Skills: the agent runs the CLI Install the True Markets skills and the agent discovers the commands without being told the syntax. The CLI must be [installed](https://docs.truemarkets.co/cli/installation) and [authenticated](https://docs.truemarkets.co/cli/authentication) first. Install the skills your agent will use ```bash npx skills install truemarkets # general trading npx skills install truemarkets-limit-orders # limit orders ``` ## MCP: the hosted server The hosted MCP server gives any compatible client the same trading tools: check prices, get quotes and place trades. Your signing key stays on your device. You log in from the chat with a code we email you, so there is nothing to install and no API key to place. **Claude Code:** Add the trading server to Claude Code ```bash claude mcp add truemarkets --transport http https://mcp.truemarkets.co/mcp ``` **Claude Desktop** reads `claude_desktop_config.json`. **Cursor** reads `.cursor/mcp.json`. The one entry both config files need ```json { "mcpServers": { "truemarkets": { "url": "https://mcp.truemarkets.co/mcp" } } } ``` Per-client walkthroughs are under [MCP](https://docs.truemarkets.co/mcp). ## Example session An agent looks up trending assets, then buys one ```console > What tokens are trending by volume? ▶ Calling get_trending_assets(limit: 3) 1. SOL $4.2B volume (+38% vs 7d avg) 2. JUP $312M volume (+125% vs 7d avg) 3. BONK $89M volume (+64% vs 7d avg) > Buy $50 of JUP ▶ Calling get_quote(chain: solana, side: buy, base_asset: JUP, qty: 50) Price: $0.6241 • Qty: 80.12 JUP • Fee: $0.10 ▶ Calling trade_prepare → trade_execute ✓ Order filled: 80.12 JUP @ $0.6241 ``` > **Agents can spend real funds** > > Both routes execute real trades. An agent passing `--force` (skills) or calling `trade_execute` (MCP) moves money without a prompt. Scope what an agent may do before handing it credentials. ## Next steps - **[MCP setup](https://docs.truemarkets.co/mcp)**: per-client walkthroughs for Claude, ChatGPT and Cursor. - **[Trading server](https://docs.truemarkets.co/mcp/servers/trading)**: every tool the hosted server exposes. --- # MCP Overview Source: https://docs.truemarkets.co/mcp Give your AI assistant live crypto market data and the ability to trade, through the [Model Context Protocol](https://modelcontextprotocol.io/). Market data needs no API key and no login. Add a True Markets server URL to your AI client. The assistant discovers the tools on its own, and when you ask a question in plain language it calls the right ones for prices, price history, trending assets and AI-generated analysis. ## Connect your client [ ### Claude Claude.ai, Desktop & Code ](https://docs.truemarkets.co/mcp/setup/claude) [ ### ChatGPT OpenAI ChatGPT ](https://docs.truemarkets.co/mcp/setup/chatgpt) [ ### Cursor Cursor IDE ](https://docs.truemarkets.co/mcp/setup/cursor) ## Choose a server The server you need depends on whether you trade. The Trading server includes every Market Data tool, so if you want both, connect only the Trading server. ### Market Data Server Live Read-only tools for asset discovery, price history, market capitalization, summaries, and trending analysis. No account needed. See the [Market Data Server](https://docs.truemarkets.co/mcp/servers/marketdata) reference. Market data server URL ```text https://mcp.truemarkets.co/marketdata/mcp ``` ### Trading Server Live Every market data tool, plus tools for trading and portfolio management. You log in with a code emailed to your True Markets account, and your keys never leave your device. See the [Trading Server](https://docs.truemarkets.co/mcp/servers/trading) reference. Trading server URL ```text https://mcp.truemarkets.co/mcp ``` ## Next steps - **[Examples](https://docs.truemarkets.co/mcp/examples)**: prompts to try once a server is connected. - **[FAQ](https://docs.truemarkets.co/mcp/faq)**: keys, clients, chains and rate limits. --- # Claude Setup Source: https://docs.truemarkets.co/mcp/setup/claude Add a True Markets MCP server to Claude.ai, Claude Desktop or Claude Code. The server you need depends on whether you trade. The Market Data server is read-only and needs no account. The Trading server includes every Market Data tool plus login, quotes and trade execution, so if you want both, add only the Trading server. ## Market Data only Prices, trending assets and AI-generated analysis, read-only. No account needed. ### Claude.ai and Claude Desktop 1. Open **Settings > Integrations**. 2. Click **Add MCP Server**. 3. Enter the URL: `https://mcp.truemarkets.co/marketdata/mcp` ### Claude Code Add the server to `.mcp.json`. .mcp.json: the market data server entry ```json { "mcpServers": { "truemarkets-marketdata": { "type": "http", "url": "https://mcp.truemarkets.co/marketdata/mcp" } } } ``` Or add it from the terminal. Add the market data server from the terminal ```bash claude mcp add truemarkets-marketdata --transport http https://mcp.truemarkets.co/marketdata/mcp ``` ## Trading Everything in Market Data, plus login, quotes and trade execution. You need a True Markets account; create one at [app.truemarkets.co](https://app.truemarkets.co/). ### Claude.ai and Claude Desktop 1. Open **Settings > Integrations**. 2. Click **Add MCP Server**. 3. Enter the URL: `https://mcp.truemarkets.co/mcp` ### Claude Code Add the server to `.mcp.json`. .mcp.json: the trading server entry ```json { "mcpServers": { "truemarkets-trading": { "type": "http", "url": "https://mcp.truemarkets.co/mcp" } } } ``` Or add it from the terminal. Add the trading server from the terminal ```bash claude mcp add truemarkets-trading --transport http https://mcp.truemarkets.co/mcp ``` ## Verify it works Start a new conversation and ask: > "What crypto tokens are available for trading on Solana?" Claude calls the `list_assets` tool and returns all tradeable Solana tokens. ## Next steps - **[Examples](https://docs.truemarkets.co/mcp/examples)**: more prompts to try. - **[Trading Server](https://docs.truemarkets.co/mcp/servers/trading)**: how login works and every tool. --- # ChatGPT Setup Source: https://docs.truemarkets.co/mcp/setup/chatgpt Add a True Markets MCP server to ChatGPT. The server you need depends on whether you trade. The Market Data server is read-only and needs no account. The Trading server includes every Market Data tool plus login, quotes and trade execution, so if you want both, add only the Trading server. ## Requirements - ChatGPT Plus, Team, or Enterprise plan - MCP support enabled in your account ## Market Data only Prices, trending assets and AI-generated analysis, read-only. No account needed. 1. Open **Settings > Tools & Integrations**. 2. Click **Add MCP Server**. 3. Enter the URL: `https://mcp.truemarkets.co/marketdata/mcp` ## Trading Everything in Market Data, plus login, quotes and trade execution. You need a True Markets account; create one at [app.truemarkets.co](https://app.truemarkets.co/). 1. Open **Settings > Tools & Integrations**. 2. Click **Add MCP Server**. 3. Enter the URL: `https://mcp.truemarkets.co/mcp` ## Verify it works Ask: > "Give me a summary of the crypto market right now." ChatGPT calls the `get_market_summary` tool and returns an AI-generated market digest with trending assets and sentiment analysis. ## Next steps - **[Examples](https://docs.truemarkets.co/mcp/examples)**: more prompts to try. - **[Trading Server](https://docs.truemarkets.co/mcp/servers/trading)**: how login works and every tool. --- # Cursor Setup Source: https://docs.truemarkets.co/mcp/setup/cursor Add a True Markets MCP server to Cursor from its settings or from `.cursor/mcp.json`. The server you need depends on whether you trade. The Market Data server is read-only and needs no account. The Trading server includes every Market Data tool plus login, quotes and trade execution, so if you want both, add only the Trading server. ## Market Data only Prices, trending assets and AI-generated analysis, read-only. No account needed. ### Add it in Cursor Settings 1. Open **Cursor Settings** (Cmd+Shift+J on Mac, Ctrl+Shift+J on Windows/Linux). 2. Select **MCP** in the sidebar. 3. Click **Add new MCP server**. 4. Select **HTTP** and enter: `https://mcp.truemarkets.co/marketdata/mcp` ### Add it to the config file Add this to `.cursor/mcp.json`. .cursor/mcp.json: the market data server entry ```json { "mcpServers": { "truemarkets-marketdata": { "type": "http", "url": "https://mcp.truemarkets.co/marketdata/mcp" } } } ``` ## Trading Everything in Market Data, plus login, quotes and trade execution. You need a True Markets account; create one at [app.truemarkets.co](https://app.truemarkets.co/). ### Add it in Cursor Settings 1. Open **Cursor Settings** (Cmd+Shift+J on Mac, Ctrl+Shift+J on Windows/Linux). 2. Select **MCP** in the sidebar. 3. Click **Add new MCP server**. 4. Select **HTTP** and enter: `https://mcp.truemarkets.co/mcp` ### Add it to the config file Add this to `.cursor/mcp.json`. .cursor/mcp.json: the trading server entry ```json { "mcpServers": { "truemarkets-trading": { "type": "http", "url": "https://mcp.truemarkets.co/mcp" } } } ``` ## Verify it works Open the Cursor chat and ask: > "What tokens are trending by volume? Show me the top 5." Cursor calls the `get_trending_assets` tool and returns assets ranked by trading volume. ## Next steps - **[Examples](https://docs.truemarkets.co/mcp/examples)**: more prompts to try. - **[Trading Server](https://docs.truemarkets.co/mcp/servers/trading)**: how login works and every tool. --- # Market Data Server Source: https://docs.truemarkets.co/mcp/servers/marketdata Give your assistant real-time crypto market data through read-only tools. It needs no login and no key. Server URLLive `https://mcp.truemarkets.co/marketdata/mcp` ## Tools `list_assets` List all tradeable tokens with symbol, name, chain, address, and decimals. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | chain | string | No | Filter by chain ("solana" or "base") | `get_asset` Get details for a specific token by chain and contract address. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | chain | string | Yes | Chain name | | address | string | Yes | Contract address | `get_price_history` Get historical price data for one or more tokens. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbols | string | Yes | Comma-separated token symbols | | window | string | No | Time window (1h, 4h, 1d, 7d, 30d, 1M) | | resolution | string | No | Data resolution (5s, 1m, 5m, 15m, 1h, 4h, 1d) | `get_market_summary` Get an AI-generated market digest with trending assets and sentiment analysis. `get_trending_assets` Get assets trending by trading volume. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | limit | string | No | Max results to return (default: 10, max: 50) | | sort | string | No | Sort order: desc (default) or asc | `get_surging_assets` Get assets with the highest recent price change percentage. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | limit | string | No | Max results to return (default: 10, max: 50) | | sort | string | No | Sort order: desc (default) or asc | `get_market_cap` Get the market capitalization for a specific asset by symbol. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbol | string | Yes | Asset symbol (e.g., BTC, ETH, SOL) | `list_market_caps` List all assets ranked by market capitalization in descending order. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | limit | string | No | Max results to return (default: 10, max: 50) | `get_asset_summary` Get a daily AI-generated summary for a specific asset including news analysis and sentiment. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbol | string | Yes | Asset symbol (e.g., BTC, ETH, SOL) | `get_asset_summary_history` Get historical daily summaries for a specific asset. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbol | string | Yes | Asset symbol (e.g., BTC, ETH, SOL) | | limit | string | No | Number of historical summaries (default: 7, max: 30) | ## Next steps - **[Trading Server](https://docs.truemarkets.co/mcp/servers/trading)**: the same tools plus login, quotes and trade execution. - **[Examples](https://docs.truemarkets.co/mcp/examples)**: prompts that exercise each tool above. --- # Trading Server Source: https://docs.truemarkets.co/mcp/servers/trading Get quotes, place trades and manage your portfolio, with your keys kept on your device. You sign every transaction locally. Server URLLive `https://mcp.truemarkets.co/mcp` ## Authentication You log in with a code we email to your True Markets account: call `login_request_code` with your email, then `login_verify_code` with the code. Market data tools work without login; trading tools require it. ## Your keys stay on your device Every transaction follows a prepare, sign, execute pattern. We never hold or see your signing key. Only signed transaction payloads reach us. 1. **Prepare**: the server builds the unsigned transaction payloads. 2. **Sign**: you sign the payloads locally with your signing key on your own device. 3. **Execute**: the signed payloads go back for on-chain execution. ## Tools The tools, grouped by what they do. ### Authentication `login_request_code` Request an email verification code to log in. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | email | string | Yes | Email address for your True Markets account | `login_verify_code` Verify the email code and complete login. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | code | string | Yes | Verification code from email | ### Account & Balances `get_profile` Show account details including email and wallet addresses. Requires login. `get_balances` Show token balances across wallets. Requires login. ### Quotes & Pricing `get_quote` Get a price quote for a token swap. Shows the amount you'll receive and any fees. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | chain | string | No | Blockchain network for an on-chain swap. Omit it for an exchange (CeFi) order. | | side | string | Yes | Order side (buy, sell) | | base\_asset | string | Yes | Token symbol or contract address to trade | | qty | string | Yes | Amount to trade | | qty\_unit | string | No | Quantity unit: 'base' (token amount) or 'quote' (USDC amount). Defaults to 'quote' for buys and 'base' for sells. | ### Trading `trade_prepare` Create a market order. Returns an order\_id and, when signing is needed, unsigned payloads for client signing. Requires login. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | chain | string | No | Blockchain network for an on-chain swap. Omit it for an exchange (CeFi) order. | | side | string | Yes | Order side (buy, sell) | | base\_asset | string | Yes | Token symbol or contract address to trade | | qty | string | Yes | Amount to trade | | qty\_unit | string | No | Quantity unit: 'base' (token amount) or 'quote' (USDC amount). Defaults to 'quote' for buys and 'base' for sells. | `trade_execute` Execute a prepared trade with client-provided signatures. Call trade\_prepare first. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | order\_id | string | Yes | Order ID from trade\_prepare | | signatures | string | Yes | JSON array of Turnkey stamps, one per payload from trade\_prepare | ### Market Data `list_assets` List all tradeable tokens with symbol, name, chain, address, and decimals. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | chain | string | No | Filter by chain ("solana" or "base") | `get_asset` Get details for a specific token by chain and contract address. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | chain | string | Yes | Chain name | | address | string | Yes | Contract address | `get_price_history` Get historical price data for one or more tokens. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbols | string | Yes | Comma-separated token symbols | | window | string | No | Time window (1h, 4h, 1d, 7d, 30d, 1M) | | resolution | string | No | Data resolution (5s, 1m, 5m, 15m, 1h, 4h, 1d) | `get_market_summary` Get an AI-generated market digest with trending assets and sentiment analysis. `get_trending_assets` Get assets trending by trading volume. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | limit | string | No | Max results to return (default: 10, max: 50) | | sort | string | No | Sort order: desc (default) or asc | `get_surging_assets` Get assets with the highest recent price change percentage. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | limit | string | No | Max results to return (default: 10, max: 50) | | sort | string | No | Sort order: desc (default) or asc | `get_market_cap` Get the market capitalization for a specific asset by symbol. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbol | string | Yes | Asset symbol (e.g., BTC, ETH, SOL) | `list_market_caps` List all assets ranked by market capitalization in descending order. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | limit | string | No | Max results to return (default: 10, max: 50) | `get_asset_summary` Get a daily AI-generated summary for a specific asset including news analysis and sentiment. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbol | string | Yes | Asset symbol (e.g., BTC, ETH, SOL) | `get_asset_summary_history` Get historical daily summaries for a specific asset. Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | symbol | string | Yes | Asset symbol (e.g., BTC, ETH, SOL) | | limit | string | No | Number of historical summaries (default: 7, max: 30) | ## Next steps - **[Examples](https://docs.truemarkets.co/mcp/examples)**: a full buy walked through tool by tool. - **[Setup for Claude](https://docs.truemarkets.co/mcp/setup/claude)**: connect this server to your client. --- # Examples Source: https://docs.truemarkets.co/mcp/examples Each prompt below shows the tool your assistant calls and what comes back. Start with the first three to confirm your setup works. ## Check your setup "What crypto tokens are available for trading on Solana?" Calls:`list_assets` - Returns all tradeable Solana tokens - Includes symbol, name, contract address, and decimals "What's the current price of SOL?" Calls:`get_price_history` - Returns recent price data for SOL - Includes timestamp and price for each data point - Uses the default window and resolution "Give me a summary of the crypto market right now." Calls:`get_market_summary` - AI-generated market digest with key bullets - Includes trending assets, sentiment analysis, and sources - Updated throughout the day ## Find trending and surging assets "What tokens are trending by volume? Show me the top 3." Calls:`get_trending_assets` - Assets ranked by trending ratio (recent vs. historical volume) - Includes market cap and volume data "What tokens are surging right now? Show me the top 5." Calls:`get_surging_assets` - Assets ranked by recent price change percentage - Shows both gainers and the magnitude of moves "Show me the 7-day price history for SOL and ETH with hourly resolution." Calls:`get_price_history` - Timestamped price points for both assets - Hourly granularity over a 7-day window ## Look up market capitalization "What's the market cap of Bitcoin?" Calls:`get_market_cap` - Returns the current market capitalization for BTC - Includes price, circulating supply, and total market cap - No authentication required "Show me the top 10 assets by market cap." Calls:`list_market_caps` - Assets ranked by market capitalization in descending order - Includes symbol, price, and market cap for each asset ## Read AI-generated analysis "Give me today's analysis of SOL." Calls:`get_asset_summary` - AI-generated summary with sourced sentences - Includes sentiment analysis and relevant news context - Updated daily "Show me the last 7 days of daily summaries for ETH." Calls:`get_asset_summary_history` - Historical daily AI-generated summaries - Track sentiment trends over time ## Place a trade Trading needs the [Trading server](https://docs.truemarkets.co/mcp/servers/trading) and a funded True Markets account. Create and fund one at [app.truemarkets.co](https://app.truemarkets.co/). "Buy SOL using 100 USDC" What happens 1 `get_quote` Fetches the current swap price and fees for 100 USDC of SOL 2 `trade_prepare` Builds the unsigned transaction payloads for the swap 3 Local signing You sign the transaction on your device with your signing key. True Markets never sees it 4 `trade_execute` Submits the signed payload for on-chain execution Every trade follows this prepare, sign, execute pattern. Only the signed payloads come back to us, and we never hold or see your signing key. > **Not investment advice** > > The MCP servers provide market data and trade execution tools. They do not provide investment advice. Always do your own research before trading. Cryptocurrency markets are volatile and you may lose some or all of your investment. ## Next steps - **[Trading server](https://docs.truemarkets.co/mcp/servers/trading)**: every tool behind the trade flow above. - **[FAQ](https://docs.truemarkets.co/mcp/faq)**: keys, clients, chains and rate limits.