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

# Core API

> One merchant account, all activity in one place. Simplest integration.

Core API is the single-account integration model. Your merchant key is the
only identity that exists — every trade, deposit, and withdrawal posts to
that one account. No sub-users, no `On-Behalf-Of`, no inter-account
transfers.

This is the right starting point if you operate one storefront, one
treasury, one consolidated ledger.

## Mental model

```mermaid theme={null}
flowchart TB
    backend["Your backend<br/>(one API key)"]
    merchant["SkinShark<br/>(one merchant)"]
    backend -->|api-key| merchant
```

## When this fits

* One product, one storefront, one treasury.
* You don't need per-customer balance isolation in SkinShark.
* All reconciliation can happen at one merchant level.
* Operations is a single team, not multi-tenant.

If any of these *don't* apply, jump to
[Full Platform](/docs/integration/full-platform).

## Endpoints you'll use

All sub-user-context routes work without an `On-Behalf-Of` header — they
run as the merchant. So you only ever need:

| Action                                | Endpoint                                                    |
| ------------------------------------- | ----------------------------------------------------------- |
| Read merchant profile + balances      | `GET /merchant`                                             |
| Read full ledger                      | `GET /merchant/ledger`                                      |
| Search the catalog                    | `GET /market/search`                                        |
| Bulk per-item price feed              | `GET /market/prices`                                        |
| Live listings for an item             | `GET /market/items/{itemId}/listings`                       |
| Buy specific listings                 | `POST /market/buy`                                          |
| Buy by item, server picks fills       | `POST /market/buy/quick`                                    |
| List trades                           | `GET /market/transactions` (or `GET /merchant/trades`)      |
| Get trade detail                      | `GET /market/transactions/{tradeId}`                        |
| Cancel one undelivered item           | `POST /market/transactions/{tradeId}/items/{itemId}/cancel` |
| Cancel a whole trade                  | `POST /market/transactions/{tradeId}/cancel`                |
| Stats (GMV, fees)                     | `GET /merchant/stats`                                       |
| Deposit (Gate Pay / on-ramp / crypto) | `POST /user/wallet/deposit/...`                             |

## Skip these (for now)

These endpoints are real, just not relevant in Core API mode:

* `/merchant/users/*` — sub-user CRUD and funding
* `/merchant/users/{id}/wallet` — sub-user wallet reads
* `On-Behalf-Of` header — never needed

You can adopt them later without breaking anything; they're additive.

## Daily operation

A typical day in Core API:

<Steps>
  <Step title="User browses">
    Your frontend calls `GET /market/search` and `GET /market/items/{itemId}/listings`
    to render inventory.
  </Step>

  <Step title="User buys">
    `POST /market/buy` debits your merchant wallet by `totalPrice` and
    creates a trade.
  </Step>

  <Step title="Server confirms">
    A `trade.completed` webhook fires (or your WebSocket subscriber sees
    `trade.completed`). Persist the trade against your own order table by
    `externalId`.
  </Step>

  <Step title="Reconcile">
    Nightly job calls `GET /merchant/ledger?type=spot` and `GET /merchant/stats`
    against yesterday's window for accounting.
  </Step>

  <Step title="Top up">
    When the merchant balance dips, deposit via Gate Pay / on-ramp /
    crypto. The deposit credits the merchant wallet directly.
  </Step>
</Steps>

## Cancelling an order

```ts theme={null}
// One item
await api(`/market/transactions/${tradeRef}/items/${itemRef}/cancel`, { method: "POST" });
// Everything still cancellable on the trade
await api(`/market/transactions/${tradeRef}/cancel`, { method: "POST" });
```

`tradeRef` is the trade id or the `externalId` you sent. `itemRef` is `TradeItem.id` or the
per-item `externalId` you sent — set one on every item you might want to cancel later:

```ts theme={null}
items: [{ listingId, maxPrice, externalId: "order-42-item-1" }]
// quick buy: one ref per unit, in order
{ itemId, maxPrice, amount: 3, delivery: "standard",
  externalIds: ["order-42-a", "order-42-b", "order-42-c"] }
```

For quick buys this is the only way to tell your units apart. Fills arrive over time and in no
fixed order, and a quick-buy item has no listing of its own to name.

An item is cancellable only while `initiated` or `pending`, once the order reached the supplier,
and no earlier than **30 minutes** after creation — before that you get `TRADE_CANCEL_TOO_SOON`
with `cancellableAt`. Anything already delivered or in flight to Steam is `TRADE_NOT_CANCELLABLE`.

Both are best-effort. The trade-level call returns `200` even when some items are refused, with
a per-item `status` and `reason`, so check the body rather than the status code. A successful
cancel refunds in full with no penalty, and the money arrives asynchronously via
`trade.refunded` — the response only confirms the supplier accepted.

## Code template

```ts theme={null}
import { randomUUID } from "node:crypto";

async function checkoutOnSkinShark(input: {
  checkoutId: string;       // your order ID
  listingId: string;
  maxPrice: string;         // decimal string
}) {
  // Buy as the merchant (no On-Behalf-Of)
  return api<{ id: string; status: string; totalPrice: number }>(
    "/market/buy",
    {
      method: "POST",
      body: JSON.stringify({
        items: [
          {
            listingId: input.listingId,
            maxPrice: input.maxPrice,
            externalId: `${input.checkoutId}-1`,
          },
        ],
        externalId: input.checkoutId,
      }),
    },
  );
}

async function reconcileLastDay() {
  const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
  const stats = await api<{
    totals: { gmv: number; feesEarned: number; tradeCount: number };
  }>(`/merchant/stats?from=${encodeURIComponent(since)}`);
  return stats.totals;
}
```

## What you give up

* **No tenant isolation.** All trades show up under one merchant. If a
  customer disputes, they're not separable in SkinShark's view.
* **One ledger.** Per-customer accounting is your job, not SkinShark's.
* **Migration cost later.** If your business needs per-customer wallets,
  you'll move to Full Platform — usually a one-time refactor where you
  swap one `api()` call signature to take a sub-user ID.

## When to graduate

Move to [Full Platform](/docs/integration/full-platform) when you need:

* Per-customer balance isolation (chargeback containment, audit clarity).
* Multi-tenant reporting in the merchant dashboard.
* Per-customer fee overrides.
* Self-service crypto deposits per customer.

The migration is mostly mechanical: provision a sub-user per customer,
add `On-Behalf-Of`, route customer-attributable calls there. The catalog
and trade primitives don't change.
