# DeckSense API — app integration

## POST `/api/deck/resolve`

Resolves a **Limitless-style** tournament decklist to catalog cards. Used when opening a deck from **GET `/api/tournaments/id/{id}`** standings.

### Request

- **Method:** `POST`
- **Content-Type:** `application/json`
- **Query:** `detail=1` (optional) — merge full `cards` row fields + `copy_stats` per resolved line (same shape as `GET /api/cards/detail/{card_id}`).

**Body:**

```json
{
  "decklist": {
    "pokemon": [{ "set": "TWM", "number": "130", "name": "Dragapult ex", "count": 4 }],
    "trainer": [],
    "energy": []
  }
}
```

Each line may include optional `card_id` (if present, resolved first against `cards.card_id`).

### Response (stable contract)

| Field | Type | Description |
|-------|------|-------------|
| `cards` | `ResolvedCard[]` | One entry per **distinct** `card_id`, with aggregated `count`. |
| `unresolved` | `UnresolvedLine[]` | Lines that did not match the catalog. |
| `resolved_count` | `number` | Sum of `count` on `cards` (copies in deck). |
| `total_count` | `number` | Sum of `count` across all input lines (resolved + unresolved). |

#### `ResolvedCard` (default, no `detail`)

```ts
interface ResolvedCard {
  card_id: string;
  name: string;
  image_url: string | null;
  supertype: string; // "Pokémon" | "Trainer" | "Energy" | …
  count: number;
}
```

#### `ResolvedCard` with `?detail=1`

Extends the full card record from the database (e.g. `subtypes`, `types`, `set_id`, `number`, `hp`, `attacks`, `abilities`, `is_legal_now`, `tags`, …) plus:

- `copy_stats`: `{ pct_1, pct_2, pct_3, pct_4, total_decks, decks_with_card }` when `card_copy_stats` has data; `decks_with_card` is `0` if DB predates migration `008_card_copy_stats_decks_with_card.sql`.

#### `UnresolvedLine`

Always an object (never an opaque string):

```ts
type UnresolvedReason = "missing_fields" | "not_in_catalog";

interface UnresolvedLine {
  set: string | null;
  number: string | null;
  name: string;
  count: number;
  reason: UnresolvedReason;
}
```

- `missing_fields` — `set` or `number` empty after trim.
- `not_in_catalog` — no row matched after set-code normalization, number variants, energy name variants, and name+set fallbacks.

### Server behavior (resolver)

- **Set codes** are lowercased and mapped (e.g. `TWM` → `sv6`, `MEE` → `me2`). Product-style codes `ME03` / `me03` normalize to `me3` before map lookup.
- **Card numbers** — tries raw value, trimmed integers, and 3-digit zero padding when numeric.
- **Energy** — tries `Basic Darkness Energy` and `Darkness Energy` (and similar) interchangeably for the `energy` bucket.

### TypeScript alignment (`types/deckApi.ts`)

Align client types with:

- `ResolvedDeckResponse`: `{ cards, unresolved, resolved_count, total_count }`
- `ResolvedCard` as above; with `detail=1` use intersection with your full `CardDetail` type if you have one.
- `UnresolvedLine` including `reason`.

### Related endpoints

- `GET /api/cards/detail/{card_id}` — same `copy_stats` / `decks_with_card` rules as `resolve?detail=1`.
- Run migration `sql/migrations/008_card_copy_stats_decks_with_card.sql` to populate `decks_with_card` in production; APIs work without it (column omitted from SQL, value `0`).
