# Fine Art Finder AI — API Reference

All endpoints accept and return JSON.

- **Base URL:** `https://ai.fineartfinder.co.uk/api/v1`
- **Auth:** `Authorization: Bearer aig_xxx` on every request.
- **Rate limits:** per-account, defaults 100/min and 10,000/day (admin-configurable).
- **IP restrictions:** optional per-key allowlist. Suspended accounts and disallowed IPs return `403`.

Every successful call deducts tokens from the account that owns the API key.

---

## POST /connection

Returns the current state of your account. Does **not** consume tokens.

**Response**

```json
{
  "account_id": "uuid",
  "account_name": "...",
  "tokens": 50,
  "total_tokens_used": 0,
  "active": true
}
```

---

## POST /generate

Generates a structured artwork description JSON. **Cost: 1 token.**

**Request**

```json
{
  "input_text": "Title: ... Artist: ... Medium: ...",
  "base64_of_image": "iVBORw0KG..."
}
```

**Response** (shape depends on the account's `prompt_profile`; FAF profile shown)

```json
{
  "title": "...",
  "intro": "...",
  "description": "...",
  "artist_bio": "...",
  "image_desc": "...",
  "meta_desc": "...",
  "meta_title": "...",
  "keywords": ["..."],
  "colours": ["..."],
  "image_prompt": "..."
}
```

Model: `google/gemini-3-flash-preview`.

---

## POST /generate-artist-bio

Writes a structured artist bio JSON. **Cost: 1 token.**

**Request**

```json
{ "artist": "Jane Doe", "current_bio": "" }
```

**Response**

```json
{
  "intro": "...",
  "short_desc": "...",
  "artist_bio": "...",
  "keywords": ["..."],
  "meta_desc": "...",
  "meta_title": "...",
  "key_facts": [{ "title": "...", "fact": "..." }]
}
```

---

## POST /generate-image

Creates a photorealistic in-room mockup PNG of the artwork in a described or uploaded scene.

**Cost:**
- `quality: "standard"` (default) — **5 tokens**
- `quality: "pro"` — **15 tokens** (slower, best scale & frame fidelity, uses Gemini 3 Pro)

### Request — describe a scene

```json
{
  "scene_source": "describe",
  "scene": "A bright modern living room with oak floors...",
  "dimensions": "80cm x 60cm",
  "position": "above a grey sofa",
  "artwork_type": "framed",
  "base64_of_image": "...",
  "extra_reference_images_base64": ["...", "..."],
  "artwork_context": "Oil on canvas, deep red palette",
  "quality": "standard"
}
```

### Request — upload a real room photo

```json
{
  "scene_source": "upload",
  "scene_image_base64": "...",
  "scene_scale_reference": "the sofa is ~200cm wide",
  "dimensions": "80cm x 60cm",
  "position": "centred on the back wall",
  "base64_of_image": "...",
  "quality": "pro"
}
```

### Request — iterate on a previous render

```json
{
  "previous_image_base64": "...",
  "tweak_instruction": "warmer evening lighting, slightly larger frame",
  "base64_of_image": "..."
}
```

### Response

```json
{
  "image_base64": "iVBORw0KG...",
  "mime_type": "image/png",
  "tokens_used": 5,
  "quality": "standard",
  "model": "google/gemini-3.1-flash-image",
  "scene_adjusted": {
    "scene": "rewritten room brief (if pre-pass adjusted it)",
    "position": "refined position",
    "notes": "what was adjusted"
  }
}
```

### Field reference

| Field | Type | Notes |
|---|---|---|
| `scene_source` | `"describe"` \| `"upload"` | Required unless using `previous_image_base64`. |
| `scene` | string | Required when `scene_source = "describe"`. |
| `scene_image_base64` | string | Required when `scene_source = "upload"`. |
| `scene_scale_reference` | string | Optional. Helps the model judge scale in uploaded rooms. |
| `dimensions` | string | Real-world artwork size, **including frame** (e.g. `"80cm x 60cm"`). |
| `position` | string | Where to hang/place the piece. |
| `artwork_type` | `"framed"` \| `"sculpture"` | Defaults to `"framed"`. Sculptures unlock orbit/tilt camera moves in video. |
| `base64_of_image` | string | The artwork image. Frame must be included for framed works; do not crop. |
| `extra_reference_images_base64` | string[] | Optional additional angles/details. |
| `artwork_context` | string | Optional artist notes (medium, palette). |
| `quality` | `"standard"` \| `"pro"` | See cost table. |
| `previous_image_base64` + `tweak_instruction` | string | Iterate on a prior render instead of starting fresh. |

---

## POST /generate-scene-video

Generates a short, locked in-room video. The first frame is composed exactly like `/generate-image`, then animated with a chosen camera move via Google Veo. Returns a `job_id` to poll.

**Cost:** `round(base × duration / 4) + 5` tokens, where `base = 25` for `fast` and `60` for `standard` / `preview`. The `+5` is the locked first-frame composition.

### Request

```json
{
  "scene_source": "describe",
  "scene": "A minimal gallery wall...",
  "dimensions": "80cm x 60cm",
  "position": "centred at eye level",
  "base64_of_image": "...",
  "artwork_type": "framed",
  "camera_move": "slow_push_in",
  "mood_preset": "match_first_frame",
  "duration": 4,
  "aspect_ratio": "16:9",
  "resolution": "720p",
  "quality": "fast"
}
```

### Camera moves

- **Framed:** `locked_off`, `breathing`, `slow_push_in`, `slow_pull_back`, `pan_left_to_right`, `pan_right_to_left`.
- **Sculpture only:** `quarter_orbit_left`, `quarter_orbit_right`, `tilt_up`.

### Response (202)

```json
{
  "job_id": "uuid",
  "status": "pending",
  "first_frame_base64": "...",
  "first_frame_mime_type": "image/png"
}
```

---

## GET /video-status/:job_id

Polls a video job. Tokens are deducted **once**, only when the job completes successfully.

**While running**

```json
{ "status": "pending", "job_id": "uuid", "tokens_used": 35 }
```

**On completion**

```json
{
  "status": "complete",
  "job_id": "uuid",
  "video_base64": "...",
  "mime_type": "video/mp4",
  "tokens_used": 35
}
```

**On failure**

```json
{ "status": "failed", "job_id": "uuid", "error": "..." }
```

---

## Errors

| Status | Meaning |
|---|---|
| `400` | Invalid request body |
| `401` | Missing or invalid API key |
| `402` | Out of tokens (top up to continue) |
| `403` | Account suspended or IP not allowed |
| `429` | Rate limit exceeded (per-minute or per-day) |
| `500` / `502` | Upstream AI failure (visible on your Logs page) |

---

## Logs & usage

Every call is logged with endpoint, status, latency, token cost and the full request/response payload. The Logs page supports search, filter and a detail panel. The Dashboard shows layered usage charts (calls, tokens, errors) for the last 14 days.
