> ## Documentation Index
> Fetch the complete documentation index at: https://skinshark.gg/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Make your first authenticated call, create a sub-user, fund it, and place a buy order — all in TypeScript.

This walkthrough takes about 5 minutes. By the end you'll have a typed client
hitting the API, a sub-user with funds, and a confirmed trade.

## Prerequisites

* A SkinShark merchant API key (`sk_live_...`). Generate one in the dashboard.
* Node 20+ (or any TypeScript runtime that supports `fetch` natively).
* Optional: `openapi-typescript` for fully typed responses.

## 1. Set up the client

<CodeGroup>
  ```ts client.ts theme={null}
  const BASE_URL = "https://api.skinshark.gg";
  const API_KEY = process.env.SKINSHARK_API_KEY!;

  export async function api<T>(
    path: string,
    init: RequestInit = {},
  ): Promise<T> {
    const res = await fetch(`${BASE_URL}${path}`, {
      ...init,
      headers: {
        "api-key": API_KEY,
        "content-type": "application/json",
        ...init.headers,
      },
    });

    const body = (await res.json()) as
      | { success: true; requestId: string; data: T }
      | { success: false; requestId: string; error: { code: number; key: string; message: string } };

    if (!body.success) {
      throw new Error(`[${body.requestId}] ${body.error.key}: ${body.error.message}`);
    }
    return body.data;
  }
  ```

  ```bash curl theme={null}
  export SKINSHARK_API_KEY="sk_live_..."
  ```
</CodeGroup>

<Tip>
  For full type safety, generate types from the OpenAPI spec with
  [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript) and
  parameterise `api<T>` against the `paths` object.
</Tip>

## 2. Confirm credentials

`GET /merchant` returns your merchant profile, current fees, and wallet
snapshot. If this works, your key + IP allowlist + role are all good.

<CodeGroup>
  ```ts TypeScript theme={null}
  const profile = await api<{
    id: string;
    email: string;
    merchantFeeBps: number;
    wallets: { spot: { currency: string; balance: number } | null };
  }>("/merchant");

  console.log(profile.email, "-", profile.wallets.spot?.balance, "USD");
  ```

  ```bash curl theme={null}
  curl https://api.skinshark.gg/merchant \
    -H "api-key: $SKINSHARK_API_KEY"
  ```
</CodeGroup>

## 3. Create a sub-user

A sub-user is an isolated account under your merchant: own wallet, own Steam
trade URLs, own trade history. Pass an `externalId` you control so you can
reference this sub-user with your own ID later.

<Note>
  `POST /merchant/users` is **API-key auth only** — it rejects dashboard
  sessions. This is intentional: provisioning happens server-to-server.
</Note>

<CodeGroup>
  ```ts TypeScript theme={null}
  const sub = await api<{
    id: string;
    externalId: string | null;
    currency: "USD" | "EUR";
  }>("/merchant/users", {
    method: "POST",
    body: JSON.stringify({
      email: "alice@example.com",
      externalId: "user_42",
    }),
  });

  console.log("Sub-user", sub.id, "external", sub.externalId);
  ```

  ```bash curl theme={null}
  curl https://api.skinshark.gg/merchant/users \
    -H "api-key: $SKINSHARK_API_KEY" \
    -H "content-type: application/json" \
    -d '{"email": "alice@example.com", "externalId": "user_42"}'
  ```
</CodeGroup>

## 4. Fund the sub-user

Move balance from your merchant spot wallet to the sub-user. Supply a unique
`Idempotency-Key` so retrying after a network blip never double-funds.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { randomUUID } from "node:crypto";

  const fund = await api<{ transactionId: string; idempotent: boolean }>(
    `/merchant/users/${encodeURIComponent("user_42")}/fund`,
    {
      method: "POST",
      headers: { "idempotency-key": randomUUID() },
      body: JSON.stringify({ amount: "25.00" }),
    },
  );

  console.log("Funded", fund.transactionId, "(replay:", fund.idempotent, ")");
  ```

  ```bash curl theme={null}
  curl https://api.skinshark.gg/merchant/users/user_42/fund \
    -H "api-key: $SKINSHARK_API_KEY" \
    -H "content-type: application/json" \
    -H "idempotency-key: $(uuidgen)" \
    -d '{"amount": "25.00"}'
  ```
</CodeGroup>

## 5. Find an item to buy

Acting **on behalf of** the sub-user, search the catalog and grab a live
listing. The `On-Behalf-Of` header tells the API to scope this call to that
sub-user — the call runs against their wallet, their Steam trade URL.

<CodeGroup>
  ```ts TypeScript theme={null}
  const search = await api<{
    items: Array<{ id: string; marketHashName: string; price: number }>;
  }>("/market/search?q=ak-47&limit=5", {
    headers: { "on-behalf-of": "user_42" },
  });

  const itemId = search.items[0]!.id;

  const listings = await api<{
    items: Array<{ id: string; price: number; market: string }>;
  }>(`/market/items/${itemId}/listings?limit=10`, {
    headers: { "on-behalf-of": "user_42" },
  });

  const listing = listings.items[0]!;
  console.log("Picked listing", listing.id, "$" + listing.price);
  ```

  ```bash curl theme={null}
  curl "https://api.skinshark.gg/market/search?q=ak-47&limit=5" \
    -H "api-key: $SKINSHARK_API_KEY" \
    -H "on-behalf-of: user_42"
  ```
</CodeGroup>

## 6. Buy the listing

Place the order with a `maxPrice` ceiling — if the listing's price drifted
above this between fetch and buy, that item fails instead of overcharging
your sub-user.

<CodeGroup>
  ```ts TypeScript theme={null}
  const trade = await api<{
    id: string;
    status: string;
    itemCount: number;
    totalPrice: number;
  }>("/market/buy", {
    method: "POST",
    headers: { "on-behalf-of": "user_42" },
    body: JSON.stringify({
      items: [
        {
          listingId: listing.id,
          maxPrice: listing.price.toFixed(2),
        },
      ],
      externalId: "checkout_2026_001",
    }),
  });

  console.log("Trade", trade.id, "status", trade.status);
  ```

  ```bash curl theme={null}
  curl https://api.skinshark.gg/market/buy \
    -H "api-key: $SKINSHARK_API_KEY" \
    -H "on-behalf-of: user_42" \
    -H "content-type: application/json" \
    -d '{
      "items": [{ "listingId": "<listing-id>", "maxPrice": "1.50" }],
      "externalId": "checkout_2026_001"
    }'
  ```
</CodeGroup>

## 7. Watch it execute

The happy path is `initiated → pending → active → hold → completed`; a trade can
also terminate as `failed`, `canceled`, `declined`, or `reverted` (see [trade
outcomes](/docs/guides/error-handling#async-trade-outcomes)). Two ways to follow it:

<Tabs>
  <Tab title="Poll merchant trades">
    ```ts theme={null}
    const detail = await api<{ status: string; settledAt?: string }>(
      `/merchant/trades/${trade.id}`,
    );
    ```
  </Tab>

  <Tab title="WebSocket (recommended)">
    ```ts theme={null}
    const { token } = await api<{ token: string }>("/auth/ws-token", {
      method: "POST",
      headers: { "on-behalf-of": "user_42" },
    });

    const ws = new WebSocket(`wss://api.skinshark.gg/ws?token=${token}`);
    ws.addEventListener("message", (e) => {
      const event = JSON.parse(e.data.toString());
      if (event.event.startsWith("trade.")) console.log(event);
    });
    ```
  </Tab>

  <Tab title="Webhook">
    Configure a callback URL in the dashboard, then handle `trade.*` events
    with signature verification — see [Webhooks](/docs/guides/webhooks).
  </Tab>
</Tabs>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" href="/docs/guides/authentication">
    Key types, IP allowlists, and the rules for what API-key auth can and
    can't do.
  </Card>

  <Card title="Acting on behalf of" href="/docs/guides/on-behalf-of">
    The full sub-user model, how `On-Behalf-Of` resolves, and what happens
    when you forget it.
  </Card>

  <Card title="Idempotency" href="/docs/guides/idempotency">
    Safe retries for funding, buying, and any operation that moves money.
  </Card>

  <Card title="Full Platform integration" href="/docs/integration/full-platform">
    The recommended pattern for SaaS reselling SkinShark to your own users.
  </Card>
</CardGroup>
