Skip to main content
POST /api/v1/facade/generate can render two ways, chosen by operation:
operationBehaviorUse when
"preview"Synchronous — the response contains the rendered outputsOne image, user actively waiting
"render"Async — you get a job back immediately (HTTP 202) and the render happens on a queueBackground pipelines, webhook-driven flows, anything the user isn’t watching
This guide covers the async path.

Step 1 — submit the job

Submit a normal generate request with "operation": "render":
curl -X POST https://api.kynva.ai/api/v1/facade/generate \
  -H "Authorization: Bearer $KYNVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "operation": "render",
    "generation_type": "image",
    "intent": {
      "content": { "headline": "Summer Sale", "cta": "Shop now" },
      "export_intent": {
        "primary": { "platform": "instagram", "format": "png", "profile_id": "instagram-post" }
      }
    }
  }'
Response — HTTP 202, immediately, before rendering:
{
  "job": {
    "job_id": "job-9f3c2a1e-77b4-4d2c-9a1b-0c8d7e6f5a4b",
    "status": "queued",
    "created_at": "2026-07-04T18:42:11.812Z"
  }
}

Step 2A — subscribe to webhooks (preferred)

Register a webhook once and Kynva delivers a render.completed (or render.failed) event when any of your jobs finishes — no polling at all:
curl -X POST https://api.kynva.ai/api/v1/facade/webhooks \
  -H "Authorization: Bearer $KYNVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your.app/hooks/kynva", "events": ["render.completed", "render.failed"] }'
See the webhooks overview for signatures, retries, and the delivery contract.

Step 2B — poll for status

GET /api/v1/facade/render-jobs/{job_id}
async function waitForJob(jobId: string) {
  let delay = 2_000;
  while (true) {
    const res = await fetch(
      `https://api.kynva.ai/api/v1/facade/render-jobs/${jobId}`,
      { headers: { Authorization: `Bearer ${process.env.KYNVA_API_KEY!}` } },
    );
    if (res.status === 404) throw new Error("unknown job");
    const { data } = await res.json();

    if (data.status === "completed") return data;
    if (data.status === "failed") throw new Error(data.error?.message ?? "render failed");

    await new Promise(r => setTimeout(r, delay));
    delay = Math.min(delay * 1.5, 15_000);
  }
}
While the job runs, the response also carries progress detail:
{
  "data": {
    "job_id": "job-9f3c…",
    "status": "processing",
    "progress_pct": 70,
    "stage": "UPLOADING",
    "outputs": null,
    "error": null,
    "design_id": "clx…",
    "created_at": "2026-07-04T18:42:12.001Z",
    "completed_at": null
  }
}
  • status is the coarse lifecycle: queued → processing → completed | failed.
  • stage is the granular pipeline step while processing (QUEUED, ASSET_DOWNLOADING, ASSET_READY, RENDERING_SCENES, UPLOADING, COMPLETING) — useful for progress UIs.
Polls count against the jobs:status rate-limit bucket (60/min on the free tier). Back off as shown above — or use webhooks and don’t poll at all.

Step 2C — live progress over SSE

For a progress bar without polling, open the server-sent-events stream:
GET /api/v1/facade/render-jobs/{job_id}/stream
Events: job_status (progress updates), job_complete, job_failed. The stream times out after 5 minutes; the poll endpoint above is the durable fallback.

Step 3 — fetch the results

Once status is "completed", outputs holds the rendered files:
{
  "data": {
    "job_id": "job-9f3c…",
    "status": "completed",
    "progress_pct": 100,
    "outputs": [
      {
        "export_profile": "instagram-post",
        "url": "https://cdn.kynva.ai/renders/usr_…/job-9f3c…/instagram-post.png",
        "width": 1080,
        "height": 1080,
        "file_size": 184320
      }
    ],
    "completed_at": "2026-07-04T18:42:19.443Z"
  }
}
Output URLs are stable, long-lived CDN URLs (served with Cache-Control: immutable) — safe to store and hot-link. Because rendering is deterministic, the same job re-run from the same request produces the same bytes.

How long is job status available?

StateQueue retentionAfter that
Completed24 hoursThe poll endpoint falls back to the durable design record — status, design_id, and outputs keep answering
Failed7 daysSame fallback; the detailed error message is only retained for the 7-day window
Store design_id if you want a permanent handle — the design (and its outputs) outlives the job record.