> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.westiq.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.westiq.ai/_mcp/server.

# Hero Images

Two endpoints resolve a banner/hero image for a subject — one for a shopping category, one for a
destination city — through the **same shared image-search provider and cache**. Behavior, error
handling, and the response shape are identical; only the request vocabulary differs.

Both are [airline-scoped](/guides/sessions#endpoints-that-need-no-session), **model-free**: no
`X-Session-Id` (harmless if sent — simply ignored).

## `POST /v1/shopping/hero-image`

Body (max 2 KiB):

| Field         | Required | Rules                                                                                                                                                                                                                             |
| ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subject`     | yes      | Non-blank, and must case-insensitively match one of the shopping category labels: `Fashion`, `Fragrance`, `Beauty`, `Accessories`, `Electronics`, `Travel Essentials`, `Food & Beverage`, `Local & Destination`, `Kids & Family`. |
| `orientation` | no       | Free string; no validation applied.                                                                                                                                                                                               |

```bash
curl -X POST "https://api.staging.westiq.ai/v1/shopping/hero-image" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "subject": "Fashion" }'
```

## `POST /v1/destination/hero-image`

Body (max 16 KiB):

| Field     | Required | Rules                                      |
| --------- | -------- | ------------------------------------------ |
| `city`    | yes      | Non-blank, 64 characters max.              |
| `country` | no       | If supplied: non-blank, 64 characters max. |

Both fields accept letters, digits, spaces, commas, periods, apostrophes, and hyphens only —
anything else is rejected, never silently stripped.

```bash
curl -X POST "https://api.staging.westiq.ai/v1/destination/hero-image" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "city": "Rome", "country": "Italy" }'
```

## Response — shared shape

```json
{
  "schemaVersion": "1",
  "correlationId": "req_0197c0a1b2c3d4e5f60718293a4b5c6d",
  "image": {
    "url": "https://images.example/paris-hero.jpg",
    "width": 1920,
    "height": 1080,
    "alt": "The Eiffel Tower at dusk",
    "photographer": "Jane Doe",
    "photographerUrl": "https://images.example/photographers/jane-doe"
  }
}
```

| Field                                            | Notes                                                  |
| ------------------------------------------------ | ------------------------------------------------------ |
| `image`                                          | **Omitted** (never `null`) when no image was resolved. |
| `image.url`, `.width`, `.height`                 | Always present when `image` is present.                |
| `image.alt`, `.photographer`, `.photographerUrl` | Omitted individually when unknown.                     |

> **Warning**
>
> **A missing `image` is honest degradation, not an error — always a `200`.** It happens for any
> of three reasons: the image provider is disabled for your environment, the provider found no
> match for the subject, or the provider call itself failed. All three return the same shape with
> `image` omitted — never a fabricated URL, and never a `4xx`/`5xx` for a legitimate "no image"
> outcome. Design your seatback UI to render cleanly with no hero image.

A positive result is cached for a short TTL keyed on the resolved query, so requesting the same
subject repeatedly does not repeatedly call the provider. A miss or a provider fault is never
negatively cached, so a transient hiccup self-heals on the next request.

## Errors

Standard [envelope](/guides/errors). Both: `401` / `403`, `500`. Never `429` — these endpoints
are model-free, so the [per-client request-rate limit on AI-backed endpoints](/guides/errors#rate-limits) does not apply to them.

Both endpoints also return `413 request.malformed` over their body cap, and `400
request.malformed` for a missing/malformed/empty JSON body — including when a required field
(`subject`, or destination's `city`) is absent from the JSON entirely, since that's a binding
failure, not a validation failure.

* **`POST /v1/shopping/hero-image`**: `422 request.validation` when `subject` is present but
  blank or outside the known category list.
* **`POST /v1/destination/hero-image`**: `422 request.validation` for a blank-but-present `city`,
  an over-length `city`/`country`, or an out-of-charset character in either. A blank `country` is
  **not** an error — it's silently treated as "no country given" and the lookup falls back to
  city-only.

> **Note**
>
> Whether a live call actually returns a populated `image` depends on your environment's image
> provider being enabled — don't assume every call returns an image while integrating; confirm
> with your partner manager if `image` is consistently absent and you expect otherwise.