# Using the CardSight AI API - CardMagic (Listing-Ready Card Images) API

## Overview
CardMagic turns a phone photo of one or more trading cards into clean, listing-ready card images, with no scanner or custom hardware. It finds each card in the photo and returns it as its own image. In the default `process` mode each card is straightened and squared up at standard trading-card proportions, as if it had been scanned; `crop` mode returns each card as it appears in the photo, trimmed to the card. Padding, padding fill, auto-levels, output format, and the returned image size are adjustable, and optional corner close-ups help judge condition. The response is binary, not JSON: one card returns the image itself, while two or more cards (or any request with corner close-ups) return a ZIP archive.

## Endpoint Details
- **Method**: POST
- **URL**: `https://api.cardsight.ai/v1/cardmagic/process`
- **Authentication**: API Key required (X-Api-Key header)
- **Request body**: `multipart/form-data` with the photo in an `image` field, or the photo as a direct binary body (`image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`)

## Constraints
- **Maximum upload**: 20MB and 8192px per side
- **Supported formats**: JPEG, PNG, WebP, HEIC, HEIF
- **Send the original photo**, including its orientation flag. Don't downscale or rotate it first.
- Raw cards give the best results. Cards in toploaders or grading-company slabs may crop poorly.

## Parameters (query string, all optional)

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| mode | string | `process` | `process`: each card straightened and squared up at standard trading-card proportions, as if scanned. `crop`: each card as it appears in the photo, trimmed to the card. |
| paddingPercent | number | `5` | Margin around the card as a percent of the card size on each side, 0 to 50. |
| paddingFill | string | `background` | What fills the margin: `background` (the real surroundings) or a solid `#RRGGBB` color. URL-encode the `#` as `%23` in a raw query string. |
| autoLevels | string | `true` | `"true"` or `"false"`. Restores contrast and removes color cast. Set `"false"` when the photo's own color is what matters. |
| outputFormat | string | `jpeg` | Encoding of the returned card image(s): `jpeg` or `png`. |
| longEdge | number | Photo's native size | Size of every returned image as the length of its long side in pixels, padding included, 32 to 2100. |
| corners | string | `false` | `"true"` or `"false"`. Adds close-ups of each card's four corners for judging condition, plus a sheet combining them. The response is then always a ZIP, even for one card. |

**Corner close-ups:** each close-up is 600x600 and shows 14 mm of the card from the corner plus 2.5 mm beyond it, with a light 1 mm grid (heavier every 5 mm; the grid assumes a standard 2.5 x 3.5 in card), in the photo's original color. The combined sheet is 1210x1210 and shows all four corners as they sit on the card.

## Using the CardSight AI SDK (Recommended)

### Node.js / TypeScript
```typescript
import { CardSightAI, CardSightAIError, getCardMagicInfo } from 'cardsightai'
import { readFileSync, writeFileSync } from 'fs'

const client = new CardSightAI({ apiKey: 'your-api-key' })

// Send the original photo from the phone, not a downscaled or rotated copy
const photo = new Uint8Array(readFileSync('path/to/IMG_1234.HEIC')).buffer

try {
  const result = await client.cardMagic.process(photo, {
    mode: 'process',       // default: straightened and squared up, as if scanned
    outputFormat: 'jpeg',  // or 'png'
    longEdge: 1500         // long side in pixels (32-2100); omit for the photo's native size
  })

  if (result.data) {
    // The body is binary: result.data is a Blob, and metadata comes from the headers
    const info = getCardMagicInfo(result.response)
    const bytes = Buffer.from(await result.data.arrayBuffer())

    if (info.isZip) {
      // Two or more cards: card_0.jpg, card_1.jpg, ... in reading order
      writeFileSync('cards.zip', bytes)
      console.log(`${info.count} cards processed`)
    } else {
      // One card: the image itself
      writeFileSync('card.jpg', bytes)
      console.log(`Card image: ${info.width}x${info.height}px`)
    }
  }
} catch (error) {
  // Error responses are thrown as CardSightAIError, not returned in result.error
  if (error instanceof CardSightAIError && error.response?.code === 'NO_CARD_FOUND') {
    console.log('No card found in the photo') // status 422
  } else {
    throw error
  }
}
```

In the browser, pass the `File` from a file input straight through:

```typescript
import { CardSightAI, getCardMagicInfo } from 'cardsightai'

const client = new CardSightAI({ apiKey: 'your-api-key' })

// <input type="file" accept="image/*" capture="environment">
const input = document.querySelector<HTMLInputElement>('input[type="file"]')
const photoFile = input?.files?.[0]

if (photoFile) {
  const { data, response } = await client.cardMagic.process(photoFile, {
    corners: 'true',        // yes/no options take the strings 'true' and 'false'
    paddingFill: '#FFFFFF'  // white border instead of the real background
  })

  if (data && getCardMagicInfo(response).isZip) {
    // Unzip with the library of your choice (the SDK has no zip dependency),
    // or offer the archive as a download
    const link = document.createElement('a')
    link.href = URL.createObjectURL(data)
    link.download = 'cardmagic.zip'
    link.click()
  }
}
```

## Direct API Call (cURL)

### One card, default options
```bash
curl -X POST "https://api.cardsight.ai/v1/cardmagic/process" \
  -H "X-Api-Key: your-api-key" \
  -F "image=@path/to/photo.jpg" \
  --output card.jpg
```

### Crop only, white border, PNG, with corner close-ups
```bash
curl -X POST "https://api.cardsight.ai/v1/cardmagic/process?mode=crop&paddingFill=%23FFFFFF&outputFormat=png&corners=true" \
  -H "X-Api-Key: your-api-key" \
  -F "image=@path/to/photo.jpg" \
  --output cards.zip
```

Add `-D headers.txt` to save the `X-CardMagic-*` response headers alongside the image.

## What Comes Back

| Photo contains | `corners` | Response body |
|----------------|-----------|---------------|
| One card | `false` | The image, `image/jpeg` or `image/png` per `outputFormat` |
| Two or more cards | `false` | `application/zip` containing `card_0.<ext>`, `card_1.<ext>`, ... in reading order (top to bottom, then left to right) |
| One or more cards | `true` | Always `application/zip`. Each `card_N.<ext>` is followed by `card_N_top-left.<ext>`, `card_N_top-right.<ext>`, `card_N_bottom-right.<ext>`, `card_N_bottom-left.<ext>`, and `card_N_corners.<ext>` (all four on one sheet) |
| No card | any | `422` error with code `NO_CARD_FOUND` |

### Response Headers (200)

| Header | Type | Description |
|--------|------|-------------|
| Content-Type | string | `image/jpeg`, `image/png`, or `application/zip` |
| X-CardMagic-Count | number | Number of cards in the photo |
| X-CardMagic-Width | number | Single image only. Image width in pixels, padding included |
| X-CardMagic-Height | number | Single image only. Image height in pixels, padding included |

`getCardMagicInfo(response)` in the Node SDK parses these into `{ contentType, isZip, count?, width?, height? }`.

### Error Responses
Errors return JSON with status `400`, `401`, `408`, `422`, `429`, `500`, or `503`. A photo with no detectable card returns `422` with code `NO_CARD_FOUND`.

## Common Use Cases
- Create marketplace listing photos from a phone, no scanner required
- Photograph a whole row or page of cards and get each card back as its own image
- Check all four corners for wear before grading or selling
- Standardize image size and format across a listing or inventory app with `longEdge` and `outputFormat`
- Keep a card's true color (for example, to judge toning) by turning off `autoLevels`

## Tips for AI Assistants
- The response is **binary, not JSON**. In the Node SDK `result.data` is a `Blob`; read metadata with `getCardMagicInfo(result.response)`
- Check `Content-Type` (or `info.isZip`) before handling the body. The same request returns a single image or a ZIP depending on how many cards are in the photo
- Error responses are **thrown** by the Node SDK as `CardSightAIError` (with `status` and `response`), so wrap calls in `try`/`catch`
- `autoLevels` and `corners` take the strings `"true"` and `"false"`, not booleans
- Upload the original photo, including its orientation flag. Don't downscale, recompress, or rotate it first
- ZIP entries are in reading order (top to bottom, then left to right)
- `longEdge` is the long side in pixels with the padding included, not the size of the card alone
- A browser app can only read the `X-CardMagic-*` headers if the API exposes them to cross-origin requests. If they come back empty, count the `card_N.<ext>` files in the ZIP or read the size from the decoded image
