---
title: "Finish a draft into a locked version."
method: POST
path: "/scripts/{scriptUploadId}/finish"
tags: ["Scripts"]
---

# Finish a draft into a locked version.

`POST /scripts/{scriptUploadId}/finish`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/scripts/{scriptUploadId}/finish`
- Auth: HTTP bearer

The editor's Finalize, through its core, in two steps. Without `coverage`: Slug's judgment of whether the last passing coverage can be carried onto this version (`status: judged`, `carryover`), and nothing is locked. With `coverage: adopt`: that report is copied onto the version, the draft is locked and the screenplay is parsed and its characters analysed (refused with 409 `script_state_invalid` when no earlier version has passing coverage, before anything is locked). With `coverage: fresh`: the draft is locked and new coverage runs; the screenplay is parsed free, while character extraction, casting, voices and the table read wait until its coverage meets the target — refused with 409 `project_precondition_failed` while the project gate holds paid work. A version that is not a draft is refused with 409 `script_state_invalid`. Requires `Idempotency-Key`.

## Headers

- `Idempotency-Key` string, required

## Request body

- FinishDraftRequest
  - `coverage` 'adopt' | 'fresh': `adopt` carries the last passing coverage forward; `fresh` runs new coverage before the screenplay is prepared. Leave it out for the recommendation only.

## Response `200`

The recommendation (`judged`), or the finished version (`finished`).

- union
  - object
    - `status` 'judged', required
    - `scriptUploadId` string, required
    - `carryover` object, required
      - `available` boolean, required: Whether an earlier version has passing coverage to carry forward.
      - `base` object, nullable, required: That report: `reportId`, `scriptUploadId`, `versionNumber`, `overallScore`, `gateThreshold`.
      - `change` object, nullable, required: How much changed since: `addedLines`, `removedLines`, `changedFraction`.
      - `recommendation` 'adopt' | 'rerun', required
      - `reason` string, required
  - object
    - `status` 'finished', required
    - `scriptUploadId` string, required
    - `versionStatus` string, required: The locked version's status, e.g. `v2`.
    - `coverage` 'adopt' | 'fresh', required
    - `script` WriterScript, required
      - `scriptId` string, required
      - `routeId` string, required: The id in its Writers Lab URL.
      - `projectId` string, nullable, required: Its project, or null for a script in no project.
      - `title` string, required: The stored title.
      - `label` string, required: The name the episode is listed by everywhere, e.g. "S1E1 — The Return": its season and number with its title (a leading project name and episode code taken off); a script in no project is its title. Display only.
      - `slug` string, nullable, required: Its URL slug.
      - `logline` string, nullable, required: The writer's one-sentence logline — the same one Slug and the Writers Lab read and write.
      - `mediaType` string, nullable, required: `film` or `tv`.
      - `genre` string, nullable, required: Free text, in the writer's words.
      - `seasonNumber` integer, required
      - `createdAt` string, date-time, required
      - `updatedAt` string, date-time, required
      - `workspacePath` string, required: Where it opens in the Writers Lab, relative to the site origin.
      - `versions` WriterScriptVersion[], required: Newest first.
        - `scriptUploadId` string, required: The version's id: what coverage, overrides, table reads and exports take.
        - `versionNumber` integer, required
        - `title` string, required
        - `status` string, required: `draft` while it is being written; otherwise a locked version.
        - `parseStatus` string, nullable, required: Where the screenplay parse stands (`queued`, `parsing`, `complete`, `failed`); null when none ran.
        - `slug` string, nullable, required
        - `wordCount` integer, required
        - `pageCount` integer, nullable, required
        - `format` string, required: How it was written: text, fountain or fdx.
        - `createdAt` string, date-time, required
        - `counts` object, required: How much has been made from this version.
          - `coverageReports` integer, required
          - `tableReadJobs` integer, required
          - `pitchDeckJobs` integer, required
          - `visualizerJobs` integer, 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.
- `409`: Not a draft, no passing coverage to carry forward (`script_state_invalid`), or the project gate holds paid work (`project_precondition_failed`).
- `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)
