# Effects and catalogue

List effects, read their variants and prices, use their sell-ready assets, and follow new releases with effect.published.

An **effect** is one finished, tuned generation recipe: a slug such as `golden-hour-pan`, the inputs it needs, the variants it can produce and a fixed price for each. The catalogue grows every week.

## List effects

```sh
curl "https://api.moan.hot/v1/effects?type=video&limit=20" \
  -H "Authorization: Bearer $MOANHOT_API_KEY"
```

| Query | Meaning |
|---|---|
| `type` | `video`, `image` or `storyboard` |
| `category` | One of the catalogue categories, as listed on each effect |
| `orientation` | `straight` or `gay` |
| `enabled` | `true` (default) returns only effects enabled for your account. `false` returns the whole catalogue |
| `published_after` | Only effects published after this RFC 3339 time. Use it to sync new releases |
| `limit`, `starting_after`, `ending_before` | Cursor pagination, 1 to 100 per page (default 20) |

The response is a list object:

```json
{ "object": "list", "data": [ { "id": "eff_...", "slug": "...", "...": "..." } ], "has_more": true }
```

To get the next page, pass the last item's `id` as `starting_after`.

`GET /effects/{slug}` returns a single effect. An unknown or retired slug answers `404 effect_not_found`.

## Anatomy of an effect

| Field | What it is |
|---|---|
| `slug` | The stable identifier you send to `POST /generations` |
| `type` | `video`, `image` or `storyboard` |
| `name`, `description` | Localised text: `en` always, plus `ru`, `de` and `es` where available |
| `category`, `orientation` | Catalogue facets for your own filters |
| `inputs[]` | Input slots: `name` (the key in `inputs`), `kind`, `required`, `min_short_side_px` |
| `variants[]` | Every priced combination this effect offers (below) |
| `blend` | `true` for two-person effects: two input photos, two people, one video |
| `locations[]` | Storyboards only: `key`, localised `name`, `poster_url`, `preview_mp4_url`. Otherwise `null` |
| `popularity_tier` | `top10`, `trending`, `new` or `null` |
| `success_rate` | Share of generations that succeeded over the last 30 days |
| `assets` | Sell-ready media (below) |
| `enabled` | Whether your account can create it |
| `published_at`, `retires_at` | Release date, and a retirement date when one is scheduled |

## Variants

A variant is one combination of axes with one fixed price. Video effects use `duration`, `resolution`, `audio` and `prep`; image effects use `resolution` only (`1mp` or `2mp`); storyboards use `location` and `resolution`.

| Axis | Values |
|---|---|
| `duration` | `5s`, `10s`, `15s` for video |
| `resolution` | `512p`, `640p`, `768p` for video; `1mp`, `2mp` for images; `432p`, `576p`, `720p` for storyboards |
| `audio` | `true` adds a generated soundtrack |
| `prep` | `true` runs frame preparation before animation, for inputs that need it |
| `location` | Storyboards: the key of one of the effect's `locations[]` |

Each variant also has a `variant_key` (a stable name for the combination, `beach.720p` for a storyboard), `price_micros` and `typical_seconds`, the median time from request to result over the last 30 days. Send the axes, not the key, in `POST /generations`. A combination that is not listed answers `422 variant_unavailable`.

## Storyboards

A storyboard turns one photo into a short film: several scenes shot in one location, with the person from the photo in every scene, cut together into a single MP4. You choose the location and the resolution; each pair is its own variant with its own price.

The locations are listed on the effect itself, because each storyboard has its own and new ones arrive with catalogue releases. Read them, with their posters and previews, from `GET /effects/{slug}`:

```sh
curl "https://api.moan.hot/v1/effects/STORYBOARD_SLUG" \
  -H "Authorization: Bearer $MOANHOT_API_KEY"
```

```json
{
  "slug": "STORYBOARD_SLUG",
  "type": "storyboard",
  "locations": [
    { "key": "beach", "name": { "en": "Beach" }, "poster_url": "https://...", "preview_mp4_url": "https://..." },
    { "key": "rooftop", "name": { "en": "Rooftop" }, "poster_url": "https://...", "preview_mp4_url": null }
  ],
  "variants": [
    { "variant_key": "beach.432p", "location": "beach", "resolution": "432p", "price_micros": 1500000, "typical_seconds": 900 },
    { "variant_key": "beach.720p", "location": "beach", "resolution": "720p", "price_micros": 2500000, "typical_seconds": 900 }
  ]
}
```

To list every storyboard and its locations at once, call `GET /effects?type=storyboard`. A storyboard takes far longer than a video effect, often well over half an hour, so prefer a webhook to polling. See [Generations](/docs/generations/#storyboards) for the request.

## Assets you can show your users

Every effect carries media ready for your own catalogue page:

- `poster_url` and `poster_sfw_url`: still posters. Use the SFW poster on public or indexable pages.
- `preview_mp4_url`, `preview_webp_url`: short looping previews. `preview_mp4_url` is always an MP4, or `null` when the preview exists only in another format; that preview is then in `renditions[]`.
- `example_inputs[]`: sample input photos, `{position, url}` (position `2` is the second photo of a blend), so you can show input next to output.
- `renditions[]`: the same preview at several sizes and formats (`mp4`, `webm`, `webp`, `jpg`), with `width` and `height`.

Asset URLs are public, content-addressed and safe to cache forever. Hotlink them or copy them to your own CDN.

## Prices

`GET /prices` returns every effect and variant price in the current price-list version, plus the `next` version and its `effective_at` when a change is scheduled. See [Pricing and billing](/docs/pricing/).

## Enabling effects

New effects can be enabled for your account automatically or after you review them, as you choose in the dashboard. A create for an effect you have not enabled answers `403 effect_disabled`.

## Following new releases

Subscribe a webhook endpoint to these events to keep your catalogue in sync without polling:

| Event | When |
|---|---|
| `effect.published` | A new effect is live and enabled for your account. `data` is the full effect |
| `effect.retired` | An effect is scheduled to stop. `data.retires_at` is at least 14 days ahead |

After `retires_at`, creates for that slug answer `404 effect_not_found`, and generations still queued for it end `failed` with `effect_retired` (never charged).

If you prefer polling, call `GET /effects?published_after=<last sync time>` once a day.
