---
title: "Render a preview (0.1 credit per call)"
method: POST
path: "/render/preview"
---

# Render a preview (0.1 credit per call)

`POST /render/preview`

## Request

- Base URL: `https://rendry.io/api/v1`
- URL: `https://rendry.io/api/v1/render/preview`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer
  - HTTP bearer
  - HTTP bearer

Renders through the same preview_preset implementation as MCP and returns a capped JPEG preview as image_base64 plus mime_type and layout warnings. Preview pricing and file limits match MCP. Pass preset_id for a saved template, or canvas+layers for an unsaved inline design. Inline keep_draft defaults to true and retains a reusable draft without consuming a template slot; false never caches a draft and omits draft_id. Explicit keep_draft in saved mode, null and non-boolean values are rejected before charging. The preview request does not overwrite the authored final output format. Dynamic data overrides sample modifications; rows override sample_rows.

## Headers

- `X-Rendry-Account` string

## Request body

- object
  - `preset_id` string: Saved preset. Mutually exclusive with canvas+layers
  - `canvas` Canvas
    - `width` integer
    - `height` integer
    - `background_color` string
    - `output_format` string
    - `quality` integer
    - `max_width` integer
    - `finish_profile` string: Optional whole-canvas finish applied once after composition using the profile-specific paint policy. Use an exact id from the finish_profiles field returned by GET /capabilities; that runtime catalog also declares compatible formats and minimum dimensions.
  - `layers` object[]: Inline draft layers
  - `dynamic_data` object
  - `modifications` object: Sample values for an inline draft
  - `size` 'thumb' | 'normal' | 'large': Inline image size (default normal). Any other value is refused with 400 `invalid_preview_size` before anything is rendered or charged.
  - `region` object: Read one box of the page instead of the whole sheet, in CANVAS pixels (the coordinates your layers already use). THIS is the endpoint for it: the page is composed at its own resolution, the box is cut out of it and only then capped at 640 px on its long side, so a box no larger than that comes back 1:1 and small type on a 2480 px print sheet is finally legible. The reply carries region and scale (multiply canvas pixels by scale to land on the pixels you are looking at). Same price as any preview. Cannot be combined with debug_overlay, max_width or canvas.finish_profile; a box outside the canvas, or with zero area, is refused with the canvas size in the message.
    - `x` integer, required
    - `y` integer, required
    - `width` integer, required
    - `height` integer, required
  - `debug_overlay` boolean: Layer frames retain DSL coordinate labels and map through geometry to the displayed PNG; the thirds grid is in display coordinates.
  - `keep_draft` boolean: Inline mode only. Omission/true retains the reusable draft; false skips caching and omits draft_id. Forbidden with preset_id, including false. Null is invalid.
  - `derived` object[]: Inline derived colors, as in the preset DSL.
  - `sample_rows` object: Inline sample rows for repeat groups.
  - `rows` object: Runtime repeat rows; overrides sample_rows.
  - `palettes` object: Inline colours from data, as in the preset DSL.

## Response `200`

Preview rendered

- object
  - `status` string
  - `width` integer
  - `height` integer
  - `warnings` string[]
  - `image_base64` string
  - `mime_type` string
  - `geometry` RenderGeometry: Geometry of the successful render attempt, bound to job_id and attempt_id. Layout is the DSL coordinate space. Scale layout to pre_finish, map finish.source_rect to finish.destination_rect when present, then scale output to the actual displayed image. Without finish, pre_finish equals output. Display resizing never changes this report. Absent on legacy jobs or document results without raster geometry.
    - `version` 1, required
    - `job_id` string, required
    - `attempt_id` string, required
    - `layout` RenderGeometrySize, required: Positive raster dimensions in pixels.
      - `width` integer, required
      - `height` integer, required
    - `pre_finish` RenderGeometrySize, required: Positive raster dimensions in pixels.
      - `width` integer, required
      - `height` integer, required
    - `output` RenderGeometrySize, required: Positive raster dimensions in pixels.
      - `width` integer, required
      - `height` integer, required
    - `finish` RenderGeometryFinish: source_rect is in pre_finish; destination_rect is in output. These rectangles define the transform. source_bounds is diagnostic alpha in pre_finish; artwork_bounds and finished_bounds are diagnostic alpha in output. Alpha bounds never replace the transform.
      - `profile_id` string, required
      - `source_rect` RenderGeometryRect, required: Half-open integer pixel-edge rectangle. Transform rectangles have positive extents and are contained in their raster space. Diagnostic alpha bounds may be the all-zero rectangle for empty alpha.
        - `x` integer, required
        - `y` integer, required
        - `width` integer, required
        - `height` integer, required
      - `destination_rect` RenderGeometryRect, required: Half-open integer pixel-edge rectangle. Transform rectangles have positive extents and are contained in their raster space. Diagnostic alpha bounds may be the all-zero rectangle for empty alpha.
        - `x` integer, required
        - `y` integer, required
        - `width` integer, required
        - `height` integer, required
      - `source_bounds` RenderGeometryRect, required: Half-open integer pixel-edge rectangle. Transform rectangles have positive extents and are contained in their raster space. Diagnostic alpha bounds may be the all-zero rectangle for empty alpha.
        - `x` integer, required
        - `y` integer, required
        - `width` integer, required
        - `height` integer, required
      - `artwork_bounds` RenderGeometryRect, required: Half-open integer pixel-edge rectangle. Transform rectangles have positive extents and are contained in their raster space. Diagnostic alpha bounds may be the all-zero rectangle for empty alpha.
        - `x` integer, required
        - `y` integer, required
        - `width` integer, required
        - `height` integer, required
      - `finished_bounds` RenderGeometryRect, required: Half-open integer pixel-edge rectangle. Transform rectangles have positive extents and are contained in their raster space. Diagnostic alpha bounds may be the all-zero rectangle for empty alpha.
        - `x` integer, required
        - `y` integer, required
        - `width` integer, required
        - `height` integer, required
  - `draft_id` string: Reusable inline draft handle; omitted when keep_draft is false or in saved mode.
  - `cost` CreditCost: What this call took and what is left.
    - `credits_charged` number
    - `balance` number
  - `text_fit` TextFit[]: What the preview MEASURED for every text layer, when the render reports measurements
    - `id` string: The measured layer's own id. This is why text_fit is a list: the entry names its layer, the list does not
    - `plate` TextPlateFit: Actual unrotated text plate assembly in DSL pixels, before effects and output scaling. Text coordinates identify the logical text box, not tight glyph bounds.
      - `x` integer, required: Plate left relative to the layer origin
      - `y` integer, required: Plate top relative to the layer origin
      - `width` integer, required: Assembled plate width after minimum size and circle sizing
      - `height` integer, required: Assembled plate height after minimum size and circle sizing
      - `text_x` integer, required: Logical text origin inside the plate, including stroke and bearing placement
      - `text_y` integer, required: Logical text origin inside the plate
      - `text_width` integer, required: Resolved advance or constrained wrap width
    - `rotation_origin` TextRotationOrigin: Actual pre-transform assembly center of ordinary raster text relative to the layer origin, in DSL pixels. Half-pixel centers are preserved.
      - `x` number, required
      - `y` number, required
    - `size` integer: The size that was asked for
    - `fitted_size` integer: The size auto_fit settled on; equal to size when nothing shrank
    - `text_w` integer: Width of the rendered text mask
    - `text_h` integer: Height of the rendered text mask
    - `box_w` integer: Declared layer width; 0 means unconstrained
    - `box_h` integer: Declared layer height; 0 means unconstrained
    - `lines` integer: Lines after wrapping, not only explicit newlines
    - `wraps` integer[]: Where the render broke the text for width: the rune offset, in the text as drawn (variables substituted, list markers and soft hyphens in place, rich_text ** pairs removed), at which each line begins because the line before it was full. A line an explicit newline starts is not listed; absent when nothing wrapped. The editor draws its lines at these offsets
    - `mask_x` integer: mask_text placement inside a shape or image
    - `mask_y` integer
    - `mask_w` integer
    - `mask_h` integer
    - `ink_top` integer: Mask offset from the logical text box; a diagnostic, not an alignment source — Pango bounds can include empty rows
    - `ink_left` integer
    - `fit_group` string: The layer's fit_group name, present only for a member of one
    - `size_alone` integer: The size this member would have taken fitted BY ITSELF, before the group levelled. Present only for a member of a fit_group; a layer in no group reports fitted_size and nothing more
    - `fit_group_size` integer: The size the group settled on — the smallest size_alone among its members. A member whose own min_size is above it keeps min_size, so fitted_size can exceed fit_group_size. Present only for a member of a fit_group
    - `origin` TextDiagnosticOrigin
      - `key` string: Server-created prepared structural identity; independent of generated cell IDs.
      - `layer_id` string: Safe authored layer identifier; values and paths are excluded.
      - `table` boolean
      - `master` boolean
      - `page` integer: Final page, 1-based.
      - `row` integer: Original body row, 1-based; header row zero.
      - `column` integer: Column, 1-based.
      - `header` boolean
  - `chart_fit` object[]: Measured geometry of every chart layer; sent only when the design has one
  - `region` object: With region — the box that was cut out, in canvas pixels
    - `x` integer
    - `y` integer
    - `width` integer
    - `height` integer
  - `scale` number: With region — canvas pixels × scale = pixels of the returned picture
  - `note` string: The price of this call and what to call for the final
  - `look_at_it` string: What to check on the picture before iterating
  - `save_hint` string: Inline mode — how to turn the draft into a template without resending it
  - `warnings_note` string: What the warnings list covers
  - `autofixed_fields` string[]: Fields renamed on the way in
  - `autofix_note` string
  - `debug_overlay` string: With debug_overlay: true — what the frames and grid on the picture mean
  - `expires_at` string, date-time: Persisted result/draft access deadline when the preview owns an expiring artifact.

## Other responses

- `400`: Invalid design or arguments; nothing was charged: DSL problems (`invalid_input`), `unknown_field`, `variables_unresolved` (with `diagnostics`), a `size` outside thumb, normal, large (`invalid_preview_size`). 404 `preset_not_found` for a preset the caller cannot see.
- `401`: Missing/invalid API key
- `402`: Not enough credits: the body carries error, balance, required and packages_url.
- `429`: More than 10 requests a second from this account; Retry-After says when

---

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