> ## 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 mockups with Ruby on Rails

> Upload a PSD and render artwork from a Rails controller.

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

  **Purpose:** hold a coding agent to the current and correct way of
  rendering a SudoMock mockup from a Ruby on Rails application.

  ## Setup

  Base URL is `https://api.sudomock.com`. There is no gem to install:
  `net/http` and `json` from the standard library are enough.

  Every request carries the header `x-api-key` with a key that starts with
  `sm_`. Read it from
  `Rails.application.credentials.dig(:sudomock, :api_key)` and fall back to
  `ENV["SUDOMOCK_API_KEY"]`.

  Put the client in `app/services` and call it from a controller or from an
  Active Job worker.

  ## Two calls produce an image

  ### `POST /api/v1/psd/upload`

  | Field          | Type   | Notes                             |
  | -------------- | ------ | --------------------------------- |
  | `psd_file_url` | string | Public URL of the PSD. Required.  |
  | `psd_name`     | string | Label for the template. Optional. |

  Keep `data.uuid` as the mockup UUID and every `data.smart_objects[].uuid`.
  Run this once per template, from a rake task or a console, never inside a
  web request.

  ### `POST /api/v1/renders`

  | Field                          | Type    | Notes                                 |
  | ------------------------------ | ------- | ------------------------------------- |
  | `mockup_uuid`                  | string  | From the upload response. Required.   |
  | `smart_objects[].uuid`         | string  | The slot being filled. Required.      |
  | `smart_objects[].asset.url`    | string  | Artwork over HTTPS.                   |
  | `smart_objects[].asset.base64` | string  | Artwork bytes, no prefix.             |
  | `smart_objects[].asset.fit`    | string  | `fit`, `fill` or `crop`.              |
  | `export_options.image_format`  | string  | `webp`, `png` or `jpg`.               |
  | `export_options.image_size`    | integer | Output width, 100 to 10000.           |
  | `export_options.quality`       | integer | 1 to 100, ignored for `png`.          |
  | `is_async`                     | boolean | `true` answers `202` with a `job_id`. |

  Give the asset either `url` or `base64`, never both. The finished image
  is at `data.print_files[0].export_path`.

  ## Always do

  * Send `x-api-key` on every request.
  * Persist the mockup UUID and the smart object UUID. They stay valid
    across renders.
  * Raise on any non 2xx response and surface `error_code` from the body.
  * Retry a `429` after waiting the seconds named in `Retry-After`, and
    retry `500`, `502`, `503` and `504` with backoff.
  * For a long render, set `is_async` to `true`, read `job_id` from the
    `202`, and poll `GET /api/v1/jobs/{job_id}` from an Active Job worker.

  ## Never do

  * Always send the key in `x-api-key`. That is the header this API reads.
  * Never hardcode a key, log it, or expose it to the browser.
  * Never invent a field. Send only what
    `https://assets.sudomock.com/openapi.json` lists.
  * Never re-upload the PSD on every render.
  * Never retry a `400`, `401`, `402`, `403`, `404` or `422`. Fix the
    request.

  ## Verify

  * `GET /api/v1/me` with the key returns `200`.
  * The upload response lists one entry per smart object in the PSD.
  * A render response contains `data.print_files[0].export_path`.
</Prompt>

## Prerequisites

* An API key. [Create one](/docs/dashboard/api-keys) and keep the `sm_` value.
* A PSD with at least one visible smart object, at a public URL.
  [Preparing a PSD](/docs/psd-mockups/preparing-a-psd) covers what it needs.

## Guide

<Steps>
  <Step title="Add a client">
    There is no gem to install. Open Rails credentials with
    `bin/rails credentials:edit`, put the key there, then let one service
    object carry the header, the timeout and the failure.

    ```yaml config/credentials.yml.enc theme={"theme":{"light":"github-light","dark":"vesper"}}
    sudomock:
      api_key: sm_your_api_key
    ```

    ```ruby app/services/sudomock.rb theme={"theme":{"light":"github-light","dark":"vesper"}}
    require "net/http"
    require "json"

    module Sudomock
      BASE = "https://api.sudomock.com"

      class Error < StandardError
        attr_reader :status, :code

        def initialize(status, body)
          @status = status
          @code = body["error_code"]
          reason = body["message"] || body["detail"]
          super(reason || "Request failed")
        end
      end

      def self.api_key
        Rails.application.credentials.dig(:sudomock, :api_key) ||
          ENV.fetch("SUDOMOCK_API_KEY")
      end

      def self.post(path, payload)
        uri = URI("#{BASE}#{path}")
        request = Net::HTTP::Post.new(uri)
        request["x-api-key"] = api_key
        request["Content-Type"] = "application/json"
        request.body = JSON.generate(payload)

        response = Net::HTTP.start(
          uri.host, uri.port, use_ssl: true, read_timeout: 120
        ) { |http| http.request(request) }

        body = JSON.parse(response.body)
        return body if response.is_a?(Net::HTTPSuccess)

        raise Error.new(response.code.to_i, body)
      end
    end
    ```

    A failed call still answers with a JSON body, and `error_code` is what
    you branch on. [Errors](/docs/errors) lists the codes and says which ones
    are worth retrying.
  </Step>

  <Step title="Upload once, then render from a controller">
    Upload the PSD once to learn the mockup UUID and the name of every slot
    inside it. This belongs in a rake task, not in a request, and
    [Upload a PSD](/docs/psd-mockups/upload-a-psd) covers every field it takes.

    ```ruby lib/tasks/sudomock.rake theme={"theme":{"light":"github-light","dark":"vesper"}}
    namespace :sudomock do
      desc "Register a PSD template and print its UUIDs"
      task :upload, [:url, :name] => :environment do |_task, args|
        result = Sudomock.post("/api/v1/psd/upload", {
          psd_file_url: args[:url],
          psd_name: args[:name]
        })

        data = result["data"]
        puts "mockup_uuid: #{data['uuid']}"
        data["smart_objects"].each do |object|
          puts "  #{object['name']}: #{object['uuid']}"
        end
      end
    end
    ```

    ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
    bin/rails "sudomock:upload[https://example.com/tee.psd,Tee]"
    ```

    Those UUIDs stay valid for every later render, so they belong in
    credentials, in the environment, or on the product row they describe.
    The render call takes them with the artwork and answers with the
    finished file. Wire the action into `config/routes.rb` and post the
    artwork URL to it.

    ```ruby app/controllers/mockups_controller.rb theme={"theme":{"light":"github-light","dark":"vesper"}}
    class MockupsController < ApplicationController
      def create
        result = Sudomock.post("/api/v1/renders", {
          mockup_uuid: ENV.fetch("SUDOMOCK_MOCKUP_UUID"),
          smart_objects: [{
            uuid: ENV.fetch("SUDOMOCK_SMART_OBJECT_UUID"),
            asset: { url: params.require(:artwork_url), fit: "crop" }
          }],
          export_options: { image_format: "webp", image_size: 2048 }
        })

        files = result.dig("data", "print_files")
        render json: { image_url: files.first["export_path"] }
      rescue Sudomock::Error => error
        render json: { code: error.code, message: error.message },
               status: error.status
      end
    end
    ```

    One render answers with one image, so `print_files` carries a single
    entry whatever number of layers the request filled. A long render does
    not have to hold the connection open: set `is_async` to `true`, read
    `job_id` from the `202`, and follow it from an Active Job worker.
  </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 takes, and everything it answers with.
  </Card>

  <Card title="Create a mockup from a PSD" icon="upload" href="/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd">
    The upload call, field by field.
  </Card>

  <Card title="Retrieve a single job" icon="clock" href="/docs/api-reference/jobs/retrieve-a-single-job">
    Follow an asynchronous render through to its finished file.
  </Card>

  <Card title="Fit and blend modes" icon="crop" href="/docs/concepts/fit-and-blend-modes">
    What `fit`, `fill` and `crop` each do to the artwork.
  </Card>

  <Card title="Photo mockups" icon="camera" href="/docs/photo-mockups/overview">
    Render from a product photo when there is no PSD.
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/webhooks/overview">
    Get a signed callback when an asynchronous render finishes.
  </Card>
</CardGroup>
