---
title: "Make one of a waiting plan's advised changes."
method: POST
path: "/story-plans/{planId}/advice"
tags: ["Plans"]
---

# Make one of a waiting plan's advised changes.

`POST /story-plans/{planId}/advice`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-plans/{planId}/advice`
- Auth: HTTP bearer

A waiting plan's advice is never a gate: the plan stays approvable either way. `openingNovelty` says when its opening echoes an earlier film of the same product and the one change that avoids it (`alternative.field` and `alternative.value`); `signatureDeviceAgreement` says which capture beats show a different device from the one the story's cast is always seen with (`mismatchedBeatIds`). Applying one changes only the beat it names and nothing else. Free. Send `{ kind: "opening", field, value }` (from `openingNovelty.alternative`) or `{ kind: "device", beatIds }` (from `signatureDeviceAgreement.mismatchedBeatIds`). Answers whether it changed anything and the advice after it. Naturally idempotent. The plan review card's button and Slug make the same change.

## Request body

- union
  - object
    - `kind` 'opening', required
    - `field` 'firstWorldStaging' | 'firstScreenCapture', required: `firstWorldStaging` sets the first staging, `firstScreenCapture` the first screen capture.
    - `value` string, required: The new opening (usually `openingNovelty.alternative.value`).
  - object
    - `kind` 'device', required
    - `beatIds` string[], required: The beats to settle (usually `signatureDeviceAgreement.mismatchedBeatIds`).

## Response `200`

What the change did, and the advice after it.

- PlanAdviceApplyResponse
  - `planId` string, required
  - `applied` boolean, required: False when there was nothing to change (no such beat, no stated device, or an opening value that would read on the other side); the plan is then untouched.
  - `openingNovelty` PlanAdviceOpeningNovelty, required: The plan opens the way an earlier film of the same product did: the sentence naming it, and the one change that avoids it.
    - `trope` string, required
    - `nearestPriorFilmIds` string[], required: The same product's matching earlier plans, nearest first.
    - `sentence` string, required
    - `alternative` object, required: Send it back as `{ kind: "opening", field, value }` to make the change.
      - `field` 'firstWorldStaging' | 'firstScreenCapture', required
      - `value` string, required
  - `signatureDeviceAgreement` PlanAdviceDeviceAgreement, required: The device the story's cast is always seen with, read against each capture beat's frame. Send `mismatchedBeatIds` back as `{ kind: "device", beatIds }` to settle them.
    - `signatureDevice` 'laptop' | 'phone', nullable, required
    - `stated` boolean, required
    - `mismatchedBeatIds` string[], required
    - `agrees` boolean, required
    - `summary` string, required
    - `note` string, nullable, required
    - `verdicts` object[], required
      - `beatId` string, required
      - `verdict` 'AGREES' | 'DERIVED' | 'NO_DEVICE' | 'MISMATCH', required
      - `frameKind` string, required
      - `sentence` string, nullable, required

## Other responses

- `400`: Request payload failed validation. The error message names the offending field.
- `401`: Missing, malformed, revoked, or expired API key.
- `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.
- `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/4534f5d25540?raw)
