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

> Call the mockup API from a Django view with the Python SDK.

<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](https://sudomock.com) Python SDK.

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

  ## 1. Setup

  ### Prerequisites

  The human creates an API key at
  [sudomock.com/dashboard/api-keys](https://sudomock.com/dashboard/api-keys)
  and stores it in an environment variable called `SUDOMOCK_API_KEY`. Keys
  begin with `sm_`. The client needs Python 3.9 or later.

  ### 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 base URL is `https://api.sudomock.com`. The key travels in the
  `x-api-key` header and the client sets that header itself. `SudoMock()`
  with no argument reads `SUDOMOCK_API_KEY` on its own. Build one client
  per process and import it where it is needed.

  ### Upload a template once, render it many times

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

  print(mockup.uuid)

  for layer in mockup.smart_objects:
      print(layer.uuid, layer.name)
  ```

  An upload returns `.uuid` and a `.smart_objects` list whose entries carry
  `.uuid` and `.name`. Store both uuids next to the product they describe.
  Uploading is setup work, not request work.

  ### Render

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  render = client.renders.create(
      mockup_uuid=MOCKUP_UUID,
      smart_objects=[
          {
              "uuid": SMART_OBJECT_UUID,
              "asset": {
                  "url": "https://example.com/artwork.png",
                  "fit": "crop",
              },
          }
      ],
      export_options={
          "image_format": "webp",
          "image_size": 2048,
          "quality": 90,
      },
  )

  print(render.url)
  ```

  The result carries `.url`, the finished image, and `.print_files`, one
  entry per rendered smart object. A single smart object therefore answers
  with a single entry, and `.url` is the shortcut to it. `.warnings` carries
  advisories that a successful render still reports.

  ### Error handling

  `client.renders.create()` raises on failure rather than returning an error
  object. Catch `SudoMockError` or one of its subclasses, all importable
  from `sudomock`:

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

  try:
      render = client.renders.create(...)
  except ValidationError as error:
      ...  # fix the request body
  except AuthenticationError:
      ...  # key missing, revoked or malformed
  except InsufficientCreditsError as error:
      ...  # error.credits_reset_at
  except RateLimitError as error:
      ...  # error.retry_after, then slow the run down
  except SudoMockError as error:
      ...  # error.message, error.error_code, error.status_code
  ```

  The client has already retried a rate limit and a server error by the time
  the exception reaches your code.

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

  | Parameter        | Type   | Description                           |
  | ---------------- | ------ | ------------------------------------- |
  | `mockup_uuid`    | `str`  | Required. From the upload response.   |
  | `smart_objects`  | `list` | One entry per layer you fill.         |
  | `text_layers`    | `list` | Up to 50 text overrides.              |
  | `group_layers`   | `list` | Up to 50 group outline overrides.     |
  | `export_options` | `dict` | Format, width and quality.            |
  | `export_label`   | `str`  | Names the exported file. 100 chars.   |
  | `is_async`       | `bool` | `True` answers with a job id at once. |

  A `smart_objects` entry takes `uuid` and an `asset`:

  | Field          | Type    | Description                             |
  | -------------- | ------- | --------------------------------------- |
  | `url`          | `str`   | Public URL of the artwork.              |
  | `base64`       | `str`   | Raw bytes instead of a URL.             |
  | `content_type` | `str`   | Needed with `base64`.                   |
  | `fit`          | `str`   | `fill`, `fit` or `crop`. Default `fit`. |
  | `rotate`       | `float` | Degrees, clockwise positive.            |

  `export_options` takes `image_format` of `png`, `jpg` or `webp`,
  `image_size` as a width from 100 to 10000, `quality` from 1 to 100, and
  `dpi` as a metadata tag.

  ## 3. Background renders

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  job = client.renders.create(
      mockup_uuid=MOCKUP_UUID,
      smart_objects=[...],
      is_async=True,
  )

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

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

  `is_async=True` answers with a job id at once instead of holding the
  request open. A registered webhook endpoint removes the wait entirely.

  ## 4. Critical instructions for AI models

  ### 4.1 Always do the following

  * Read the key from the environment, and in Django through
    `django.conf.settings`.
  * Upload a PSD once and reuse its uuid for every render after it.
  * Take smart object uuids from the upload response.
  * Catch `SudoMockError` and answer with `.message`, `.error_code` and
    `.status_code`.
  * Print `.warnings` while building so advisories are not swallowed.
  * Pass `is_async=True` when a render must not hold a request open.

  ### 4.2 Never do the following

  * Never hardcode a key in source, in a settings default or in a committed
    file.
  * Never put the key in anything a visitor downloads.
  * Always send the key in `x-api-key`. That is the header this API reads.
  * Never invent a request field. The accepted set is section 2.
  * Never call the upload endpoint from a path a visitor can reach.

  ## 5. Common patterns

  ### One client module

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  # mockups/client.py
  from django.conf import settings
  from sudomock import SudoMock

  client = SudoMock(api_key=settings.SUDOMOCK_API_KEY)
  ```

  ### A catalogue run

  ```python theme={"theme":{"light":"github-light","dark":"vesper"}}
  job_ids = []

  for artwork_url in artwork_urls:
      job = client.renders.create(
          mockup_uuid=MOCKUP_UUID,
          smart_objects=[
              {
                  "uuid": SMART_OBJECT_UUID,
                  "asset": {"url": artwork_url, "fit": "crop"},
              }
          ],
          is_async=True,
      )
      job_ids.append(job.job_id)
  ```

  ## 6. AI model verification steps

  1. `python manage.py check` passes.
  2. `client.account.get()` answers for a working key.
  3. One render returns a URL that opens the finished image.
  4. A wrong `mockup_uuid` raises `ValidationError` and the handler reports
     its `error_code`.
</Prompt>

## Prerequisites

* An [API key](/docs/dashboard/api-keys), which begins with `sm_`
* A [PSD at a public URL](/docs/psd-mockups/preparing-a-psd)
* A Django app you can add a view and a management command to

## Guide

<Steps>
  <Step title="Install">
    Add the Python client to the project.

    <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="Configure the project">
    The key and the two uuids from the next step live in the environment, so a
    missing value stops the project at startup rather than on the first render.

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

    SUDOMOCK_API_KEY = os.environ["SUDOMOCK_API_KEY"]
    SUDOMOCK_MOCKUP_UUID = os.environ["SUDOMOCK_MOCKUP_UUID"]
    SUDOMOCK_SMART_OBJECT = os.environ["SUDOMOCK_SMART_OBJECT"]
    ```

    Build the client once and import it where a view needs it.

    ```python mockups/client.py theme={"theme":{"light":"github-light","dark":"vesper"}}
    from django.conf import settings
    from sudomock import SudoMock

    client = SudoMock(api_key=settings.SUDOMOCK_API_KEY)
    ```
  </Step>

  <Step title="Upload the template once">
    A PSD is uploaded once and rendered many times, so this belongs in a
    management command rather than in request handling.

    ```python mockups/management/commands/upload_mockup.py theme={"theme":{"light":"github-light","dark":"vesper"}}
    from django.core.management.base import BaseCommand

    from mockups.client import client


    class Command(BaseCommand):
        help = "Upload a PSD and print the uuids a render needs."

        def add_arguments(self, parser):
            parser.add_argument("psd_url")
            parser.add_argument("name")

        def handle(self, *args, **options):
            mockup = client.psd.upload(
                url=options["psd_url"],
                name=options["name"],
            )
            self.stdout.write(f"mockup: {mockup.uuid}")
            for layer in mockup.smart_objects:
                self.stdout.write(
                    f"smart object {layer.name}: {layer.uuid}"
                )
    ```

    Run it once, and keep the two uuids it prints as
    `SUDOMOCK_MOCKUP_UUID` and `SUDOMOCK_SMART_OBJECT`.

    ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
    python manage.py upload_mockup \
      https://example.com/heavyweight-tee.psd \
      "Heavyweight tee front"
    ```

    ```text theme={"theme":{"light":"github-light","dark":"vesper"}}
    mockup: 8f2c1d90-6b4e-4a37-9e55-0d1c7a3f21b8
    smart object Front print: b41a7e52-93c8-4d61-8f07-2ae5c9d04713
    ```
  </Step>

  <Step title="Render from a view">
    The view takes an artwork URL, fills the smart object with it, and answers
    with the finished image.

    ```python mockups/views.py theme={"theme":{"light":"github-light","dark":"vesper"}}
    import json

    from django.conf import settings
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.views.decorators.http import require_POST
    from sudomock import SudoMockError

    from mockups.client import client


    @csrf_exempt
    @require_POST
    def render_mockup(request):
        artwork_url = json.loads(request.body)["artwork_url"]

        try:
            render = client.renders.create(
                mockup_uuid=settings.SUDOMOCK_MOCKUP_UUID,
                smart_objects=[
                    {
                        "uuid": settings.SUDOMOCK_SMART_OBJECT,
                        "asset": {"url": artwork_url, "fit": "crop"},
                    }
                ],
                export_options={
                    "image_format": "webp",
                    "image_size": 1920,
                    "quality": 90,
                },
            )
        except SudoMockError as error:
            return JsonResponse(
                {"error": error.message, "code": error.error_code},
                status=error.status_code or 502,
            )

        return JsonResponse({"url": render.url})
    ```

    `csrf_exempt` suits a route your own backend calls; a route a browser form
    posts to keeps the token instead. Wire the view into `urls.py` and post an
    artwork URL to it.
  </Step>
</Steps>

## Next steps

<CardGroup cols={3}>
  <Card title="Render a PSD mockup" icon="image" href="/docs/api-reference/psd-mockups/render-a-psd-mockup">
    Every field the render call accepts.
  </Card>

  <Card title="Create a mockup from a PSD" icon="upload" href="/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd">
    What an upload answers with.
  </Card>

  <Card title="Retrieve a single job" icon="clock" href="/docs/api-reference/jobs/retrieve-a-single-job">
    Collect a background render by its job id.
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/docs/errors">
    Each `error_code`, and which statuses are worth retrying.
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/webhooks/overview">
    Let a finished render call your Django route back.
  </Card>

  <Card title="SDKs" icon="package" href="/docs/sdks">
    Every resource the Python client exposes, and the Node one.
  </Card>
</CardGroup>
