Skip to main content

SudoMock Python SDK

Official Python client for the SudoMock Mockup Generator API.

Generate photorealistic product mockups from PSD templates or photo mockups, all from your Python code.

PyPI Python License: MIT CI

Installation

pip install sudomock

Quick Start

from 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.psd_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.webp

Batch text personalization

Use 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.psd_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)

Hide layers for one render

hidden_layers leaves up to 50 layers out of a single render; the template itself does not change. client.psd_mockups.layers() lists every layer of the template with its UUID, including the layers inside a smart object's contents. Hiding a group hides every layer inside it, a layer hidden inside a smart object is hidden in every copy of it, and hidden_layers works on its own.

tree = client.psd_mockups.layers("mockup-uuid")
badge = next(layer for layer in tree.layers if layer.name == "Sleeve badge")

render = client.renders.create(
    mockup_uuid="mockup-uuid",
    hidden_layers=[badge.uuid],
)

Async Usage

import asyncio
from sudomock import AsyncSudoMock

async def main():
    async with AsyncSudoMock(api_key="sm_your_api_key") as client:
        mockups = await client.psd_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())

Photo mockups via API

Create a photo 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. surfaces holds one entry per printable product in the photo, and one for the main wall of a room photo, each exposing a surface_uuid; print_areas holds the bounded zones somebody drew on those products. A product can have both, and they are separate render targets -- a saved print area does not close off the surface it sits on.

Placement follows the target, and sizing has exactly one answer per target. A surface takes coverage, a percentage from 10 to 100 that spans the whole surface when omitted. A print area takes a fit (contain, fill, cover), which meets its bounds when omitted. Either kind also takes an explicit width + height in pixels, for a box whose proportions a percentage or a fit cannot express. Send one way of sizing, not two, and give both axes or neither. Anchoring -- position, offset_x, offset_y, rotation -- belongs to both. Whatever you leave unset is left off the wire so the renderer applies its own default.

"tile": True in a placement repeats the artwork as a pattern across the whole target. The sizing answer (coverage, fit, or width and height) is one tile, and position with offset_x and offset_y sets where the grid starts. tile_spacing sets the gap between tiles, as on a PSD asset below. Each entry in mockup.surfaces carries a label, what the surface is, such as pillow or wall, so you can tell surfaces apart before you pick one.

To move a print area and keep its id, pass that area's print_area_id to client.photo_mockups.update_2d_print_areas with its new points. An area without one gets a new id, and an id that is not one of the mockup's print areas is refused with 422.

from sudomock import SudoMock

client = SudoMock(api_key="sm_your_api_key")

# Create the photo mockup (synchronous by default -- returns the finished mockup)
mockup = client.photo_mockups.create(
    source_url="https://example.com/product.jpg",
    name="Product Front",
    idempotency_key="product-front-001",
)

render = client.photo_mockups.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 product surface is rendered directly with its surface UUID.
if mockup.surfaces:
    render = client.photo_mockups.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.photo_mockups.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)

Async Rendering (Server-Side Queue)

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)

Video Rendering

Animate a mockup into a video. Video renders are always async (return a JobAccepted). Every account gets its first video render at no charge, once, for the lifetime of the account. 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,
)

Background Removal

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 5 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)              # public cutout URL, available for 7 days
print(cutout.width, cutout.height)
print(cutout.credits_charged)  # 5

# 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 photo mockup print area. It adds 5 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},
    }],
)

# Photo mockup render
client.photo_mockups.render(
    mockup_uuid="mockup-uuid",
    print_areas=[{
        "uuid": "print-area-uuid",
        "artwork_url": "https://example.com/photo.jpg",
        "remove_background": True,
    }],
)

Seam Check and Fix

Before you repeat an artwork as a pattern (tile: True), check that copies of the tile meet without a visible line. Seam check: free. Seam fix: 60 credits, refunded if the fix fails.

Both take exactly one of url or base64. Pass crop_aspect (width divided by height) when the render places the tile with fit "crop", so the check sees exactly the tile the render shows.

check = client.images.check_seams(url="https://example.com/pattern-tile.png")
print(check.status)        # "seamless" | "faint_seam" | "visible_seam"
print(check.seams)         # sides with a seam, each with pixel ranges
print(check.fixable, check.unfixable_reason)
print(check.previous_fix)  # your latest fix of this same tile, or None

if check.status == "visible_seam" and check.fixable:
    job = client.images.fix_seams(url="https://example.com/pattern-tile.png")
    done = client.jobs.wait(job.job_id)
    if done.succeeded:
        print(done.result_url)       # the fixed tile, same width and height
        print(done.alternative_url)  # a second fixed version, or None
        print(done.result_status)    # the fixed tile, checked again at no cost
        print(done.changes)          # "edges" | "whole_tile" | "rearranged"
        print(done.fine_detail)      # "unchanged" | "softer"
        print(done.changed_area)     # "part" | "whole"
    elif done.error_code == "TILE_NOT_FIXABLE":
        print(done.error_details.reason)  # "not_a_pattern_tile" | "no_clean_result"

A fix runs as a job of kind seam_fix; while it runs, client.jobs.get reports stage: queued, fixing or finishing. A tile with transparent edges, or one larger than a fix takes, is refused with a ValidationError and nothing is charged. Each call sends a fresh Idempotency-Key; pass idempotency_key to retry the same fix without paying twice. To be told when a fix ends instead of polling, subscribe a webhook endpoint to seam_fix.succeeded and seam_fix.failed; their payload carries result_url and alternative_url.

A fix that cannot make the tile repeat cleanly fails with error_code TILE_NOT_FIXABLE and is refunded. error_details.reason says why: not_a_pattern_tile when the image is not a repeating pattern tile, no_clean_result when no fixed version of the tile repeated cleanly.

Once a seam check has found a visible seam in a tile, a PSD or photo mockup render that repeats that tile with no gap between copies still completes, and its warnings carry the code TILE_SEAM_VISIBLE.

render = client.photo_mockups.render(
    mockup_uuid="mockup-uuid",
    print_areas=[{
        "uuid": "print-area-uuid",
        "artwork_url": "https://example.com/pattern-tile.png",
        "placement": {"fit": "fit", "tile": True},
    }],
)
for warning in render.warnings:
    if warning.code == "TILE_SEAM_VISIBLE":
        print(warning.message)

PSD Upload

Upload a PSD by URL and get back a reusable 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)

Folders

Folders keep a large library in a tree. PSD mockups and photo mockups share that tree, every mockup sits in exactly one folder or at the top of the library, and folders nest. Moving never changes a mockup's uuid, so renders, webhooks and integrations keep working against the uuid they already hold. Every call here is free (zero credits).

# "root" lists the folders at the top of the library, a uuid lists the ones
# inside that folder, and omitting it lists every folder you have
page = client.folders.list(parent_uuid="root")

folder = client.folders.create(name="Frames")
child = client.folders.create(name="8x10", parent_uuid=folder.uuid)

# The trail above a folder, and what it holds at any depth
detail = client.folders.get(folder.uuid)
print(detail.path, detail.total_psd_mockup_count)

# Rename, move, or both. parent_uuid=None is the top of the library, and an
# argument you do not pass is left alone
client.folders.update(child.uuid, name="Frames 8x10")
client.folders.update(child.uuid, parent_uuid=None)

# Any mix of mockups and folders, in one call. All of it moves or none does
client.folders.move(
    target_folder_uuid=folder.uuid,
    psd_mockup_uuids=["mockup-uuid"],
)

# An empty folder deletes on its own. A full one needs recursive=True, which
# also deletes the mockups inside it and cannot be undone
gone = client.folders.delete(folder.uuid, recursive=True)
print(gone.deleted_folders, gone.deleted_psd_mockups)

A name has to be unique among the folders beside it, whatever the capitalisation, so the same create call can safely be made twice. Read the folder first to tell someone what a recursive delete would remove: the total_ counts on get are exactly those numbers.

Both mockup lists take folder_uuid, and both create calls take it too:

in_folder = client.psd_mockups.list(folder_uuid=folder.uuid)
at_top = client.photo_mockups.list(folder_uuid="root")

client.psd.upload(url="https://example.com/t.psd", folder_uuid=folder.uuid)
client.psd_mockups.update("mockup-uuid", folder_uuid=folder.uuid)

Webhooks

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

# Photo-mockup events reach an endpoint in one spelling, its `event_naming`
# pin: "current" (photo_mockup.*, photo_mockup_render.*) or "legacy"
# (2d_mockup.*, 2d_render.*). A new endpoint is pinned to "current"; pass
# "legacy" for a handler that still reads the older names, and re-pin later.
print(ep.event_naming)  # "current"
client.webhook_endpoints.update(ep.id, event_naming="legacy")

# 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 signature

Error Handling

from 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})")

Studio

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 photo mockup sessions. PSD supports customize; photo mockup 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.

Account & Credits

from sudomock import SudoMock

client = SudoMock(api_key="sm_your_api_key")
account = client.account.get()

print(f"Plan: {account.subscription.plan}")
print(f"Funding: {account.usage.funding_summary()}")
print(f"Period ends: {account.subscription.current_period_end}")

An account can be funded two independent ways, so read both before you draw a conclusion. credits_limit is a subscription's allowance for the current billing period (a month, or a year on a yearly plan), and prepaid_balance is money the account holds and spends per render. An account paying as it goes has no allowance at all, so its three credits_* fields are legitimately 0 while it is fully able to pay. Printing only those renders it as 0 / 0, and a bar drawn from them sits at 0% forever.

usage = account.usage

if usage.credits_limit > 0:
    print(f"{usage.credits_remaining:,} of {usage.credits_limit:,} credits left")
if usage.prepaid_balance > 0:
    print(f"{usage.prepaid_balance:.2f} {usage.prepaid_balance_currency} balance")
if not usage.is_funded:
    print("No credits or balance. Add a credit card.")

usage.funding_summary() is the same logic in one line. Draw a progress bar only from credits_limit, never from prepaid_balance: a balance is an amount, not a fraction, so it has no denominator to be a percentage of.

Pricing in one paragraph

Subscriptions from $0.002 per render. Without one, $0.05 per render, the same rate standalone mockup APIs charge on a paid plan. The first payment into the balance is at least $5. Photo mockups and video are priced by what they cost to produce, not at the flat render rate. A new account gets 500 credits once, with no card required to spend them, but until a card is verified its renders are watermarked and capped at 1,024 px, it can keep 5 PSD templates, and one render runs at a time. Funding the $5 minimum lifts every one of those: the watermark and the width cap come off, the stored-template limit goes to 150, and renders run 25 at a time alongside 10 concurrent uploads. There is no separate "free plan": that account is on the pay-as-you-go tier, unfunded.

Configuration

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)
)

API Reference

Mockups

Method Description
client.psd_mockups.list(limit=, offset=, name=, created_after=, created_before=, sort=, order=, folder_uuid=) List mockup templates (filter by name, or by folder_uuid, a folder uuid or "root")
client.psd_mockups.get(uuid) Get mockup details
client.psd_mockups.layers(uuid) List every layer, nested as in Photoshop's Layers panel, with the UUIDs hidden_layers takes
client.psd_mockups.update(uuid, name=, folder_uuid=) Rename a mockup, or move it to another folder (folder_uuid=None is the top of the library)
client.psd_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.

Renders

Method Description
client.renders.create(mockup_uuid=, smart_objects=None, text_layers=None, hidden_layers=None, export_options=, export_label=, is_async=False) Render artwork, text replacements, or both, optionally leaving layers out (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)

Jobs

Method Description
client.jobs.list(kind=, mockup_uuid=, limit=, cursor=) List your async jobs (keyset-paginated, newest first). kind is a JobKind: render, video, upload, 2d_create, 2d_render, photo_mockup_create, photo_mockup_render, seam_fix; a photo-mockup kind selects both spellings of that job
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

PSD

Method Description
client.psd.upload(url=, name=None, folder_uuid=None, is_async=False) Upload a PSD by URL (free; sync Mockup or JobAccepted)

Photo Mockups

Method Description
client.photo_mockups.create(source_url=, source_base64=, name=, print_areas=, folder_uuid=, is_async=False, idempotency_key=) Create a photo mockup (25 credits; sync PhotoMockup by default, or JobAccepted when is_async=True)
client.photo_mockups.wait_for_2d_mockup(job_id, poll_interval=2.0, timeout=180.0) Wait for an is_async=True creation and return the full photo mockup (accepts a job of kind 2d_create or photo_mockup_create)
client.photo_mockups.update_2d_print_areas(mockup_id, print_areas) Replace a photo mockup's print areas (free); an area with print_area_id keeps that id
client.photo_mockups.render(mockup_uuid=, print_areas=, export_options=, is_async=False) Render artwork onto a photo mockup (5 credits; sync PhotoMockupRender with render_uuid and warnings by default, or JobAccepted when is_async=True)
client.photo_mockups.list(limit=, offset=, customizable_only=, folder_uuid=) List your photo mockups; set customizable_only=True for shopper-ready items, or folder_uuid for one folder
client.photo_mockups.get(mockup_id) Get a photo mockup
client.photo_mockups.delete(mockup_id) Delete a photo mockup

Folders

Method Description
client.folders.list(parent_uuid=, limit=, offset=) List folders by name; parent_uuid takes a folder uuid or "root" (free)
client.folders.create(name=, parent_uuid=) Create a folder (free)
client.folders.get(uuid) Get a folder with path and the total_ counts a recursive delete would remove (free)
client.folders.update(uuid, name=, parent_uuid=) Rename or move a folder; parent_uuid=None is the top of the library (free)
client.folders.delete(uuid, recursive=False) Delete a folder; recursive=True also deletes everything inside it and cannot be undone (free)
client.folders.move(target_folder_uuid=, psd_mockup_uuids=, photo_mockup_uuids=, folder_uuids=) Move any mix of mockups and folders in one call, up to 500 ids per list (free)

Images

Method Description
client.images.remove_background(url=, base64=, content_type=) Remove an image's background (5 credits; returns a BackgroundRemoval with a public transparent-PNG cutout URL that stays available for 7 days)
client.images.check_seams(url=, base64=, content_type=, crop_aspect=) Check a pattern tile for seams (free; returns a SeamCheck with status, seams, fixable and previous_fix)
client.images.fix_seams(url=, base64=, content_type=, crop_aspect=, idempotency_key=) Fix the seams of a pattern tile (60 credits, refunded if the fix fails; returns a JobAccepted of kind seam_fix)

Account

Method Description
client.account.get() Get account info, credits, subscription

Packages (public)

Method Description
client.packages.plans() List active subscription plans (no auth)
client.packages.pricing() List public pricing (no auth)

Webhook Endpoints

Method Description
client.webhook_endpoints.list() List registered endpoints
client.webhook_endpoints.create(url=, events=, description=None, event_naming=None) Register an endpoint (empty events = all; event_naming "current" / "legacy"; left out, the API follows the spelling of events)
client.webhook_endpoints.get(uuid) Get an endpoint
client.webhook_endpoints.update(uuid, url=, events=, description=, enabled=, event_naming=) Update an endpoint (event_naming re-pins it to "current" or "legacy")
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

export_options = {
    "image_format": "webp",  # "webp", "png", "jpg"
    "image_size": 1920,       # output width in pixels (100-10000), or "original"
    "quality": 95,            # 1-100 (for webp/jpg)
}

Smart Object Configuration

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 (+5 credits per artwork)
        "tile": False,       # True repeats the artwork as a pattern; size is one tile
        "tile_spacing": 0,   # gap between tiles, 0 to 100 percent of the tile; only with tile
    },
    "color": {
        "hex": "#FFFFFF",
        "blending_mode": "multiply",
    },
}]

With "tile": True, size is one tile, fit places the artwork inside each tile, and position sets where the grid starts. A tile needs a size; without one the API answers 422. tile_spacing is a percentage of the tile size, from 0 to 100: the horizontal gap is that percent of the tile width, the vertical gap that percent of the tile height. It is used only when tile is True. The render mode of client.renders.create_video takes the same keys on its smart objects.

Requirements

License

MIT -- see LICENSE.

MCP Server

SudoMock also offers an official Model Context Protocol (MCP) server, enabling AI assistants like Claude, Cursor, and VS Code Copilot to generate mockups directly.

Metadata

Release files for sudomock 0.20.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sudomock 0.20.0
File Size Uploaded Provenance Actions
sudomock-0.20.0.tar.gz 67.4 kB With provenance Details for sudomock-0.20.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sudomock 0.20.0
File Interpreter ABI Platform Actions
sudomock-0.20.0-py3-none-any.whl Python 3 none any Details for sudomock-0.20.0-py3-none-any.whl

Total release size: 131.3 kB

Release files / sudomock-0.20.0.tar.gz

Download URL sudomock-0.20.0.tar.gz
Size 67.4 kB
Tags Source
SHA-256 checksum
How to use checksums
6aa064c79a76a689bd09bb1ac8c2b6ec3b6e32a3ee24f6e300ef0f3af9b6a661
BLAKE2b-256 checksum
How to use checksums
6e4816561cd7603eebbbf7fe91dc6cb45d79641e6e237784d8a1b9ac01294217
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Release files / sudomock-0.20.0-py3-none-any.whl

Download URL sudomock-0.20.0-py3-none-any.whl
Size 63.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f79efacffc8b397091f53c0967e71964077ecc9db6e50d5e8fb7b75d7417f657
BLAKE2b-256 checksum
How to use checksums
6ed29730742596af4cb53468112e6c2ba366c028205b8b3ebee78842b4aea99e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Security for sudomock 0.20.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sudomock-0.20.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page