Skip to content
Developers
API docs / Get started

MAKEUGC PLATFORM API · DOCS V2

Jobs & webhooks

Match every generation request to its status endpoint, handle terminal states and use notifications alongside polling.

Job lifecycle

  1. Submit a generation request once and store its job ID.
  2. Poll the matching status endpoint every 5–10 seconds.
  3. Stop when the job fails or completes with a non-empty output URL.
  4. Save the output in your own storage if you need lasting access.
HTTP 200 means a status request succeeded. It can still describe a failed generation. Check the job’s status field, not just the HTTP code or an outer status: true.

Endpoint map

Generation ID → status route State / output
Talking actors data.id → /video/status?id=… data.status / data.url
Product in hand data.id → /product-in-hand/status?id=… data.status / data.url
Content Library data.db_id → /content-library/status?id=… data.status / data.url
Shopify templates data.db_id → /content-library/shopify/status?id=… data.status / data.url
Shoppable images / videos data.id → /shopable/images/status?id=… or /shopable/videos/status?id=… data.status / data.url
Dedicated Seedance data.id → /seedance/status?id=… data.status / data.url
Enterprise models request_id → /enterprise/status/…?media=image|video status / output.output_link[] or video_url

Most feature status values are processing, completed, or failed. Enterprise status is normally 0, 1, or 2; video status can also be a numeric or named string. Custom avatar creation returns avatar details directly.

Feature jobs and immediate results

Ad Remake, Gestures, Image Ads, Video Extend, Background Remover and Templates return data.id. Poll the corresponding feature's /status?id=ID endpoint. Video outputs use data.video_url, Image Ads uses data.image_urls , and Background Remover uses data.output_url.

Custom Voice status returns data.voice_id when the voice is ready. Scripts, speech text tools, ad analysis, prompt suggestions and speech audio return their results immediately.

Upscale requires billing_id and the original video identifiers at /upscale/status. Poll while the attempt is preparing or charged; stop on completed, refunded or failed. Only a completed attempt exposes the new video URL.

These feature endpoints use polling and reject webhook_url. Check each feature reference for its exact response shape.

Bounded polling

This server-side example polls an enterprise job for up to ten minutes, backs off between reads and handles temporary GET failures. Keep the job ID when a local timeout occurs so you can resume later.

Node.js · Enterprise status polling
// Server-side Node.js 20+. GET retries only; never resubmit generation here.
const apiKey = process.env.MAKEUGC_API_KEY;
if (!apiKey) throw new Error("Set MAKEUGC_API_KEY first");
const base = "https://app.makeugc.ai/api/platform";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function waitForEnterpriseJob(requestId, media) {
  const deadline = Date.now() + 10 * 60_000;
  let delayMs = 5_000;

  while (Date.now() < deadline) {
    let response;
    try {
      response = await fetch(
        base + "/enterprise/status/" + encodeURIComponent(requestId)
          + "?media=" + encodeURIComponent(media),
        { headers: { "X-Api-Key": apiKey },
          signal: AbortSignal.timeout(Math.min(30_000, deadline - Date.now())) },
      );
    } catch {
      await sleep(Math.min(delayMs, Math.max(0, deadline - Date.now())));
      delayMs = Math.min(10_000, delayMs * 1.5);
      continue;
    }

    if ([429, 502, 503, 504].includes(response.status)) {
      const retryAfter = response.headers.get("retry-after");
      const seconds = Number(retryAfter);
      const retryMs = retryAfter
        ? (Number.isFinite(seconds) ? seconds * 1000 : Date.parse(retryAfter) - Date.now())
        : delayMs;
      const waitMs = Number.isFinite(retryMs) ? Math.max(delayMs, retryMs) : delayMs;
      await sleep(Math.min(waitMs, Math.max(0, deadline - Date.now())));
      continue;
    }
    if (!response.ok) {
      throw new Error("HTTP " + response.status + ": " + await response.text()
        + " (request " + response.headers.get("x-request-id") + ")");
    }

    const job = await response.json();
    const state = String(job.status).toLowerCase();
    if (["2", "failed"].includes(state)) throw new Error(job.error || "Generation failed");
    const urls = media === "image" ? job.output?.output_link : [job.video_url];
    const output = (urls || []).filter((url) => typeof url === "string" && url.length > 0);
    if (["1", "completed"].includes(state) && output.length) return output;
    if (!["0", "1", "queued", "processing", "completed"].includes(state)) {
      throw new Error("Unexpected job state: " + state);
    }
    await sleep(Math.min(delayMs, Math.max(0, deadline - Date.now())));
    delayMs = Math.min(10_000, delayMs * 1.2);
  }
  // Preserve the ID so you can resume polling. Do not create a replacement job.
  throw new Error("Polling timed out; resume with request ID " + requestId);
}

const urls = await waitForEnterpriseJob("REQUEST_ID", "image");
console.log(urls);

Webhooks

Several generation endpoints accept webhook_url. Use a public HTTPS endpoint that accepts JSON POST requests. Persist notifications and acknowledge them promptly; make your processing tolerant of duplicates and out-of-order delivery.

There is no single guaranteed callback schema, signature header or delivery/retry contract across these endpoints. Treat callbacks as notifications and confirm the job through its authenticated status endpoint. Do not assume a callback body matches the polling response or mark a job completed from an unverified callback.

Keep polling as a fallback. Never put your MakeUGC API key in a callback URL. For an integration that requires a verified callback contract, confirm the endpoint-specific details with API support.

Output files

Output links may be signed or temporary. Download completed assets promptly and store them if you need durable access. No universal retention period is specified by these API routes. Keep your input media accessible throughout processing.