Official Python client for the SudoMock Mockup Generator API.
Generate photorealistic product mockups from PSD templates or 2D mockups -- all from your Python code.
pip install sudomockfrom sudomock import SudoMock
# 1. Create a client (or set SUDOMOCK_API_KEY env var)
client = SudoMock(api_key="sm_your_api_key")
# 2. List your mockup templates
mockups = client.mockups.list(limit=10)
for m in mockups.mockups:
print(f"{m.name} ({m.uuid})")
# 3. Render a mockup with your artwork
render = client.renders.create(
mockup_uuid=mockups.mockups[0].uuid,
smart_objects=[{
"uuid": mockups.mockups[0].smart_objects[0].uuid,
"asset": {"url": "https://example.com/your-design.png"},
}],
)
print(render.url) # https://cdn.sudomock.com/renders/.../render.webpUse an editable text layer from the mockup response to create personalized
outputs. smart_objects is optional for text-only renders, and fit defaults
to "overflow".
mockup = client.mockups.get("mockup-uuid")
name_layer = next(layer for layer in mockup.text_layers if layer.name == "Customer Name")
if not name_layer.is_editable:
raise ValueError("Customer Name is not editable")
renders = [
client.renders.create(
mockup_uuid=mockup.uuid,
text_layers=[{
"uuid": name_layer.uuid,
"text": name,
"font": "Montserrat-Bold",
"color": "#FFFFFF",
"fit": "overflow",
}],
)
for name in ["Aylin", "Deniz", "Mert"]
]
for render in renders:
print(render.url)
for warning in render.warnings:
print(warning.code, warning.message)import asyncio
from sudomock import AsyncSudoMock
async def main():
async with AsyncSudoMock(api_key="sm_your_api_key") as client:
mockups = await client.mockups.list()
render = await client.renders.create(
mockup_uuid=mockups.mockups[0].uuid,
smart_objects=[{
"uuid": mockups.mockups[0].smart_objects[0].uuid,
"asset": {"url": "https://example.com/design.png"},
}],
)
print(render.url)
asyncio.run(main())Create a 2D mockup from a product image, wait until its print areas are ready, then render your artwork. Creation costs 25 credits and rendering costs 5 credits. Unsuccessful creations are refunded automatically.
customizable is always a boolean, and surfaces and print_areas are typed
lists. Each full surface exposes surface_uuid and coverage="full".
from sudomock import SudoMock
client = SudoMock(api_key="sm_your_api_key")
# Create the 2D mockup (synchronous by default -- returns the finished mockup)
mockup = client.ai.create(
source_url="https://example.com/product.jpg",
name="Product Front",
idempotency_key="product-front-001",
)
render = client.ai.render(
mockup_uuid=mockup.mockup_id,
print_areas=[{
"uuid": mockup.quads[0].print_area_id,
"artwork_url": "https://example.com/your-design.png",
}],
)
print(render.url)
# A full product surface can be rendered directly with its surface UUID.
if mockup.surfaces:
render = client.ai.render(
mockup_uuid=mockup.mockup_id,
print_areas=[{
"surface_uuid": mockup.surfaces[0].surface_uuid,
"artwork_url": "https://example.com/your-design.png",
}],
)
# Async variant: submit to the server queue and poll (returns a JobAccepted)
job = client.ai.render(
mockup_uuid=mockup.mockup_id,
print_areas=[{
"uuid": mockup.quads[0].print_area_id,
"artwork_url": "https://example.com/your-design.png",
}],
is_async=True,
)
result = client.jobs.wait(job.job_id) # terminal Job carries result_url
if result.succeeded:
print(result.url)Submit long-running renders to the server-side queue and poll for the result.
This is independent of AsyncSudoMock -- is_async controls server queueing,
while AsyncSudoMock only controls how your process performs HTTP I/O. Either
client can submit async jobs.
from sudomock import SudoMock
client = SudoMock(api_key="sm_your_api_key")
# Submit -> returns a JobAccepted (HTTP 202), does not block on the render
job = client.renders.create(
mockup_uuid="...",
smart_objects=[{"uuid": "...", "asset": {"url": "https://example.com/d.png"}}],
is_async=True,
)
print(job.job_id, job.status_url)
# Poll until terminal (succeeded / failed)
result = client.jobs.wait(job.job_id) # or client.jobs.get(uuid) once
if result.succeeded:
print(result.url) # result_url
else:
print("failed:", result.error)Animate a mockup into an AI video. Video renders are always async (return a
JobAccepted). The first video render on a free plan is granted once for the
account's lifetime. Unsupported duration_seconds values return 400;
quality selection is automatic.
job = client.renders.create_video(
mockup_uuid="...",
smart_objects=[{"uuid": "...", "asset": {"url": "https://example.com/d.png"}}],
duration_seconds=4,
audio=False,
motion="ambient", # optional; "ambient" (default) or "showcase"
)
video = client.jobs.wait(job.job_id)
print(video.url)
# Raw-image mode: animate a public image URL directly (no mockup render step)
job = client.renders.create_video(
image_url="https://example.com/product.jpg",
duration_seconds=4,
)Remove the background from an image; returns a reusable transparent-PNG URL
valid for 7 days that you can hand straight back to a render as artwork.
Supply exactly one of url or base64. Costs 25 credits per image;
credits are refunded automatically if processing fails.
cutout = client.images.remove_background(url="https://example.com/product-photo.jpg")
print(cutout.url) # signed cutout URL, valid for 7 days
print(cutout.width, cutout.height)
print(cutout.credits_charged) # 25
# Reuse the URL in renders during its 7-day validity window
render = client.renders.create(
mockup_uuid="mockup-uuid",
smart_objects=[{"uuid": "so-uuid", "asset": {"url": cutout.url}}],
)To clean artwork inline during a render instead, set remove_background on the
render asset or 2D print area. It adds 25 credits per unique artwork to the
render (the same artwork reused across several smart objects or print areas is
charged once).
# PSD render
client.renders.create(
mockup_uuid="mockup-uuid",
smart_objects=[{
"uuid": "so-uuid",
"asset": {"url": "https://example.com/photo.jpg", "remove_background": True},
}],
)
# 2D render
client.ai.render(
mockup_uuid="mockup-uuid",
print_areas=[{
"uuid": "print-area-uuid",
"artwork_url": "https://example.com/photo.jpg",
"remove_background": True,
}],
)Upload a PSD by URL and parse it into a mockup template. PSD uploads are free
(zero credits) and support is_async.
mockup = client.psd.upload(url="https://example.com/template.psd", name="My PSD")
print(mockup.uuid)
# Async variant:
job = client.psd.upload(url="https://example.com/template.psd", is_async=True)
mockup = client.jobs.wait(job.job_id)Manage outbound webhook endpoints (authenticated with your x-api-key) and
verify inbound HMAC-signed deliveries.
# Register an endpoint
ep = client.webhook_endpoints.create(
url="https://your-app.com/webhooks/sudomock",
events=["render.succeeded", "render.failed"],
)
print(ep.secret) # store this -- it signs deliveries
# List / update / rotate / test / replay
client.webhook_endpoints.list()
client.webhook_endpoints.update(ep.id, enabled=False)
client.webhook_endpoints.rotate_secret(ep.id)
client.webhook_endpoints.test(ep.id)
deliveries = client.webhook_endpoints.deliveries(ep.id)
client.webhook_endpoints.replay_delivery(ep.id, deliveries.deliveries[0].id)
# Cross-endpoint deliveries feed + bulk replay of all failed deliveries
client.webhook_endpoints.events(limit=100)
client.webhook_endpoints.replay_failed(ep.id)Verify an inbound delivery in your handler (use the raw request body). SudoMock sends the signature and timestamp in two separate headers:
from sudomock import verify_webhook_signature
from sudomock.exceptions import WebhookVerificationError
signature = request.headers["X-SudoMock-Signature"] # hex HMAC-SHA256 digest
timestamp = request.headers["X-SudoMock-Timestamp"] # unix timestamp
try:
verify_webhook_signature(secret, signature, timestamp, raw_body)
except WebhookVerificationError:
... # reject: missing header / replayed / bad signaturefrom sudomock import SudoMock
from sudomock.exceptions import (
AuthenticationError,
InsufficientCreditsError,
RateLimitError,
NotFoundError,
ValidationError,
ServerError,
SudoMockError, # base class for all errors
)
client = SudoMock(api_key="sm_your_api_key")
try:
render = client.renders.create(
mockup_uuid="...",
smart_objects=[...],
)
except AuthenticationError:
print("Invalid API key")
except InsufficientCreditsError as e:
print(f"Out of credits. Resets at: {e.credits_reset_at}")
except RateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after}s")
except NotFoundError:
print("Mockup not found")
except ValidationError:
print("Invalid request parameters")
except ServerError:
print("Server error, will be retried automatically")
except SudoMockError as e:
print(f"Unexpected error: {e.message} (HTTP {e.status_code}, code={e.error_code})")product_id = "product-123"
variant_id = "variant-456"
session = client.studio.create_session(
mockup_type="2d",
session_kind="customize",
mockup_uuid="11111111-1111-4111-8111-111111111111",
allowed_origin="https://shop.example",
product_id=product_id,
variant_id=variant_id,
action_id="add-to-cart",
ui={
"primary_action_label": "Add to cart",
"secondary_action_label": "Preview",
"accent_color": "#3366FF",
},
)
# Open studio.sudomock.com/editor?session=<session.session> in your iframe.
# Keep session.bootstrap_secret on the trusted parent page for the required handshake.allowed_origin, product_id, and variant_id work for both PSD and 2D
sessions. PSD supports customize; 2D supports setup and customize.
Every response includes session, expires_in, message_session_id, and
bootstrap_secret.
Never put the bootstrap secret in the iframe URL or logs.
Setup emits studio.mockup-saved; customize emits
studio.design-submitted. Every result carries stable mockup_uuid and
render_uuid, plus the optional action_id. Treat render_uuid as the opaque
confirmation handle; the parent page does not receive editor revision state.
On your server, confirm that browser event before saving the mockup or adding anything to a cart:
receipt = client.studio.consume_action(
event,
action_context={
"product_id": product_id,
"variant_id": variant_id,
},
)The action context must exactly match the values used to create the session. The typed receipt is bound to the session, render, API key owner, and context, and can be consumed only once.
from sudomock import SudoMock
client = SudoMock(api_key="sm_your_api_key")
account = client.account.get()
print(f"Plan: {account.subscription.plan}")
print(f"Credits remaining: {account.usage.credits_remaining}")
print(f"Credits limit: {account.usage.credits_limit}")
print(f"Period ends: {account.subscription.current_period_end}")from sudomock import SudoMock
client = SudoMock(
api_key="sm_your_api_key", # or SUDOMOCK_API_KEY env var
base_url="https://api.sudomock.com", # default
timeout=30.0, # default request timeout (seconds)
render_timeout=120.0, # render request timeout (seconds)
max_retries=3, # TOTAL attempts on 429/5xx/network: initial + up to 2 retries (exponential backoff)
)| Method | Description |
|---|---|
client.mockups.list(limit=, offset=, name=, created_after=, created_before=, sort=, order=) |
List mockup templates (filter by name) |
client.mockups.get(uuid) |
Get mockup details |
client.mockups.update(uuid, name=) |
Rename a mockup |
client.mockups.delete(uuid) |
Delete a mockup |
Bulk delete (
DELETE /mockups/all) is dashboard-only (Bearer/JWT auth) and is intentionally not exposed in this api-key SDK.
| Method | Description |
|---|---|
client.renders.create(mockup_uuid=, smart_objects=None, text_layers=None, export_options=, export_label=, is_async=False) |
Render artwork, text replacements, or both (sync Render, or JobAccepted when is_async=True) |
client.renders.create_video(mockup_uuid=, smart_objects=, image_url=, duration_seconds=, audio=False, motion=None, webhook=None, ...) |
AI video render (always async, returns JobAccepted). Render mode (mockup_uuid+smart_objects) or raw-image mode (image_url) |
| Method | Description |
|---|---|
client.jobs.list(kind=, mockup_uuid=, limit=, cursor=) |
List your async jobs (keyset-paginated, newest first) |
client.jobs.get(job_id) |
Get async job status (queued/running/succeeded/failed) |
client.jobs.wait(job_id, poll_interval=2.0, timeout=300.0) |
Poll until the job reaches a terminal state |
| Method | Description |
|---|---|
client.psd.upload(url=, name=None, is_async=False) |
Upload a PSD by URL (free; sync Mockup or JobAccepted) |
| Method | Description |
|---|---|
client.ai.create(source_url=, source_base64=, name=, print_areas=, is_async=False, idempotency_key=) |
Create a 2D mockup (25 credits; sync TwoDMockup by default, or JobAccepted when is_async=True) |
client.ai.wait_for_2d_mockup(job_id, poll_interval=2.0, timeout=180.0) |
Wait for an is_async=True creation and return the full 2D mockup |
client.ai.update_2d_print_areas(mockup_id, print_areas) |
Replace a 2D mockup's print areas (free) |
client.ai.render(mockup_uuid=, print_areas=, export_options=, is_async=False) |
Render artwork onto a 2D mockup (5 credits; sync AIRender with render_uuid by default, or JobAccepted when is_async=True) |
client.ai.list(limit=, offset=, customizable_only=) |
List your 2D mockups; set customizable_only=True for shopper-ready items |
client.ai.get(mockup_id) |
Get a 2D mockup |
client.ai.delete(mockup_id) |
Delete a 2D mockup |
| Method | Description |
|---|---|
client.images.remove_background(url=, base64=, content_type=) |
Remove an image's background (25 credits; returns a BackgroundRemoval with a signed transparent-PNG cutout URL valid for 7 days) |
| Method | Description |
|---|---|
client.account.get() |
Get account info, credits, subscription |
| Method | Description |
|---|---|
client.packages.plans() |
List active subscription plans (no auth) |
client.packages.pricing() |
List public pricing (no auth) |
| Method | Description |
|---|---|
client.webhook_endpoints.list() |
List registered endpoints |
client.webhook_endpoints.create(url=, events=, description=None) |
Register an endpoint (empty events = all) |
client.webhook_endpoints.get(uuid) |
Get an endpoint |
client.webhook_endpoints.update(uuid, url=, events=, description=, enabled=) |
Update an endpoint |
client.webhook_endpoints.delete(uuid) |
Delete an endpoint |
client.webhook_endpoints.rotate_secret(uuid) |
Rotate the signing secret |
client.webhook_endpoints.test(uuid) |
Send a synthetic test delivery |
client.webhook_endpoints.events(status=, event_type=, limit=) |
Deliveries feed across all endpoints |
client.webhook_endpoints.deliveries(uuid) |
List delivery attempts for one endpoint |
client.webhook_endpoints.replay_delivery(uuid, delivery_id) |
Replay one failed delivery |
client.webhook_endpoints.replay_failed(uuid) |
Replay all failed/dead deliveries |
verify_webhook_signature(secret, signature, timestamp, raw_body) |
Verify an inbound HMAC signature (split headers) |
export_options = {
"image_format": "webp", # "webp", "png", "jpg"
"image_size": 1920, # max dimension in pixels
"quality": 95, # 1-100 (for webp/jpg)
}smart_objects = [{
"uuid": "smart-object-uuid",
"asset": {
"url": "https://example.com/design.png",
"fit": "fill", # "fill" (default), "contain", "cover"
"rotate": 0, # degrees
"position": {"top": 100, "left": 100},
"size": {"width": 800, "height": 600},
"remove_background": False, # True isolates the subject (+25 credits per artwork)
},
"color": {
"hex": "#FFFFFF",
"blending_mode": "multiply",
},
}]- Python 3.9+
- httpx for HTTP
- Pydantic v2 for response models
- tenacity for retry logic
MIT -- see LICENSE.
SudoMock also offers an official Model Context Protocol (MCP) server, enabling AI assistants like Claude, Cursor, and VS Code Copilot to generate mockups directly.
- npm package: @sudomock/mcp
- Remote server:
mcp.sudomock.com(HTTP transport, no Node.js required) - Documentation: sudomock.com/docs/mcp