---
title: "Getting started with Trading API"
url: https://docs.truemarkets.co/trading-api/quickstart
description: "Mint a token, buy a small amount of SOL, and find the fill in your transactions, in about 10 minutes."
---

Docs index: https://docs.truemarkets.co/llms.txt

# Getting started with Trading API

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.