Skip to content
Mockly

Mockly API

POST /api/v1/render

Submit a chat or story mockup for rendering. Image renders return synchronously; video renders return immediately and are polled.

Endpoint

POST https://www.getmockly.com/api/v1/render
Authorization: Bearer mk_live_...
Content-Type: application/json

POST /api/v1/chat/render still works as a back-compat alias for the same handler.

Body: a render payload, discriminated on mockupType — one of chats, ai, stories, posts, comments, or emails. See Schema for the full reference for each.

Body size limit: 10 MB. If you have many image messages encoded as data URLs you may hit this — host the images and pass URLs instead.

Response shape depends on renderKind:

  • "image"200 OK synchronously with downloadUrl and sizeBytes.
  • "video"202 Accepted immediately with status: "rendering" and a statusUrl to poll.

Image render

Image renders return as soon as the still is generated (typically sub-second on Lambda). The response includes a downloadUrl you can fetch directly:

curl -X POST https://www.getmockly.com/api/v1/render \
  -H "Authorization: Bearer mk_live_..." \
  -H "Content-Type: application/json" \
  -d @payload.json
// 200 OK
{
  "id": "abc-123-...",
  "status": "completed",
  "renderKind": "image",
  "format": "png",
  "downloadUrl": "https://...",
  "sizeBytes": 482113
}

format is always "png" for images. downloadUrl is a short-lived signed URL — see URL expiry.

Video render

Video renders are async because they take 30 seconds to a few minutes to encode. The POST returns immediately with a statusUrl:

// 202 Accepted
{
  "id": "abc-123-...",
  "status": "rendering",
  "renderKind": "video",
  "statusUrl": "https://www.getmockly.com/api/v1/exports/abc-123-..."
}

Set renderKind: "video" in the request. Video is available for chats, stories, and ai (AI chat) mockups — posts, comments, and emails are image-only and return 400 validation_failed if you ask for video. For transparent-background video (chats and stories), set props.transparentBackground: true — the output will be WebM (with alpha) instead of MP4.

AI chat videos animate the conversation: each prompt appears, then the assistant's answer streams in like a typewriter. You don't set a duration — it's derived from the messages.

The top-level canvas field works for video too: the mockup animates centered on the framed canvas. Note that a canvas background is always painted, so combining canvas with props.transparentBackground gives you a WebM without visible transparency. Framed videos are capped at 4096 px on the longest side: when a ratio would push past that, the render automatically steps down to the largest resolution tier that fits (playability and render time both break beyond it).

3D phone render

Set the top-level render3D: true to render the mockup on a 3D phone in 3D space — works for both chats and stories. Add the optional top-level cameraPosition ([number, number, number]) to control the camera's XYZ position; omit it for the default angle. The canvas field works in 3D as well — the scene keeps its alpha channel, so the phone floats directly on the canvas background.

Polling pattern

Poll the statusUrl every 2–4 seconds until you see status: "completed". Don't keep the original POST connection open — it would hold a render slot for nothing.

const submit = await fetch("https://www.getmockly.com/api/v1/render", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MOCKLY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});
const { id, statusUrl } = await submit.json();

// Poll until done
while (true) {
  await new Promise((r) => setTimeout(r, 3000));

  const status = await fetch(statusUrl, {
    headers: { Authorization: `Bearer ${process.env.MOCKLY_API_KEY}` },
  });
  const data = await status.json();

  if (data.status === "completed") {
    console.log("Done:", data.downloadUrl);
    break;
  }
  if (data.status === "failed") {
    throw new Error(data.error);
  }
  console.log(`Rendering — ${Math.round((data.progress ?? 0) * 100)}%`);
}

Recommended: cap your polling to 10 minutes total (videos rarely take longer). After that, treat as failed and retry the render.

Rate limits

Per-minute rate limits are enforced per API key:

EndpointLimit
POST /api/v1/render60 requests / min
GET /api/v1/exports/:id and /exports/:id/download120 requests / min

When exceeded, you'll get 429 rate_limited with these headers:

HeaderMeaning
Retry-AfterSeconds to wait before retrying.
X-RateLimit-LimitThe limit (e.g. 60).
X-RateLimit-RemainingRequests left in the current window (usually 0 on a 429).
X-RateLimit-ResetUnix-seconds timestamp when the window resets.

Honor Retry-After and you'll be fine. The window slides — there's no fixed-minute boundary.

Monthly limits

Each API key has a per-month render quota:

Render kindIncluded per month
Image10,000
Video500

Limits reset on the first day of each month (UTC). We're not enforcing the cap in v1 — usage is tracked per key and we'll start returning 429 rate_limited once we have signal on real traffic. If you're planning a workload that approaches these numbers, email us first so we can set you up.