> ## 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.

# SDK & TypeScript types

> Use the official @skinshark/sdk for the fastest path, or generate types from the OpenAPI spec for typed fetch.

There are two supported ways to call the API from TypeScript. Use the
official SDK if you want a polished client; generate types if you'd rather
keep your own HTTP code.

## Option 1 — official SDK (recommended)

```bash theme={null}
npm install @skinshark/sdk
```

```ts theme={null}
import { Skinshark, SkinsharkError, isError, meta } from "@skinshark/sdk";

const sdk = new Skinshark({ apiKey: process.env.SKINSHARK_API_KEY! });

// Merchant context
const profile = await sdk.account.get();
const stats = await sdk.account.stats({ from: "2026-01-01T00:00:00Z" });

// Sub-user context — bind once, then call as that user
const user = await sdk.as("user_42");           // validates ownership at bind time
const trade = await user.market.buy(
  [{ listingId, maxPrice: "5.00" }],
  "order-7421",                                  // your correlation id
);

// Or one-off via opts
await sdk.market.buy(items, "order-1", { onBehalfOf: "user_42" });
```

The package is **ESM-only**, **Node 22+**, server-to-server. It uses `got`
under the hood, ships zero runtime deps beyond that, and exposes:

* `sdk.account`, `sdk.users`, `sdk.trades` — merchant-only
* `sdk.profile`, `sdk.tradeUrls`, `sdk.wallet`, `sdk.deposits`, `sdk.market` — actor-context
* `sdk.as(ref)` — async, validates the ref and returns a sub-user-bound view
* `sdk.health()`, `sdk.newIdempotencyKey()`, `sdk.request<T>(...)` — utilities
* `verifyWebhook(rawBody, headers, { secret })` — signature verification (zero deps beyond `node:crypto`)
* `meta(response)` — read `requestId`, `status`, `headers`, `rateLimit` from any successful response

```ts theme={null}
import { verifyWebhook, isError } from "@skinshark/sdk";

try {
  const event = verifyWebhook(rawBody, req.headers, {
    secret: process.env.SKINSHARK_WH_SECRET!,
  });
  switch (event.type) {
    case "trade.completed": /* ... */ break;
    case "deposit.completed": /* ... */ break;
  }
} catch (e) {
  if (isError(e, "INVALID_SIGNATURE")) return res.status(401).end();
  throw e;
}
```

### Type-only imports

For UI/server code that just needs the wire types without the runtime
client, import from the `/types` subpath:

```ts theme={null}
import type {
  Trade,
  MarketListing,
  BuyBody,
  ErrorKey,
} from "@skinshark/sdk/types";
```

### Errors

A single `SkinsharkError` class is thrown for every failure. Branch on the
discriminated `key`:

```ts theme={null}
import { SkinsharkError, isError, isAuthError, isRateLimited } from "@skinshark/sdk";

try {
  await sdk.market.buy(items, "order-1", { onBehalfOf: "u-1" });
} catch (e) {
  if (!(e instanceof SkinsharkError)) throw e;

  if (isError(e, "INSUFFICIENT_BALANCE")) /* refill */;
  if (isError(e, "PRICE_MISMATCH"))       /* refresh listing */;
  if (isError(e, "TRADEURL_REQUIRED"))    /* prompt user */;
  if (isAuthError(e))                     /* rotate key */;
  if (isRateLimited(e))                   await sleep(e.retryAfterMs ?? 1000);

  console.error({ key: e.key, requestId: e.requestId });   // for support
}
```

See [Error handling](/docs/guides/error-handling) for retry strategy and the
full key catalogue at [reference/errors](/docs/reference/errors).

## Option 2 — generate types from the OpenAPI spec

If you'd rather not pull in the SDK runtime — for example, you're calling
from a non-Node runtime or already have an HTTP client you're happy with —
generate a `paths` type from the spec and pair it with a tiny `fetch`
wrapper.

```bash theme={null}
npm install -D openapi-typescript
npx openapi-typescript \
  https://api.skinshark.gg/openapi.yaml \
  -o src/skinshark-types.ts
```

```ts theme={null}
import type { paths } from "./skinshark-types";

type CreateSubUserBody =
  paths["/merchant/users"]["post"]["requestBody"]["content"]["application/json"];

type CreateSubUserResponse =
  paths["/merchant/users"]["post"]["responses"]["201"]["content"]["application/json"]["data"];

type ListTradesQuery = NonNullable<
  paths["/merchant/trades"]["get"]["parameters"]["query"]
>;
```

### A typed fetch helper

```ts theme={null}
import type { paths } from "./skinshark-types";

type Method = "get" | "post" | "patch" | "delete";

type SuccessData<P extends keyof paths, M extends Method> =
  paths[P][M] extends { responses: { 200: { content: { "application/json": { data: infer D } } } } }
    ? D
    : paths[P][M] extends { responses: { 201: { content: { "application/json": { data: infer D } } } } }
      ? D
      : never;

export async function call<P extends keyof paths, M extends Method>(
  method: M,
  path: P,
  init?: { body?: unknown; onBehalfOf?: string; query?: Record<string, string>; idempotencyKey?: string },
): Promise<SuccessData<P, M>> {
  const qs = init?.query
    ? "?" + new URLSearchParams(init.query).toString()
    : "";

  const headers: Record<string, string> = {
    "api-key": process.env.SKINSHARK_API_KEY!,
    "content-type": "application/json",
  };
  if (init?.onBehalfOf) headers["on-behalf-of"] = init.onBehalfOf;
  if (init?.idempotencyKey) headers["idempotency-key"] = init.idempotencyKey;

  const res = await fetch(`https://api.skinshark.gg${path}${qs}`, {
    method: method.toUpperCase(),
    headers,
    body: init?.body ? JSON.stringify(init.body) : undefined,
  });

  const env = await res.json();
  if (!env.success) {
    const e = env.error;
    throw new SkinsharkError(env.requestId, e.code, e.key, e.message);
  }
  return env.data;
}
```

```ts theme={null}
const profile = await call("get", "/merchant");
//    ^? { id: string; email: string; merchantFeeBps: number; ... }

const sub = await call("post", "/merchant/users", {
  body: { email: "alice@example.com", externalId: "user_42" },
});
//    ^? { id: string; externalId: string | null; idempotent: boolean; ... }

const trades = await call("get", "/merchant/trades", {
  query: { status: "completed", limit: "50" },
});
//    ^? { items: Trade[]; nextCursor: string | null }
```

Zero runtime dependencies, types drift automatically as the spec evolves,
and the wrapper is \~30 lines. Pin the spec to a git tag (or a vendored
copy) for stable types between releases.

## Webhook signature verification

The SDK ships a `verifyWebhook(rawBody, headers, { secret })` helper that
follows [Standard Webhooks](https://www.standardwebhooks.com/) — see the
[Webhooks guide](/docs/guides/webhooks#signature-verification) for the full
shape and example handlers.

If you're not using the SDK, the verification is \~30 lines of
`node:crypto` — copy from the same guide, or use any Standard Webhooks
library from npm.
