Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: SudoMock Video mockups API
description: 'Product mockup API. Render PSD templates, turn product photos into reusable mockups, create still images and videos, personalize text with fonts, and manage asynchronous jobs and signed webhooks.
Every endpoint answers failures with the same envelope, so the status codes, the error_code values and the retry rule are documented once at https://sudomock.com/docs/errors rather than repeated per operation.'
version: 1.0.0
servers:
- url: https://api.sudomock.com
description: Production
tags:
- name: Video mockups
description: Render a mockup as a short video.
paths:
/api/v1/renders/video:
post:
tags:
- Video mockups
summary: Render a video mockup
description: 'Animates a mockup: produces a still render from the given smart objects, then animates it. Returns 202 with a job_id to poll (GET /api/v1/jobs/{job_id}).'
operationId: render_video_api_v1_renders_video_post
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VideoRenderRequest'
responses:
'202':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/VideoRenderAcceptedResponse'
x-mint:
metadata:
sidebarTitle: Render video mockup
security:
- ApiKeyAuth: []
x-codeSamples:
- lang: Node.js
source: "const response = await fetch(\"https://api.sudomock.com/api/v1/renders/video\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": \"sm_your_api_key\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n }),\n});\n\nconst data = await response.json();\nconsole.log(data);"
- lang: PHP
source: "<?php\n\n$payload = <<<'JSON'\n{\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n}\nJSON;\n\n$ch = curl_init(\"https://api.sudomock.com/api/v1/renders/video\");\ncurl_setopt($ch, CURLOPT_CUSTOMREQUEST, \"POST\");\ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\ncurl_setopt($ch, CURLOPT_HTTPHEADER, [\n \"x-api-key: sm_your_api_key\",\n \"Content-Type: application/json\",\n]);\ncurl_setopt($ch, CURLOPT_POSTFIELDS, $payload);\n\n$response = curl_exec($ch);\ncurl_close($ch);\n\necho $response;"
- lang: Python
source: "import requests\n\npayload = {'mockup_uuid': '123e4567-e89b-12d3-a456-426614174000',\n 'smart_objects': [{'asset': {'fit': 'fill',\n 'url': 'https://example.com/user-design.png'},\n 'uuid': '223e4567-e89b-12d3-a456-426614174001'}],\n 'video': {'audio': False,\n 'duration_seconds': 4,\n 'motion': 'ambient',\n 'advanced_model': None}}\n\nresponse = requests.post(\n \"https://api.sudomock.com/api/v1/renders/video\",\n headers={\"x-api-key\": \"sm_your_api_key\"},\n json=payload,\n)\n\nresponse.raise_for_status()\nprint(response.json())"
- lang: Ruby
source: "require \"net/http\"\nrequire \"uri\"\n\nuri = URI(\"https://api.sudomock.com/api/v1/renders/video\")\nrequest = Net::HTTP::Post.new(uri)\nrequest[\"x-api-key\"] = \"sm_your_api_key\"\nrequest[\"Content-Type\"] = \"application/json\"\n\nrequest.body = <<~JSON\n {\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n }\nJSON\n\nresponse = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|\n http.request(request)\nend\n\nputs response.body"
- lang: Go
source: "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tpayload := []byte(`{\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n}`)\n\n\treq, err := http.NewRequest(\"POST\", \"https://api.sudomock.com/api/v1/renders/video\", bytes.NewBuffer(payload))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"x-api-key\", \"sm_your_api_key\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tres, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer res.Body.Close()\n\n\tbody, _ := io.ReadAll(res.Body)\n\tfmt.Println(string(body))\n}"
- lang: Java
source: "import java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\nString payload = \"\"\"\n{\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n}\n\"\"\";\n\nHttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(\"https://api.sudomock.com/api/v1/renders/video\"))\n .header(\"x-api-key\", \"sm_your_api_key\")\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(payload))\n .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n .send(request, HttpResponse.BodyHandlers.ofString());\n\nSystem.out.println(response.body());"
- lang: .NET
source: "using System.Net.Http;\nusing System.Text;\n\nvar payload = \"\"\"\n{\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n}\n\"\"\";\n\nvar request = new HttpRequestMessage(HttpMethod.Post, \"https://api.sudomock.com/api/v1/renders/video\");\nrequest.Headers.Add(\"x-api-key\", \"sm_your_api_key\");\nrequest.Content = new StringContent(payload, Encoding.UTF8, \"application/json\");\n\nvar client = new HttpClient();\nvar response = await client.SendAsync(request);\n\nConsole.WriteLine(await response.Content.ReadAsStringAsync());"
- lang: cURL
source: "curl -X POST \"https://api.sudomock.com/api/v1/renders/video\" \\\n -H \"x-api-key: sm_your_api_key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"mockup_uuid\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"smart_objects\": [\n {\n \"asset\": {\n \"fit\": \"fill\",\n \"url\": \"https://example.com/user-design.png\"\n },\n \"uuid\": \"223e4567-e89b-12d3-a456-426614174001\"\n }\n ],\n \"video\": {\n \"audio\": false,\n \"duration_seconds\": 4,\n \"motion\": \"ambient\",\n \"advanced_model\": null\n }\n }'"
components:
schemas:
SmartObjectInput:
properties:
uuid:
type: string
pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
description: UUID of the smart object to render
examples:
- b41a7e52-93c8-4d61-8f07-2ae5c9d04713
asset:
anyOf:
- $ref: '#/components/schemas/AssetInput'
- type: 'null'
description: Asset configuration (image to place)
color:
anyOf:
- $ref: '#/components/schemas/ColorOverlay'
- type: 'null'
description: Color overlay configuration
adjustment_layers:
anyOf:
- $ref: '#/components/schemas/AdjustmentLayers'
- type: 'null'
description: Image adjustments applied to your artwork after the fit transform and before it is blended into the mockup. PSD-level adjustment layers are not affected.
type: object
required:
- uuid
title: SmartObjectInput
description: One smart object and what goes into it.
example:
asset:
fit: fill
rotate: 0
url: https://example.com/user-design.png
color:
blending_mode: multiply
hex: '#FF5733'
uuid: 223e4567-e89b-12d3-a456-426614174001
AssetInput:
properties:
url:
anyOf:
- type: string
- type: 'null'
description: 'URL to the user''s image (HTTP/HTTPS or data: URL). Either url or base64 must be provided. The server fetches the image, so the request stays small: the right choice for large files.'
examples:
- https://example.com/user-design.png
base64:
anyOf:
- type: string
- type: 'null'
description: 'Raw base64-encoded image bytes (no data: prefix). Either url or base64 must be provided. Saves the server-side fetch for small images already in memory. Base64 adds about a third to the file size and the whole request must finish uploading within 100 seconds, so send large files by url.'
content_type:
anyOf:
- type: string
pattern: ^image/(png|jpeg|webp|gif)$
- type: 'null'
description: 'MIME type when using base64 field. Supported: image/png, image/jpeg, image/webp, image/gif. Defaults to image/png if omitted.'
examples:
- image/png
fit:
type: string
enum:
- fill
- fit
- crop
description: 'How the artwork meets the smart object area. ''fit'' scales it until it fits inside, keeping proportions, which can leave empty space. ''fill'' stretches it to the bounds and does not keep its proportions. ''crop'' covers the area and cuts the overflow, keeping proportions. ''contain'' and ''cover'' are the older names for ''fit'' and ''crop'' and are still accepted. Defaults to ''fit'': a caller who says nothing is not asking to have their artwork distorted, and a silently stretched design is a defect the caller cannot see.'
default: fit
size:
anyOf:
- $ref: '#/components/schemas/AssetSize'
- type: 'null'
description: 'Custom size override: the box the artwork is fitted into, in the smart object''s own pixels. Width and height are both optional and each must be at least 1 pixel; an axis left out is the smart object''s own size on that axis, and a size with neither axis is ignored.'
position:
anyOf:
- $ref: '#/components/schemas/AssetPosition'
- type: 'null'
description: 'Custom position override (top/left in pixels). With size, the top-left corner of the size box in the smart object''s own pixels, which stays axis-aligned: rotate turns the artwork inside it, never the box. Without size, it places the whole smart object on the mockup instead, its top-left corner at left/top in the template''s own pixels; a smart object in a clipping group or inside a masked group can stay where the template has it.'
rotate:
type: number
maximum: 360.0
minimum: -360.0
description: Rotation angle in degrees (clockwise positive). Applied to the artwork before it is fitted, so the turned artwork, corners included, is what fit places inside the still axis-aligned size box at position.
default: 0
examples:
- 15
flip_horizontal:
type: boolean
description: Flip artwork horizontally (left-right mirror)
default: false
flip_vertical:
type: boolean
description: Flip artwork vertically (top-bottom mirror)
default: false
remove_background:
type: boolean
description: Remove the image background before placing the artwork; the subject is isolated onto a clean transparent cutout. Adds 25 credits per artwork to the render cost.
default: false
tile:
type: boolean
description: Repeat the artwork as a pattern across the whole smart object. size is one tile, fit places the artwork inside each tile, and position sets where the grid starts. Requires size.
default: false
tile_spacing:
type: number
maximum: 100.0
minimum: 0.0
description: Gap between tiles as a percentage of the tile size, 0 to 100. The horizontal gap is that percent of the tile width, the vertical gap that percent of the tile height. Used only when tile is true.
default: 0
type: object
title: AssetInput
description: 'The image to place into a smart object, and how it should sit there.
Image source priority: base64 > url (including data: URLs)
- base64: Raw base64 string (no data: prefix). Suits small images already in memory.
- url: HTTP/HTTPS URL or data:image/...;base64,... URL. Suits large files.'
example:
fit: fill
flip_horizontal: false
flip_vertical: false
position:
left: 20
top: 10
rotate: 15
size:
height: 600
width: 800
url: https://example.com/user-design.png
VideoRenderRequest:
properties:
mockup_uuid:
anyOf:
- type: string
pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
- type: 'null'
description: 'RENDER MODE: UUID of the mockup to animate (from GET /api/v1/psd-mockups or POST /api/v1/psd/upload). Required in render mode; omit in raw-image mode.'
smart_objects:
anyOf:
- items:
$ref: '#/components/schemas/SmartObjectInput'
type: array
minItems: 1
- type: 'null'
description: 'RENDER MODE: smart objects with their assets, identical to a still render (at least 1 required). Required in render mode; omit in raw-image mode.'
export_options:
$ref: '#/components/schemas/ExportOptions'
description: 'RENDER MODE: still-render export configuration (the i2v input frame). Format/size/quality. Ignored in raw-image mode.'
image_url:
anyOf:
- type: string
- type: 'null'
description: 'RAW-IMAGE MODE: a public https png/jpg URL to animate directly (general image-to-video, no render). Supply this OR (mockup_uuid + smart_objects), never both.'
video:
$ref: '#/components/schemas/VideoOptions'
description: Animation options (duration, audio, motion, optional advanced_model override).
webhook:
anyOf:
- additionalProperties: true
type: object
- type: 'null'
description: Completion events (video.succeeded, video.failed) are delivered to the webhook endpoints on your account that subscribe to them. GET /api/v1/jobs/{job_id} stays the source of truth.
type: object
title: VideoRenderRequest
description: 'Request body for POST /api/v1/renders/video.
Supply exactly one of two input modes. Render mode (mockup_uuid and
smart_objects, optionally export_options) mirrors a still render: the
still is produced first, then animated. Raw-image mode
(image_url) animates a public https png or jpg directly with no render
step, and the render fields are ignored.
Both modes take the video animation options and an optional completion
webhook. The call always queues a job; poll GET /api/v1/jobs/{job_id} for
the result. Sending both modes, or neither, returns 400.'
example:
mockup_uuid: 123e4567-e89b-12d3-a456-426614174000
smart_objects:
- asset:
fit: fill
url: https://example.com/user-design.png
uuid: 223e4567-e89b-12d3-a456-426614174001
video:
audio: false
duration_seconds: 4
motion: ambient
advanced_model: null
AdjustmentLayers:
properties:
brightness:
type: integer
maximum: 150.0
minimum: -150.0
description: Brightness adjustment (-150 to 150). 0=no change.
default: 0
contrast:
type: integer
maximum: 100.0
minimum: -100.0
description: Contrast adjustment (-100 to 100). 0=no change.
default: 0
opacity:
type: integer
maximum: 100.0
minimum: 0.0
description: Artwork opacity (0=fully transparent, 100=fully opaque). Default 100.
default: 100
saturation:
type: integer
maximum: 100.0
minimum: -100.0
description: Saturation adjustment (-100 to 100). 0=no change, -100=grayscale.
default: 0
vibrance:
type: integer
maximum: 100.0
minimum: -100.0
description: Vibrance adjustment (-100 to 100). Similar to saturation but preserves skin tones.
default: 0
blur:
type: number
maximum: 100.0
minimum: 0.0
description: Gaussian blur amount (0=sharp, 100=max blur). Useful for background effects.
default: 0
type: object
title: AdjustmentLayers
description: 'Image adjustment parameters applied to the user''s artwork before blending into the mockup.
Applied after fit transformation, does not affect PSD-level adjustments.'
ColorOverlay:
properties:
hex:
anyOf:
- type: string
pattern: ^#[0-9A-Fa-f]{6}$
- type: 'null'
description: Hex color code (e.g., '#FF5733'). Send this or 'label'.
examples:
- '#FF5733'
label:
anyOf:
- type: string
maxLength: 32
minLength: 1
- type: 'null'
description: One of the colours saved on this mockup, by the name you gave it. Send this instead of 'hex' to keep calling a colour what your own catalogue calls it. Names match exactly, and are set with PATCH /api/v1/psd-mockups/{uuid}.
blending_mode:
type: string
description: 'Blend mode for color overlay. Common values: ''normal'', ''multiply'' (fabric/textile mockups), ''screen'', ''overlay'', ''soft_light''. All 27 Photoshop blend modes supported.'
default: normal
examples:
- multiply
type: object
title: ColorOverlay
description: A colour laid over the smart object, with a blending mode.
example:
blending_mode: multiply
hex: '#FF5733'
VideoRenderAcceptedResponse:
properties:
job_id:
type: string
description: Identifier of the accepted job, used to poll its status.
examples:
- 5f1c0b2a-7d43-4e9a-8c16-0b5d3f7a2e94
kind:
type: string
const: video
description: The kind of work accepted, which is always a video.
status:
type: string
enum:
- queued
- dispatched
- running
- succeeded
- failed
- cancelled
description: State of the job when this response was returned.
status_url:
type: string
description: Path to poll for this job's status.
examples:
- /api/v1/jobs/5f1c0b2a-7d43-4e9a-8c16-0b5d3f7a2e94
model:
type: string
description: The video model chosen for this clip.
outcome_tier:
type: string
description: The quality level this clip is produced at.
examples:
- standard
estimated_credits:
anyOf:
- type: integer
- type: 'null'
description: Credits this clip costs, which is null only when an accepted job's original quote is no longer known.
examples:
- 38
duration_seconds:
type: integer
description: Length of the clip in seconds.
examples:
- 4
audio:
type: boolean
description: Whether the clip is produced with sound.
examples:
- false
type: object
required:
- job_id
- kind
- status
- status_url
- model
- outcome_tier
- duration_seconds
- audio
title: VideoRenderAcceptedResponse
description: Acknowledgement that a video mockup was accepted and is being produced.
example:
audio: false
duration_seconds: 4
estimated_credits: 38
job_id: 5f1c0b2a-7d43-4e9a-8c16-0b5d3f7a2e94
kind: video
model: veo-3.1-fast
outcome_tier: standard
status: queued
status_url: /api/v1/jobs/5f1c0b2a-7d43-4e9a-8c16-0b5d3f7a2e94
VideoOptions:
properties:
duration_seconds:
type: integer
maximum: 15.0
minimum: 1.0
description: Clip length in seconds chosen by the customer. Must be one of the supported durations; the endpoint rejects an unsupported value with 400. The credit cost scales with this value.
default: 4
audio:
type: boolean
description: 'Generate sound with the clip. Default OFF (muted clips are cheaper). Audio increases the credit cost on models that charge an audio premium. Note: motion=''showcase'' always includes sound, so the endpoint treats it as audio=true and prices it accordingly.'
default: false
motion:
type: string
enum:
- ambient
- showcase
description: '''ambient'' = subtle looping hero motion that keeps the print readable (muted unless audio=true); ''showcase'' = one deliberate cinematic camera/product move, always with sound (priced as audio=true).'
default: ambient
advanced_model:
anyOf:
- type: string
- type: 'null'
description: Advanced override for the automatic quality selection. Unsupported values are rejected. null = automatic (recommended).
type: object
title: VideoOptions
description: 'Animation options for a POST /renders/video request.
The still is produced from the same mockup_uuid + smart_objects payload as a
normal render, then animated into a short video. SudoMock picks the best video
model for the image automatically; `advanced_model` is an optional override.
You choose `duration_seconds` and `audio`, and the credit cost scales with both.
`duration_seconds` must be one of the chosen model''s allowed durations, or the
endpoint returns 400 INVALID_VIDEO_DURATION otherwise. The 1..15 range here is a
coarse guard; the exact allowed set depends on the model.'
ExportOptions:
properties:
image_format:
type: string
enum:
- png
- jpg
- webp
description: 'Output format: ''webp'' (30-70% smaller, recommended), ''png'' (lossless), ''jpg'' (smallest, no transparency)'
default: webp
image_size:
anyOf:
- type: integer
maximum: 10000.0
minimum: 100.0
- type: string
const: original
description: 'Output width in pixels (100-10000), or "original" to render a PSD template at its own size and resolution: the template''s own width and, unless dpi is set, the resolution the PSD was saved with. Height auto-calculated from the source aspect ratio. Renders on an account in trial are capped at 1024: a larger width is rejected with error_code OUTPUT_RESOLUTION_LIMIT (HTTP 402) and is never silently downscaled. Add a payment method to render at full width.'
default: 2048
examples:
- 2048
- original
quality:
type: integer
maximum: 100.0
minimum: 1.0
description: 'Compression quality for JPG/WebP (1-100). Ignored for PNG (always lossless). Default: 90.'
default: 90
dpi:
anyOf:
- type: integer
maximum: 2400.0
minimum: 72.0
- type: 'null'
description: 'Resolution tag stamped into the output file metadata (JPG/WebP via Exif XResolution, PNG via pHYs), e.g. 300 for print. This is metadata only: it does NOT change the pixels. Pixel dimensions are controlled by image_size: set image_size = print_size_inches * dpi for a true print-ready file (e.g. 12 in * 300 = 3600 px). Range 72-2400. Default: null (opt-in). Omitting it does not leave the file untagged: the encoder writes a default ~25.4 DPI (1 px/mm) tag. With image_size "original" and no dpi, the file carries the PSD''s own resolution. All three formats carry the tag, but for maximum print-tool compatibility prefer jpg or png, because WebP stores resolution in Exif and not every viewer surfaces it.'
examples:
- 300
type: object
title: ExportOptions
description: Format, size and quality of the rendered file.
example:
image_format: webp
image_size: 2048
quality: 95
AssetSize:
properties:
width:
anyOf:
- type: number
minimum: 1.0
- type: 'null'
description: Custom width in pixels
examples:
- 800
height:
anyOf:
- type: number
minimum: 1.0
- type: 'null'
description: Custom height in pixels
examples:
- 600
type: object
title: AssetSize
description: 'How large the artwork is drawn inside the smart object.
Fractional pixels are accepted.'
AssetPosition:
properties:
top:
anyOf:
- type: number
- type: 'null'
description: Top offset in pixels
examples:
- 100
left:
anyOf:
- type: number
- type: 'null'
description: Left offset in pixels
examples:
- 100
type: object
title: AssetPosition
description: 'Where the artwork sits inside the smart object.
Fractional pixels are accepted.'
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: x-api-key
description: API key with sm_ prefix. Get your key at https://sudomock.com/dashboard/api-keys