---
title: "Render the project head"
method: POST
path: "/v1/projects/{project_id}/renders"
tags: ["projects"]
---

# Render the project head

`POST /v1/projects/{project_id}/renders`

## Request

- Base URL: `https://api.framelane.io`
- URL: `https://api.framelane.io/v1/projects/{project_id}/renders`
- Auth: HTTP bearer

Submit the current project head to the render engine as a standard (billable) render. Idempotency-Key note: reusing a key returns the original job unconditionally -- the body (here, the project head at submission time) is not compared, so a key reused after the project changed still replays the earlier job rather than 409ing. The response carries an ADVISORY `ok` + `violations[]` lint of the head; the render is dispatched either way.

## Path parameters

- `project_id` string, required

## Headers

- `Idempotency-Key` string, nullable

## Response `202`

Successful Response

- RenderSubmitOut: A freshly submitted render: the job row plus the advisory lint of its composition. Returned only by the two submit routes (``POST /v1/renders``, ``POST /v1/projects/{id}/renders``). A *subclass* rather than two more fields on :class:`JobOut`, because ``JobOut`` is the shape of a job row on every read route (``GET /v1/renders``, all ``/v1/tasks`` routes, the MCP read tools) where a lint result does not exist and would be permanently null.
  - `id` string, required: Unique job ID. Prefix indicates type: `render_` or `task_`.
  - `kind` 'render' | 'task', required
  - `is_preview` boolean: True for a cheap preview render. Previews are not billed and are excluded from the renders list.
  - `editor_url` string, nullable: Open the project this job was submitted from in the authenticated console. Absent for a job with no project.
  - `task_type` 'transcribe': The AI task types the API offers. Values: - `transcribe` — Speech-to-text transcription (via AssemblyAI) with optional analysis. Returns the transcript inline on the task's `result` object — there is no file artifact, so `output` is null and `/v1/tasks/{id}/download` returns 404.
  - `workspace_id` string, required: ID of the workspace that owns this job.
  - `status` 'ingesting' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled', required: Values: - `ingesting` — External source files are being probed for duration before the render is queued. - `queued` — Accepted and waiting for a worker to pick it up. - `processing` — Actively rendering or processing. - `completed` — Finished successfully; the output artifact is available. - `failed` — Terminated with an error (see `error`). - `cancelled` — Cancelled by the caller before completion.
  - `progress_percent` integer: 0–100 progress indicator updated by the render engine.
  - `progress_stage` 'downloading' | 'compositing' | 'encoding' | 'uploading': Values: - `downloading` — Fetching source media. - `compositing` — Compositing the scene on the GPU. - `encoding` — Encoding the output video. - `uploading` — Uploading the finished artifact to storage.
  - `output` JobOutput
    - `url` string, nullable: CDN URL to the completed artifact.
    - `width` integer, nullable: Output video width in pixels.
    - `height` integer, nullable: Output video height in pixels.
    - `duration` number, nullable: Output duration in seconds.
    - `size_bytes` integer, nullable: Artifact file size in bytes.
  - `result` object, nullable: Structured JSON result for tasks that return data rather than a file — `transcribe` returns the transcript here (`text`, `words`, `utterances`, and any requested analysis). Populated once `status == completed`; `null` for renders and for any job whose only artifact is the file at `output.url`.
  - `error` JobError
    - `code` 'invalid_source' | 'source_not_found' | 'source_too_large' | 'asset_not_ready' | 'codec_unsupported' | 'decode_failed' | 'encode_failed' | 'timeout' | 'out_of_memory' | 'gpu_error' | 'upload_failed' | 'provider_error' | 'ingest_failed' | 'render_refused' | 'internal' | 'cancelled', required: Structured error codes surfaced on Job.error_code. Values: - `invalid_source` — A source URL was malformed or inaccessible. - `source_not_found` — A source URL returned 404. - `source_too_large` — A source file exceeded the maximum allowed size. - `asset_not_ready` — A referenced asset was still ingesting when the job ran. - `codec_unsupported` — A source used a codec the renderer cannot decode. - `decode_failed` — A source file could not be decoded. - `encode_failed` — The output could not be encoded. - `timeout` — Processing exceeded the time limit. - `out_of_memory` — The renderer ran out of GPU memory. - `gpu_error` — A GPU hardware/driver error occurred. - `upload_failed` — The output artifact failed to upload to storage. - `provider_error` — An upstream provider (e.g. AssemblyAI) returned an error. - `ingest_failed` — An external source could not be probed (unreachable, not a media file, or timed out). - `render_refused` — The renderer refused the composition by name: a runtime shader that did not compile or ran over its GPU time budget, or an operation, a component or a nested composition it could not apply. `error_message` carries the engine's words; change what it names and submit again. - `internal` — An unexpected internal error occurred. - `cancelled` — The job was cancelled.
    - `message` string, required: Human-readable error description.
  - `metadata` object: Caller-supplied key-value pairs echoed back on every webhook and response.
  - `created_at` string, date-time, required: ISO-8601 UTC timestamp when the job was created.
  - `updated_at` string, date-time, required: ISO-8601 UTC timestamp of the last status change.
  - `completed_at` string, date-time, nullable: ISO-8601 UTC timestamp when the job reached a terminal state.
  - `ok` boolean, required: Advisory — the render was still accepted and dispatched. False means the composition has error-severity findings that will likely make the output wrong (a self-transition, a fully off-canvas element); it does NOT mean the submission was refused. Everything that actually blocks a render is a 422 before you ever see this field.
  - `violations` Violation[]: Advisory — the render was still accepted and dispatched. Cross-element and layout findings from the same linter the preview dry-run runs, attached so you can see problems on the render you just paid for instead of only in the artifact. To catch these BEFORE spending a render, call POST /v1/preview with dry_run.Truncated to the first 36 findings (errors first) on very large compositions, with a VIOLATIONS_TRUNCATED marker carrying the full count; the free dry run is never truncated.
    - `code` string, required: Stable machine code, e.g. 'DANGLING_TRANSITION'.
    - `message` string, required: Human-readable explanation.
    - `severity` 'error' | 'warning'
    - `path` string, nullable: Where it happened, e.g. 'elements[2]' or 'transitions[0].from_id'.
    - `pointer` string, nullable: The same place as an RFC 6901 JSON pointer into the request, e.g. '/elements/2/children/0/text_color'.
    - `target_id` string, nullable: Id of the element, frame, instance or group the finding is about; inside an instance, its expanded path (e.g. 'card1/title'). Null when it names none.
    - `bbox` number[], nullable: Canvas-space box [x, y, w, h] in pixels for layout findings (B1); null otherwise.
    - `time` number, nullable: Timeline second at which the finding applies; null for whole-timeline findings.

## Other responses

- `401`: Missing or invalid API key.
- `404`: Project not found.
- `422`: Validation error or unsupported feature.

---

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