---
title: "Render candidate mood board frames (metered — TWO CALLS, price first)."
method: POST
path: "/story-projects/{projectId}/mood-board/render-frames"
tags: ["Projects"]
---

# Render candidate mood board frames (metered — TWO CALLS, price first).

`POST /story-projects/{projectId}/mood-board/render-frames`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-projects/{projectId}/mood-board/render-frames`
- Auth: HTTP bearer

Draw candidate frames for the project's look from a brief. METERED: one still image per frame. Sent WITHOUT `confirmed` it prices the batch and renders nothing (`quotedOnly: true`, `credits`, and a `note` to show the writer); sent again with `confirmed: true` it reserves the credit lines and queues the batch. `confirmed` is optional rather than a required `true` because a field that must be true to validate cannot tell a confirmed call from an unconfirmed one, and it would force the price to be announced after the money moved. EVERY CANDIDATE COSTS, KEPT OR NOT — keeping is the free act that agrees the look, so the frames the writer drops were still drawn and still charged; the quote says so and a caller showing the price must show that sentence with it. Preconditions run above the price: a project that has not passed premise coverage, or no image model bound for frames, is 409 `project_precondition_failed` with nothing quoted. A confirmed call answers with a `chatToolJobId` — the frames are NOT in the response. Poll getProjectMoodBoard until the batch leaves `renders` and its frames appear in `candidates`, then keep the ones that are right.

## Headers

- `Idempotency-Key` string, required

## Request body

- MoodBoardRenderRequest
  - `brief` string, required: What this batch should show, in the writer's terms — a place, a light, a texture, a weather.
  - `count` integer: How many candidates to draw. Defaults to 2; the cap is the board's cap. EACH ONE COSTS.
  - `aspectRatio` string: Frame shape, e.g. "16:9". Defaults to the show's widescreen.
  - `confirmed` boolean: Omit (or false) to PRICE the batch and render nothing. True only after the writer has seen that price and said go.

## Response `200`

The price (no `confirmed`) or the queued batch.

- MoodBoardRenderEnvelope
  - `quotedOnly` boolean, required: true when this call only PRICED the batch: nothing reserved, nothing queued.
  - `projectId` string, required
  - `count` integer, required
  - `brief` string, required
  - `modelId` string, required: The bound image model the frames would come from.
  - `credits` integer, required
  - `costLabel` string, nullable: Quote only.
  - `balanceAfter` integer: Quote only. Studio Credits the writer would have left once this is spent: the balance available now (as Billing shows it) minus the price. Negative means they are that many short.
  - `sufficient` boolean: Quote only. True when the available balance covers the price. False means the writer must top up first (the note names the Billing link) — do not confirm; the reserve would refuse.
  - `note` string: Quote only — the price, what it would leave (or how short the writer is, with the top-up link), and that unkept frames still cost. Show it to the writer.
  - `queued` boolean: Confirmed calls only.
  - `chatToolJobId` string: Confirmed calls only. Poll getProjectMoodBoard until this batch leaves `renders` and its frames appear in `candidates`.
  - `message` string: Confirmed calls only.

## Other responses

- `400`: Request payload failed validation. The error message names the offending field.
- `401`: Missing, malformed, revoked, or expired API key.
- `402`: Not enough Studio Credits to reserve this job (`insufficient_credits`), and nothing was reserved. `details` is InsufficientCreditsDetails: `required` and `available`, and `autoTopup` when auto top-up is on but did not cover it — `pending` (retry in a moment), `over_cap` (raise the monthly spending cap), `too_small` (buy a bigger pack) or `failed`.
- `403`: API key is valid but lacks the scope, is blocked by IP allowlist, or the account is disabled.
- `404`: The named project or source does not exist or is owned by another account.
- `409`: The project has not reached the development stage this call needs (`project_precondition_failed`), and nothing was created, queued or charged. `details` is ProjectPreconditionDetails: `stage` names the stage to finish, `missingFields` what it still lacks, `canPlan` / `canStartEpisode` what the project can do now. Finish the named stage — do not retry; the identical call refuses the same way until it is done.
- `429`: Rate limit for this API key + IP + route family was exceeded.
- `500`: The API failed unexpectedly. Safe to retry with the same Idempotency-Key.

---

[API](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api.md) · [All operations](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/revisions/64c1102ccb2f?raw)
