---
title: "Dry-run a POST /v1/runs body: will it be admitted, and what does it cost"
method: POST
path: "/runs/preflight"
---

# Dry-run a POST /v1/runs body: will it be admitted, and what does it cost

`POST /runs/preflight`

## Request

- Base URL: `https://www.clipzyn.com/v1`
- URL: `https://www.clipzyn.com/v1/runs/preflight`
- Auth: HTTP bearer

Preview whether this exact body would be admitted by POST /v1/runs, returning
{allowed, quota_cost, tier, rejection, resolved_order}.

This call SPENDS NOTHING: nothing is enqueued, no quota is reserved, and no
Idempotency-Key is consumed (never send one here). quota_cost is the account's
period upload cost of an accepted run, the same quota math GET /v1/account shows
as period.used/period.cap, never a money figure. When allowed is false, rejection
carries the exact typed error a real run would return (same code, hint, and
retry_after_sec), so fix that before POST /v1/runs. Preflight validates request
admissibility (source shape, plan entitlements, sandbox limits, quota, option
bounds); it does not fetch the source object, so source_invalid or
upload_too_large can still surface on the real run. Requires runs:write. Recover
from a rejection by following rejection.hint; for service_unavailable or
rate_limited, wait retry_after_sec and retry once.

## Request body

- RunRequest: POST /v1/runs body (v2 §4.2). Exactly one of storage_path | source_url | sources. No ``force`` (admin-only, never on /v1), no ``video`` (dev local-path), no ``re_run_id`` (deferred, A6): the /v1 grammar is narrower than the browser's on purpose.
  - `storage_path` string, nullable
  - `source_url` string, nullable
  - `sources` SourceInput[], nullable
    - `role` string, nullable
    - `media_kind` string, nullable
    - `origin` string, required
    - `filename` string, required
    - `storage_key` string, nullable
    - `source_url` string, nullable
    - `size_bytes` integer, nullable
    - `subject_hint` string, nullable
  - `top_n` integer
  - `sub_clips` boolean
  - `shorts` boolean
  - `midform` boolean
  - `midform_n` integer, nullable
  - `midform_request` string, nullable
  - `trailer` boolean
  - `trailer_request` string, nullable
  - `twitter` boolean
  - `twitter_n` integer, nullable
  - `twitter_request` string, nullable
  - `carousels` boolean
  - `carousels_n` integer, nullable
  - `carousels_request` string, nullable
  - `clip_request` string, nullable
  - `count_mode` 'exact' | 'engine', nullable
  - `engine` 'standard' | 'deep', nullable: The cut to run: 'standard' (the normal cut) or 'deep' (the deep cut). Omit it for the default routing. 'deep' is refused with validation_error while the deep cut is switched off. Echoed as options_effective.engine.
  - `title` string, nullable
  - `host_name` string, nullable
  - `guest_name` string, nullable
  - `source_type` string
  - `duration_sec` number, nullable
  - `pulse_recorded_at` string, nullable
  - `pulse_go_live_at` string, nullable
  - `pulse_people` PulsePerson[], nullable
    - `name` string, required
    - `role` string, nullable
    - `note` string, nullable
  - `pulse_episode_context` string, nullable
  - `pulse_focus_topics` string[], nullable
  - `pulse_avoid_topics` string[], nullable
  - `pulse_source_published_at` string, nullable

## Response `200`

Successful Response

- PreflightResponse: POST /v1/runs/preflight 200 body (R-v5-5): a QUOTA-DENOMINATED dry run. ``allowed`` is whether the same body would be admitted right now; ``quota_cost`` is what an accepted run spends against the account's period upload cap (the number GET /v1/account already exposes as period.used/period.cap), NEVER a money/COGS figure; ``tier`` is the effective tier the verdict was computed on; ``rejection`` is the typed reject a real run would return (null when allowed). ``resolved_order`` previews the exact per-format counts and compiler activation. Nothing is enqueued, no quota is spent, and no Idempotency-Key is consumed.
  - `allowed` boolean, required
  - `quota_cost` integer, required
  - `tier` string, required
  - `rejection` PreflightRejection: The typed reject a real POST /v1/runs WOULD have produced, embedded in a preflight verdict (R-v5-5). Same shape as the ``error`` object of the D27 envelope, so an agent parses a predicted reject exactly like a live one.
    - `code` string, required
    - `message` string, required
    - `detail` unknown
    - `retryable` boolean, required
    - `retry_after_sec` integer, nullable
    - `hint` string, required
    - `docs` string, required
    - `request_id` string, required
  - `resolved_order` ResolvedOrder, required: The exact per-format order a run will execute after count resolution. Counts are zero for formats that are not ordered. ``compiler`` reports whether non-empty per-format instructions activate the request compiler.
    - `shorts` integer, required: Number of short-form clip families ordered.
    - `twitter` integer, required: Number of Twitter/X clips ordered.
    - `midform` integer, required: Number of mid-form segments ordered.
    - `carousels` integer: Number of image carousels ordered: 0 when not ordered, and 0 when the engine owns the count (engine count_mode with no carousels_n); options_effective.carousels says whether carousels were admitted.
    - `trailer_takes` integer, required: Number of distinct trailer takes ordered.
    - `compiler` boolean, required: Whether the per-format request compiler will run.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/clipzyn/apis/clipzyn-api.md) · [All operations](https://skmtc.dev/clipzyn/apis/clipzyn-api/llms.txt) · [OpenAPI document](https://skmtc.dev/clipzyn/apis/clipzyn-api/revisions/a0f30a836536?raw)
