# Templates

> Search the public template catalogue of ~50,000 image and video templates by keyword, topic, tags, and model.

The template catalogue holds roughly 50,000 approved, public templates — the same ones the web catalogue shows — projected down to a narrow shape that is safe for an API-key client. Search and filter them, then use a template's `prompt` (when present) to seed a generation.

> [!NOTE]
> Both endpoints require a valid API key, like the rest of `/v1`. Templates are public data, so — unlike the generation endpoints — these routes charge **no credits**. They are subject to the same per-key rate limiting as every other API-key route.

## `GET /v1/templates`

Search and filter the public template catalogue. Returns only live, approved, public rows.

**Auth:** API Key

| Query param | Type | Required | Description |
| --- | --- | --- | --- |
| `q` | `string` | No | Free-text search over title, description, and prompt. |
| `media_type` | `string` | No | Restrict to `image` or `video`. |
| `topic` | `string` | No | Topic/niche keyword bucket, e.g. `aesthetic`, `dating`, `fashion`. Matches by keyword; see the note below on the `topic` response field. |
| `tags` | `string` | No | Tag filter, any-match. Repeatable — pass `tags` multiple times to match any of several tags. |
| `target_model` | `string` | No | Filter to templates built for one target model. |
| `featured` | `boolean` | No | Only featured templates. |
| `trending` | `boolean` | No | Sort by recency-weighted popularity instead of the default order. Default false. |
| `limit` | `number` | No | Page size, 1 to 100. Default 30. |
| `offset` | `number` | No | Row offset, ≥ 0. Default 0. |

**Response** `200 OK`

```json
{
  "templates": [
    {
      "id": "10423",
      "title": "Golden hour portrait",
      "prompt": "a portrait bathed in warm golden-hour light, shallow depth of field",
      "media_type": "image",
      "topic": null,
      "tags": ["portrait", "aesthetic"],
      "target_model": "gemini-3.1-flash-image-preview",
      "preview_url": "https://cdn.picxstudio.com/templates/10423/preview.jpg",
      "thumbnail_url": "https://cdn.picxstudio.com/templates/10423/thumb.jpg",
      "is_featured": true,
      "likes": 1284
    }
  ],
  "total": 31,
  "limit": 30,
  "offset": 0
}
```

Each entry in `templates` is a `TemplateInfo`:

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string` | Template id. Pass it to `GET /v1/templates/{template_id}`. |
| `title` | `string` | Human-readable template name. |
| `prompt` | `string \| null` | The sample prompt for free templates. **`null` on premium/gated rows by design** — the exact premium prompt is the paid asset and is never returned here. `null` means gated, not missing. |
| `media_type` | `string` | `image` or `video`. |
| `topic` | `null` | **Always `null`.** Topic is a query-time keyword bucket, not a per-row column — there is no stored topic to return. Filter *by* topic with the `topic` query param; do not expect it echoed back on a row. |
| `tags` | `string[]` | The template's tags. |
| `target_model` | `string \| null` | The model this template was built for, when set. |
| `preview_url` | `string \| null` | Richest available preview — the video URL for video templates, otherwise the still image. `null` when neither exists. |
| `thumbnail_url` | `string \| null` | A still image (never a video file), suitable for an `<img>`. |
| `is_featured` | `boolean` | Whether the template is featured. |
| `likes` | `number \| null` | Like count, when available. |

> [!IMPORTANT]
> **`total` is an estimate, not a count.** To keep search fast over ~50,000 rows, an exact `COUNT` is deliberately never run. `total` is computed as `offset + (rows on this page) + 1` when more rows exist, so it only tells you "there is at least one more page". Do **not** use it to compute a page count or a progress bar. Instead, **page until a short page returns**: keep requesting with a growing `offset` until you get back fewer than `limit` templates — that page is the last one.

**curl**

```bash
# Search "golden hour", image only, first page
curl "https://api.picxstudio.com/v1/templates?q=golden%20hour&media_type=image&limit=30&offset=0" \
  -H "Authorization: Bearer $PICX_API_KEY"

# Topic bucket + repeatable tags (any-match) + trending sort
curl "https://api.picxstudio.com/v1/templates?topic=fashion&tags=portrait&tags=studio&trending=true" \
  -H "Authorization: Bearer $PICX_API_KEY"

# Featured video templates for one target model
curl "https://api.picxstudio.com/v1/templates?media_type=video&featured=true&target_model=fal-ai/bytedance/seedance/v2" \
  -H "Authorization: Bearer $PICX_API_KEY"
```

**Page until a short page returns**

```bash
# limit=30. Keep bumping offset by 30 until a response has fewer than 30 templates.
curl "https://api.picxstudio.com/v1/templates?q=aesthetic&limit=30&offset=0"  -H "Authorization: Bearer $PICX_API_KEY"
curl "https://api.picxstudio.com/v1/templates?q=aesthetic&limit=30&offset=30" -H "Authorization: Bearer $PICX_API_KEY"
curl "https://api.picxstudio.com/v1/templates?q=aesthetic&limit=30&offset=60" -H "Authorization: Bearer $PICX_API_KEY"
# ...when a page returns < 30 templates, you have reached the end.
```

## `GET /v1/templates/{template_id}`

Fetch a single public template by id.

**Auth:** API Key

Returns the same `TemplateInfo` shape as one entry of the list response.

**Response** `200 OK`

```json
{
  "id": "10423",
  "title": "Golden hour portrait",
  "prompt": "a portrait bathed in warm golden-hour light, shallow depth of field",
  "media_type": "image",
  "topic": null,
  "tags": ["portrait", "aesthetic"],
  "target_model": "gemini-3.1-flash-image-preview",
  "preview_url": "https://cdn.picxstudio.com/templates/10423/preview.jpg",
  "thumbnail_url": "https://cdn.picxstudio.com/templates/10423/thumb.jpg",
  "is_featured": true,
  "likes": 1284
}
```

Returns `404` when the template does not exist, or exists but is not live, approved, and public (archived, pending, or private). A non-public row is never leaked.

**curl**

```bash
curl https://api.picxstudio.com/v1/templates/10423 \
  -H "Authorization: Bearer $PICX_API_KEY"
```

## Seed a generation from a template

A free template's `prompt` is ready to drop straight into a generation call:

```bash
# 1. Find a template
curl "https://api.picxstudio.com/v1/templates?q=golden%20hour&media_type=image&limit=1" \
  -H "Authorization: Bearer $PICX_API_KEY"

# 2. Use its prompt to generate an image
curl -X POST https://api.picxstudio.com/v1/images/generate \
  -H "Authorization: Bearer $PICX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a portrait bathed in warm golden-hour light, shallow depth of field","size":"2K","aspect_ratio":"16:9"}'
```

> [!TIP]
> When a template's `prompt` is `null`, it is a premium/gated template and its exact prompt is withheld. Pick a free template, or write your own prompt guided by the template's `title` and `tags`.

## FAQ

### Why is `total` smaller than the real number of matches?

`total` is an **estimate**, not a count. To keep search fast across ~50,000 rows, the API never runs an exact `COUNT`. It returns `offset + (rows on this page) + 1` whenever more rows exist, which only signals "there is at least one more page". Page through by increasing `offset` until a page comes back with fewer than `limit` templates — that is the last page.

### Why is the `topic` field always `null` even when I filter by topic?

Because topic is a query-time keyword bucket, not a stored per-row column. The `topic` query param filters the catalogue, but no template carries a single canonical topic value, so the `topic` field on every row is `null`. This is expected — filter by topic, don't read topic back.

### Why is `prompt` `null` on some templates?

Those are premium/gated templates. Their exact prompt is the paid asset and is never returned on the public API surface. `null` means gated, not missing — the template still exists and its `title`, `tags`, and preview are all returned.

### Do template requests cost credits?

No. Templates are public data, so `/v1/templates` and `/v1/templates/{template_id}` charge no credits. Only the generation and edit endpoints consume credits.

### How do I match several tags at once?

Repeat the `tags` param: `?tags=portrait&tags=studio`. It is any-match — a template matching any of the listed tags is returned.
