Promoat.Partner API

Videos

Jobs & polling

Video endpoints return a workflow_id, and this endpoint is how you follow it: status, cost as it settles, and the finished video URL. No webhooks — poll it.

The status endpoint

GET/api/partner/v1/jobs/{workflowId}/status

Free to call, as often as you like. The response is the job's whole story:

FieldTypeDescription
workflow_idstringThe id you got when queueing.
statusenumqueued → running → completed or failed.
video_url / image_urlstring | nullThe finished output, present once completed.
resultobject | nullvideo_url / image_url / audio_url / asset_id when present, plus poster_url, editable (can be edited) and scenes_url (its GET /videos/…/scenes link).
credits_estimatedint | nullThe reservation made at queue time.
credits_actualint | nullThe measured charge, recorded at completion.
credits_consumedint | nullThe one to display: null while queued/running, the actual charge once completed (never more than reserved), 0 on failed (refunded).
job_typestringSee job types.
phaseenumqueued → preparing → generating → assembling → finalizing → completed | failed.
progressint 0–100 | nullRough percentage for a progress bar.
poster_urlstring | nullA still of the video, once available.
source_workflow_idstring | nullOn edits: the video that was edited.
error_codestring | nullMachine-readable reason a job failed — below.
error_messagestring | nullA fixed, user-safe explanation you can show.
queued_at / completed_attimestampsFor your own duration metrics.
A completed scene video
{
  "workflow_id": "partner_h3v_5d0e9b2c4f1a4e7b9c3d2a1f0e9d8c7b",
  "status": "completed",
  "job_type": "multi_scene_video",
  "phase": "completed",
  "progress": 100,
  "error_code": null,
  "error_message": null,
  "credits_estimated": 131,
  "credits_actual": 131,
  "credits_consumed": 131,
  "video_url": "https://…/video.mp4",
  "poster_url": "https://…/poster.jpg",
  "result": {
    "video_url": "https://…/video.mp4",
    "asset_id": "…",
    "poster_url": "https://…/poster.jpg",
    "editable": true,
    "scenes_url": "…/api/partner/v1/videos/partner_h3v_5d0e…/scenes"
  },
  "queued_at": "2026-07-08T04:12:23.595Z",
  "completed_at": "2026-07-08T04:31:02.114Z"
}

A sane poll loop

  • Interval: every 8–10 seconds is plenty (30 s after 10 minutes) — jobs take minutes, not milliseconds.
  • Timeout: give talking-head jobs ~10 minutes and scene videos ~25 before alarming. A job that stays quiet for 75 minutes is failed for you with STALE_TIMEOUT and refunded.
  • Terminal states are terminal. Once completed or failed, the record never changes — cache it and stop polling.
Node example
async function waitForJob(workflowId) {
  for (;;) {
    const res = await fetch(
      `https://platform.promoat.ai/api/partner/v1/jobs/${workflowId}/status`,
      { headers: { Authorization: `Bearer ${process.env.PROMOAT_KEY}` } },
    );
    const job = await res.json();
    if (job.status === 'completed') return job;
    if (job.status === 'failed') throw new Error(job.error_message ?? 'job failed');
    await new Promise((r) => setTimeout(r, 8000));
  }
}

Showing progress to your users

Show phase as a label and progress as a bar; once poster_url appears you can show a thumbnail. The same metadata.phase is on each row of GET /usage/jobs.

Warnings raised while the job runs — for example SCENE_PHOTO_DROPPEDwhen a scene photo couldn't be used — are on the job's GET /usage/jobs row as metadata.warnings (all warning codes). Warnings about the request itself come back right away, on the POST /videos response.

Job types and workflow ids

job_typeworkflow_id prefixMade by
talking_head_videopartner_h3t_POST /videos, talking head
multi_scene_videopartner_h3v_POST /videos, scenes
scene_edit_videopartner_se_fix-scene, edit-script
fix_sound_videopartner_fs_fix-sound

New ids are the prefix plus 32 hex characters. Jobs made before 1.1.0 keep their old ids and prefixes.

Why a job failed

error_codeMeaning
PROVIDER_MODERATIONThe content was refused by a safety check. Change the script, idea or photos.
PROVIDER_INPUTAn input (image, audio) could not be used.
PROVIDER_INFRAA temporary failure while generating. Retry.
PROVIDER_TIMEOUTGeneration took too long. Retry.
START_FAILEDThe job could not start.
STALE_TIMEOUTNo progress for 75 minutes.
LINE_TOO_LONGA line was too long for its scene.
VOICE_UNAVAILABLEThe voice no longer works — re-clone it.
FIX_NOT_APPLIEDAn edit could not be applied.
MISSING_ASSETSomething the job needed is gone (e.g. the source video).
AUDIO_TOO_LONGThe speech measured over 59.5 s.
INTERNALSomething failed on our side.

Every failed job is refunded once, automatically.

404 on a status poll?

The workflow_id is wrong or belongs to a different key — existence is never leaked. Ids are returned only once, at queue time; if you lost one, find it in GET /usage/jobs.

Try it

Paste any workflow_id from a video you've queued (or grab one from your job history below):

Try itGET/api/partner/v1/usage/jobsfree
Add your API key in the sidebar to enable this.
Try itGET/api/partner/v1/jobs/{workflow_id}/statusfree
Add your API key in the sidebar to enable this.