Skip to main content
GET
/
api
/
v1
/
jobs
/
{jobId}
Get Job Status
curl --request GET \
  --url https://app.nouvel.ai/api/v1/jobs/{jobId} \
  --header 'Authorization: <authorization>'
{
  "jobId": "<string>",
  "status": "<string>",
  "overallProgress": 123,
  "stage": "<string>",
  "totalProjects": 123,
  "completedProjects": 123,
  "failedProjects": 123,
  "projects": [
    {
      "id": "<string>",
      "title": "<string>",
      "status": "<string>",
      "progress": 123
    }
  ],
  "completedDetails": [
    {
      "projectId": "<string>",
      "title": "<string>",
      "url": "<string>",
      "finalOutputUrl": {},
      "description": {},
      "status": "<string>",
      "aspectRatio": {},
      "durationSeconds": {},
      "sceneCount": 123,
      "script": {}
    }
  ]
}
Retrieve the current status of a video generation job, including per-project progress, completed videos, and final output URLs.

Authentication

Authorization
string
required
Your Nouvel API key. Format: Bearer nvl_xxxx

Path Parameters

jobId
string
required
The job ID returned from POST /api/v1/generate.
550e8400-e29b-41d4-a716-446655440000

Response

jobId
string
required
The job UUID.
status
string
required
Overall job status. One of:
StatusDescription
pendingJob created, not yet started
runningGeneration in progress
completedAll videos generated successfully
partialSome videos completed, some failed
failedAll videos failed
cancelledJob was cancelled
overallProgress
integer
required
Overall completion percentage (0-100). Capped at 99 while status: "running". Reaches 100 only when terminal.
stage
string
required
Current pipeline stage for running jobs:
  • preparing - Scraping product page, generating scripts
  • video - Generating video scenes with AI actors (Kling O3)
  • audio - Synthesizing voiceover (ElevenLabs TTS)
  • lip_sync - Applying lip sync to match voiceover
  • stitching - Combining scenes, adding captions, final export
totalProjects
integer
required
Total number of videos in this job.
completedProjects
integer
required
Number of videos successfully completed.
failedProjects
integer
required
Number of videos that failed during generation.
projects
array
required
Per-project status summary. Each entry contains:
completedDetails
array
Detailed results for completed/failed projects. Only populated when job status is terminal (completed, partial, failed).

Example Requests

curl https://app.nouvel.ai/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer nvl_xxxx"

Response Examples

{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "running",
  "overallProgress": 45,
  "stage": "video",
  "totalProjects": 2,
  "completedProjects": 0,
  "failedProjects": 0,
  "projects": [
    {
      "id": "proj_abc123",
      "title": "Protein Powder - Energetic Angle",
      "status": "generating",
      "progress": 60
    },
    {
      "id": "proj_def456",
      "title": "Protein Powder - Science-Based Angle",
      "status": "preparing",
      "progress": 30
    }
  ],
  "completedDetails": []
}

Polling Best Practices

  • Poll every 10-15 seconds during generation
  • Stop polling when status is terminal: completed, partial, failed, or cancelled
  • Use exponential backoff to reduce server load: start at 10s, increase to 15s, then 20s max
This endpoint doesn’t just return status—it actively triggers pipeline progression:
  • Polls fal.ai for scene generation updates
  • Initiates post-processing when scenes complete
  • Triggers stitching when all scenes are ready
Regular polling is critical. Without it, jobs may stall.
Track the stage field to understand where the job is:
  1. preparing (1-2 min) - Scraping, script generation
  2. video (10-12 min) - AI actor scene generation
  3. audio (30-60 sec) - Voiceover synthesis
  4. lip_sync (3-5 min) - Lip sync application
  5. stitching (30-60 sec) - Final video export with captions
Total time: 15-20 minutes
When status: "partial":
  • Some videos succeeded, some failed
  • completedDetails includes both successes and failures
  • Filter by status: "completed" to get successful videos
  • Use successful videos; retry failed ones if needed
  • Jobs have a 15-minute staleness timeout (no progress)
  • Absolute timeout: 30 minutes from creation
  • If a job exceeds these limits, it fails automatically
  • Build your own client-side timeout (e.g., 25 minutes) to detect stalled jobs
Don’t poll too aggressively. Polling faster than every 5 seconds provides no benefit and wastes API quota. The pipeline takes 15-20 minutes regardless of poll frequency.

Understanding Progress

Progress calculation:
  • overallProgress = weighted average of all projects
  • Each project contributes equally to the overall progress
  • Progress is capped at 99% while status: "running"
  • Reaches 100% only when terminal

Per-Project Progress Breakdown

StageProgress RangeWhat’s Happening
preparing0-20%Scraping product page, analyzing content, generating script concept
generating20-70%AI generating video scenes with actors (Kling O3 text-to-video)
post_processing70-85%TTS voiceover synthesis, audio mixing, lip sync application
stitching85-99%Combining scenes, generating captions, final video export
completed100%Video ready, finalOutputUrl available

Error Codes

CodeDescription
401Invalid or missing API key
404Job ID not found
500Internal server error

Webhook Alternative

Coming soon: Webhook support for job completion events. Instead of polling, you’ll be able to register a webhook URL to receive notifications when jobs complete.For now, polling is the only method to monitor job status.

Next Steps

Once your job reaches status: "completed":
  1. Extract video URLs from completedDetails[].finalOutputUrl
  2. Download videos for review or local storage
  3. Publish to social platforms using the Publishing API
  4. Track performance with built-in analytics
// After job completes
const job = await fetch(`https://app.nouvel.ai/api/v1/jobs/${jobId}`, {
  headers: { 'Authorization': `Bearer ${apiKey}` },
}).then(r => r.json());

if (job.status === 'completed') {
  for (const video of job.completedDetails) {
    if (video.status === 'completed' && video.finalOutputUrl) {
      // Download video
      const videoResponse = await fetch(video.finalOutputUrl);
      const videoBlob = await videoResponse.blob();

      // Save locally or upload to your CDN
      console.log(`Downloaded: ${video.title}`);
    }
  }
}