> ## Documentation Index
> Fetch the complete documentation index at: https://sudomock.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The base URL is https://api.sudomock.com.
> Authenticate every request with the x-api-key header. Keys begin with sm_.
> A render returns the finished image at data.print_files[0].export_path. A request sent with is_async true returns a job_id to poll at GET /api/v1/jobs/{job_id}.
> Prefer the official SDKs over hand-written HTTP calls: npm install sudomock for Node, pip install sudomock for Python.

# Render product mockups with Python

> Upload a PSD and render artwork from a Python script.

<Prompt description="Use this pre-built prompt to get started faster." icon="microchip" iconType="solid" actions={["copy", "cursor"]}>
  # Render mockups with the SudoMock Python SDK

  **Purpose:** enforce only the current and correct instructions for rendering
  mockups with the SudoMock Python SDK.

  **Scope:** all AI generated advice or code that renders a SudoMock mockup from
  Python follows these guardrails.

  ***

  ## 1. Setup

  ### Prerequisites

  The human creates an API key at
  [https://sudomock.com/dashboard/api-keys](https://sudomock.com/dashboard/api-keys)
  and has a PSD reachable at a public URL. Python 3.9 or newer is required.

  The key is stored in an environment variable called `SUDOMOCK_API_KEY`. Keys
  begin with `sm_`.

  ### Install the SDK

  ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
  pip install sudomock
  ```

  ### Build the client

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  import os

  from sudomock import SudoMock

  client = SudoMock(api_key=os.environ["SUDOMOCK_API_KEY"])
  ```

  The constructor takes keyword arguments only. `SudoMock()` with no argument
  reads `SUDOMOCK_API_KEY` from the environment itself, which is the shorter
  form when a process already has it. The client sets the `x-api-key` header,
  retries a rate limit and a server error a couple of times, and parses each
  answer into a typed object.

  ### Render a mockup

  The work is two calls. Upload the PSD once, then render it as often as you
  like.

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  mockup = client.psd.upload(
      url="https://example.com/heavyweight-tee.psd",
      name="Heavyweight tee front",
  )

  render = client.renders.create(
      mockup_uuid=mockup.uuid,
      smart_objects=[
          {
              "uuid": mockup.smart_objects[0].uuid,
              "asset": {
                  "url": "https://example.com/artwork.png",
                  "fit": "crop",
              },
          }
      ],
      export_options={"image_format": "webp", "image_size": 2048},
  )

  print(render.url)
  ```

  `client.psd.upload()` parses the file and returns a mockup carrying `uuid`,
  `name`, `width`, `height` and `smart_objects`, each smart object with its own
  `uuid` and `name`. Take smart object UUIDs from that response, never from a
  guess.

  ***

  ## 2. Complete `renders.create()` parameter reference

  ### Required parameters

  | Parameter       | Type         | Description                                                                                    |
  | --------------- | ------------ | ---------------------------------------------------------------------------------------------- |
  | `mockup_uuid`   | `str`        | UUID of the mockup to render, from the upload response.                                        |
  | `smart_objects` | `list[dict]` | The smart objects and the artwork that goes into each. Required unless `text_layers` is given. |

  ### Smart object entry

  | Field               | Type   | Description                                                                 |
  | ------------------- | ------ | --------------------------------------------------------------------------- |
  | `uuid`              | `str`  | UUID of the smart object, from the upload response.                         |
  | `asset`             | `dict` | The artwork to place. Fields below.                                         |
  | `color`             | `dict` | Colour overlay: `hex` or a saved `label`, plus an optional `blending_mode`. |
  | `adjustment_layers` | `dict` | `brightness`, `contrast`, `opacity`, `saturation`, `vibrance` and `blur`.   |

  ### Asset fields

  | Field               | Type    | Description                                                        |
  | ------------------- | ------- | ------------------------------------------------------------------ |
  | `url`               | `str`   | Public URL of the artwork. Either `url` or `base64`.               |
  | `base64`            | `str`   | Raw base64 image bytes with no `data:` prefix.                     |
  | `content_type`      | `str`   | MIME type when `base64` is used. Defaults to `image/png`.          |
  | `fit`               | `str`   | `fit`, `fill` or `crop`. Defaults to `fit`.                        |
  | `rotate`            | `float` | Degrees, clockwise positive, from -360 to 360.                     |
  | `size`              | `dict`  | `width` and `height` in pixels.                                    |
  | `position`          | `dict`  | `top` and `left` in pixels.                                        |
  | `flip_horizontal`   | `bool`  | Mirror the artwork left to right.                                  |
  | `flip_vertical`     | `bool`  | Mirror the artwork top to bottom.                                  |
  | `remove_background` | `bool`  | Isolate the subject before placing it. Charged per unique artwork. |

  `fit` scales the artwork until it fits inside the area and keeps its
  proportions. `crop` covers the area and cuts the overflow, also keeping
  proportions. `fill` stretches it to the bounds and does not keep them.

  ### Optional parameters

  | Parameter        | Type         | Description                                                  |
  | ---------------- | ------------ | ------------------------------------------------------------ |
  | `text_layers`    | `list[dict]` | Up to 50 text layer overrides, addressed by layer UUID.      |
  | `export_options` | `dict`       | Format, width and quality. Fields below.                     |
  | `export_label`   | `str`        | Label for the export filename, up to 100 characters.         |
  | `is_async`       | `bool`       | Return a job straight away instead of waiting for the image. |

  ### Export options

  | Field          | Type  | Description                                                               |
  | -------------- | ----- | ------------------------------------------------------------------------- |
  | `image_format` | `str` | `webp`, `png` or `jpg`. Defaults to `webp`.                               |
  | `image_size`   | `int` | Output width in pixels, 100 to 10000. Defaults to 2048.                   |
  | `quality`      | `int` | 1 to 100, for `jpg` and `webp`. Defaults to 90. `png` is always lossless. |
  | `dpi`          | `int` | 72 to 2400, a metadata tag that does not change pixels.                   |

  ### Response

  A successful call returns a `Render`:

  | Attribute     | Type   | Description                                                                      |
  | ------------- | ------ | -------------------------------------------------------------------------------- |
  | `url`         | `str`  | The first finished file, which is what a single smart object render produces.    |
  | `print_files` | `list` | Every finished file, each with its own `url`.                                    |
  | `warnings`    | `list` | Advisories with `code` and `message`. A render can succeed and still carry them. |

  A failed call raises a `SudoMockError` subclass. It does not return an error
  object.

  ***

  ## 3. Errors

  Each failure is its own exception, all importable from `sudomock`.

  | Exception                  | Meaning                                                        |
  | -------------------------- | -------------------------------------------------------------- |
  | `AuthenticationError`      | Key missing, revoked or malformed.                             |
  | `ValidationError`          | The body was rejected. Read `exc.message`.                     |
  | `InsufficientCreditsError` | No credits left. `exc.credits_reset_at` says when they return. |
  | `RateLimitError`           | Too many requests. `exc.retry_after` is the seconds to wait.   |
  | `NotFoundError`            | The UUID does not belong to this account.                      |
  | `ServerError`              | The call is safe to send again.                                |
  | `JobFailedError`           | A background job ended in failure. Carries `error_code`.       |
  | `JobTimeoutError`          | A waited-on job outlived the wait. Also a `TimeoutError`.      |

  The base class is `SudoMockError`, carrying `message`, `status_code` and
  `error_code`.

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  from sudomock import (
      AuthenticationError,
      InsufficientCreditsError,
      RateLimitError,
      ValidationError,
  )

  try:
      render = client.renders.create(...)
  except ValidationError as exc:
      raise SystemExit(f"Body rejected: {exc.message}")
  except AuthenticationError:
      raise SystemExit("Key missing, revoked or malformed.")
  except InsufficientCreditsError as exc:
      raise SystemExit(f"No credits until {exc.credits_reset_at}.")
  except RateLimitError as exc:
      raise SystemExit(f"Rate limited. Wait {exc.retry_after}s.")

  for warning in render.warnings:
      print(warning.code, warning.message)
  ```

  The client already retries a rate limit and a server error a couple of times,
  so a `RateLimitError` that reaches your code is a signal to slow the whole run
  down. Every `error_code`, and which failures are safe to send again, is listed
  at [https://sudomock.com/docs/errors](https://sudomock.com/docs/errors).

  ***

  ## 4. Background renders

  A catalogue run does not need to hold a connection open per image. Submit the
  render, keep the job id, and collect the result when it is done.

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  job = client.renders.create(
      mockup_uuid=mockup.uuid,
      smart_objects=[
          {
              "uuid": mockup.smart_objects[0].uuid,
              "asset": {"url": "https://example.com/artwork.png"},
          }
      ],
      is_async=True,
  )

  finished = client.jobs.wait(job.job_id, timeout=300)

  print(finished.status, finished.result_url)
  ```

  `jobs.wait()` asks every 2 seconds by default and raises `TimeoutError`
  when the timeout passes. Point a webhook endpoint at your service and the
  result arrives without asking at all.

  ***

  ## 5. Async code

  Every resource has an async twin. Use `AsyncSudoMock` inside `async def` code.

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  from sudomock import AsyncSudoMock

  async with AsyncSudoMock(api_key=os.environ["SUDOMOCK_API_KEY"]) as client:
      render = await client.renders.create(...)
  ```

  ***

  ## 6. Critical instructions for AI models

  ### 6.1 Always do the following

  1. Read the key from the environment.
  2. Upload a PSD once and reuse its `uuid` for every render after it. Store
     that `uuid` and the smart object UUID next to the product they describe.
  3. Take smart object UUIDs from the upload response.
  4. Catch `SudoMockError` or a specific subclass. The SDK raises on failure, it
     does not return an error object.
  5. Use `snake_case` for parameters, not camelCase.
  6. Use `AsyncSudoMock` instead of `SudoMock` inside async code.
  7. Print `render.warnings` so advisories are not swallowed.
  8. Check the project for an existing package manager, pip, poetry or uv, and
     install with that one.

  ### 6.2 Never do the following

  1. Do not put the key in source, in committed configuration or in anything
     that ships to a browser.
  2. Always send the key in `x-api-key`. That is the header the API reads
     and the key begins with `sm_`.
  3. Do not invent a request field. Every accepted field is in the spec at
     [https://assets.sudomock.com/openapi.json](https://assets.sudomock.com/openapi.json).
  4. Do not upload the same PSD again for a second render.
  5. Do not hold a request open for a catalogue run. Use `is_async=True`.
  6. Do not put the upload in a render loop. It belongs in setup.

  ***

  ## 7. Verification steps

  Before returning any SudoMock related solution, verify:

  1. **Key:** is the key read from `SUDOMOCK_API_KEY`?
  2. **Reachability:** does `client.account.get()` answer?
  3. **Render:** does one render return a URL that opens the finished image?
  4. **Parameters:** is every parameter name in `snake_case` and present in the
     spec?
  5. **Errors:** is the call wrapped in `try` and `except` catching
     `SudoMockError` or a subclass?

  If any check fails, stop and revise until it passes.
</Prompt>

## Prerequisites

Before you start, you'll need:

* A SudoMock [API key](/docs/dashboard/api-keys)
* A [PSD reachable over HTTPS](/docs/psd-mockups/preparing-a-psd)

## Guide

<Steps>
  <Step title="Install">
    Get the SudoMock Python SDK.

    <CodeGroup>
      ```bash pip theme={"theme":{"light":"github-light","dark":"vesper"}}
      pip install sudomock
      ```

      ```bash uv theme={"theme":{"light":"github-light","dark":"vesper"}}
      uv add sudomock
      ```

      ```bash poetry theme={"theme":{"light":"github-light","dark":"vesper"}}
      poetry add sudomock
      ```
    </CodeGroup>
  </Step>

  <Step title="Set your API key">
    Store your API key in an environment variable in your `.env` file.

    ```sh .env theme={"theme":{"light":"github-light","dark":"vesper"}}
    SUDOMOCK_API_KEY=sm_xxxxxxxxx
    ```

    Read it with `os.environ["SUDOMOCK_API_KEY"]`. See [API keys](/docs/dashboard/api-keys)
    for the full setup.
  </Step>

  <Step title="Render a PSD mockup">
    Upload the template once, then render it as often as you like.

    ```py render.py theme={"theme":{"light":"github-light","dark":"vesper"}}
    import os

    from sudomock import SudoMock

    client = SudoMock(api_key=os.environ["SUDOMOCK_API_KEY"])

    mockup = client.psd.upload(
        url="https://example.com/heavyweight-tee.psd",
        name="Heavyweight tee front",
    )

    render = client.renders.create(
        mockup_uuid=mockup.uuid,
        smart_objects=[
            {
                "uuid": mockup.smart_objects[0].uuid,
                "asset": {
                    "url": "https://example.com/artwork.png",
                    "fit": "crop",
                },
            }
        ],
        export_options={"image_format": "webp", "image_size": 2048},
    )

    print(render.url)
    ```
  </Step>
</Steps>

## Next steps

<CardGroup cols={3}>
  <Card title="Upload a PSD" icon="cloud-arrow-up" href="/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd">
    The call behind `psd.upload`
  </Card>

  <Card title="Render a PSD mockup" icon="image" href="/docs/api-reference/psd-mockups/render-a-psd-mockup">
    Every field `renders.create` accepts
  </Card>

  <Card title="Jobs" icon="clock" href="/docs/api-reference/jobs/retrieve-a-single-job">
    Collect a background render
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/webhooks/overview">
    Get called back when a render finishes
  </Card>

  <Card title="Error codes" icon="triangle-exclamation" href="/docs/errors">
    Every error code and what to do
  </Card>

  <Card title="Fit and blend modes" icon="crop" href="/docs/concepts/fit-and-blend-modes">
    What fit, fill and crop do
  </Card>

  <Card title="Photo mockups" icon="camera" href="/docs/photo-mockups/overview">
    Render artwork onto a photograph
  </Card>

  <Card title="Python SDK source" icon="github" href="https://github.com/sudomock/sudomock-python">
    github.com/sudomock/sudomock-python
  </Card>
</CardGroup>
