Skip to content
Mockly

Mockly API

POST /api/v1/chat/render

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

Endpoint

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

Body: a ChatExportPayload — see Schema for the full reference.

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/chat/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. For transparent-background video, set props.transparentBackground: true — the output will be WebM (with alpha) instead of MP4.

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/chat/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/chat/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.