Jobs

Submit media work and poll for results with a Job object — or configure webhooks to receive HTTPS callbacks on status changes instead of polling. Jobs are async — create returns with status: queued (or pending briefly if the job is not yet on Kafka). Poll until a terminal status (completed, failed, or cancelled), then fetch the result. Use POST /v1/jobs/{jobId}/cancel to stop in-flight work. Before creating a job, call GET /v1/jobs/policy?serviceType=… for plan-specific limits and supported client options.

Available service types:

  • transcription — transcribe a media file from S3 into timestamped segments. Result is a Transcript record accessible via the Transcripts API.
  • clip_generation — analyze a long-form video and render short clips in a configurable layout (vertical, widescreen, square, or landscape-in-vertical) from an existing transcript. MP4s upload to your S3; Perchat stores clip metadata via the Clips API.
  • thumbnail — generate 1–3 AI cover-image JPEGs from a completed transcription (maximum 3 per job). Images upload to your S3; metadata is stored on the transcript via the Thumbnails API.
  • indexer — chunk, embed, and index a completed transcription into the Qdrant vector store so it can be searched via Semantic search. Requires job:create; the transcription must exist before submitting.
  • metadata — generate title, description, IAB categories, and chapter markers from a completed transcript. Results are stored on the transcript via the Metadata API.
  • summarization — synthesize a complete Summarization record (short/long synopsis, 3 SEO descriptions, 3 social hooks, 5 keywords, classification) using the transcript and visual chunks natively generated by a transcription job. Create with POST /v1/jobs (serviceType: summarization).

Progress event stages — transcription

Stage timelines are returned by GET /v1/jobs/{jobId}/progress (console session only), not on the Job object from retrieve or list. Each progress[] entry has stage, event (started | completed | failed | skipped), at, and optional details. Audio preprocessing stages (extract_audio, denoise_audio, separate_vocals) run automatically — not client-configurable.

stageDescription
downloadDownload source media
transcribeTop-level transcribe stage (wraps sub-stages below)
extract_audioExtract mono 16 kHz speech audio
denoise_audioDenoise + bandpass
separate_vocalsDemucs vocal separation
probeProbe audio duration before chunking
split_chunkSplit source into chunks (mp3 when input is audio-only after preprocessing, mp4 for video)
compress_chunkCompress chunk for upload
transcribe_chunkTranscribe one chunk
mergeMerge chunk transcripts into one segment list
transcribe_fileTranscribe whole file (non-chunked path)
video_analysisVisual frame analysis — GPT-4o mini vision samples frames and produces chunk summaries used downstream for summarization. Runs natively in every transcription job.
upload_full_textUpload full transcript text to S3 (s3_direct jobs)
upload_segmentsUpload segments JSON to S3 (s3_direct jobs)
upload_transcript_srtUpload SRT captions to S3 (s3_direct jobs)
upload_transcript_vttUpload WebVTT captions to S3 (s3_direct jobs)

Progress event stages — indexer

stageDescription
fetch_transcriptionFetch transcript segments from the API
chunkSplit segments into overlapping text windows for embedding
embed_sparseBM25 sparse embedding (local, no API key)
embed_denseDense embedding via OpenAI text-embedding-3-large
indexUpsert new hybrid vectors (job-scoped point ids when re-indexing)
cutoverAfter a successful upsert, remove prior index points for that transcription
rollbackOn failure, remove partial new vectors so the previous index stays searchable

Progress event stages — clips

stageDescription
fetch_transcriptionLoad stored transcript segments from input.textSourceId (required)
downloadFetch source video from storage connection
ingestProbe metadata, build proxy, extract audio
detect_scenesScene boundary detection (PySceneDetect)
analyze_scenesGPT-4o vision per scene when enabled in platform settings; skipped otherwise
build_timelineMerge transcript, scenes, and vision into a timeline
detect_categoryClassify content genre from transcript and vision (GPT-4o-mini); skipped when categoryOverride is set, category detection is disabled in platform settings (analysis.category), or OpenAI is unavailable
detect_candidatesScore and rank clip candidates
render_clipsFFmpeg layout-aware reframe per candidate using the resolved job layout (vertical, widescreen, square, or landscape-in-vertical)
upload_clipsUpload rendered MP4s to customer S3

Progress event stages — thumbnail

Each thumbnail job generates at most 3 JPEG variants. Set input.options.thumbnailCount to 1, 2, or 3 — values above 3 are rejected at validation. Outputs are written to your bucket via the job's storage connection.

Workers emit each row below in progress[]. A thumbnail_pipeline envelope event may also appear (started/completed); dashboards timeline the flat sub-stages, not the envelope.

stageDescription
downloadFetch source video from storage connection
fetch_transcriptionLoad transcript segments from input.transcriptionId (required)
thumbnail_ingestProbe metadata and validate video readability
thumbnail_encodeDownscale proxy video for faster frame extraction
thumbnail_analyzeChunk video, sample frames, score moments with gpt-4o vision
thumbnail_mergeConsolidate scored frames and apply confidence threshold
thumbnail_audioBuild narrative context from transcript for image prompts
thumbnail_generateRender JPEG variants via gpt-image-1 (imageQuality from platform config)
upload_thumbnail_1/2/3Upload generated JPEGs to customer S3 (one stage per variant)

Progress event stages — metadata

stageDescription
load_transcriptionFetch transcript segments from input.textSourceId (required)
generate_metadataLLM generates title, description, categories, and chapters (model from platform config)
save_metadataPersist Metadata record and embed on the transcription

Progress event stages — summarization

stageDescription
synthesisCall GPT-4o to synthesize chunk summaries and the full transcript into synopses, SEO descriptions, social hooks, keywords, and classification.

Job failures & retries

When a job fails asynchronously, GET /v1/jobs/{jobId} still returns HTTP 200 — read failure fields on the Job object in data, not the top-level envelope error (that shape is for request/auth validation failures). See Errors → Job failures (async).

FieldWhen setShapeUse for
errorstatus: failed (terminal){ code, message }Primary integration signal — read error.code when polling ends in failed
lastErrorAfter any failed worker attempt (retry or terminal){ at, message } — no codeLatest attempt message while a job is queued for retry; also present on terminal failed
progress[].errorA stage event with event: failed{ code, message }Console timeline UI — returned by GET /v1/jobs/{jobId}/progress, not on retrieve/list Job objects
attemptHistory[]When the worker requeues after a retryable failureArray of attempt, failedAt, error, progressConsole-only via GET /v1/jobs/{jobId}/progress; use attemptCount and lastError when polling with API keys
How failure signals flow from a worker attempt into Job fields.

Retry lifecycle (retryable failures such as transient network errors):

  1. Worker attempt fails → lastError is set with at + message.
  2. If retries remain: status returns to queued, attemptCount increments, the failed attempt's progress[] is archived into attemptHistory[], and progress[] resets for the next run. Top-level error stays null until the job is terminal.
  3. Poll may show status: queued with a non-null lastError — the job is waiting to retry, not permanently failed.
  4. After max retries or a non-retryable code (e.g. duration_exceeded): status: failed, both error and lastError are populated.
  5. On status: completed, both error and lastError are null.
Job status transitions including retry, terminal failure, and cancel paths.

While retrying (error still null):

{
  "status": "queued",
  "attemptCount": 1,
  "lastError": {
  "at": "2026-05-18T12:40:12Z",
  "message": "Semantic search indexing could not connect to Qdrant. Configure the cluster in platform admin (Core → Qdrant): set the URL and API key, run Test connection, then save. Retry the job after Qdrant is reachable."
},
  "error": null
}

Terminal failure (error.code is the field to branch on):

{
  "status": "failed",
  "lastError": {
  "at": "2026-05-18T12:34:58Z",
  "message": "Video duration 7200.0s exceeds limit 3600s (checked at local_probe)"
},
  "error": {
  "code": "duration_exceeded",
  "message": "Video duration 7200.0s exceeds limit 3600s (checked at local_probe)"
}
}

Common error.code values and quotas: Plans & limits → Job failure codes. Full failed-job example on Retrieve a job.

A completed transcription job exposes resultSummary.textSourceId, segmentCount, and textPreview. A completed clips job exposes resultSummary.clipGenerationId and resultSummary.clipCount. A completed thumbnail job exposes resultSummary.textSourceId and resultSummary.thumbnailCount and resultSummary.nonAiThumbnailCount. A completed metadata job exposes resultSummary.metadataId and resultSummary.chapterCount. Use the Transcripts API for full text, segments, word timings, and editing — not the Job object itself. Use the Clips API for per-clip metadata after clip jobs complete. Use the Thumbnails API for per-image metadata after thumbnail jobs complete. Use the Metadata API for title, description, categories, and chapters after metadata jobs complete. A completed indexer job exposes resultSummary with textSourceId, collection, and indexedChunks. Use the Catalog API to search indexed chunks after indexer jobs complete.

Transcription flow

  1. POST /v1/jobs (serviceType: transcription) — submit using S3 storage connection
  2. GET /v1/jobs/{jobId} — poll until status: completed
  3. GET /v1/transcriptions/{transcriptionId} — fetch the transcript (use resultSummary.textSourceId from the completed job)
  4. Optional: Transcripts edit/glossary endpoints if you build a review UI

Clips flow

  1. Complete a transcription job — note the textSourceId from resultSummary
  2. Optional: GET /v1/jobs/policy?serviceType=clip_generation — read supported layouts, caps, and client options for your plan
  3. POST /v1/jobs (serviceType: clip_generation) with input.textSourceId and optional input.options.maxClips, layout, fillStyle, categoryOverride S3 storage connection
  4. GET /v1/jobs/{jobId} — poll until status: completed
  5. GET /v1/clip-generations/{clipGenerationId} — fetch per-clip metadata and segment spans (use resultSummary.clipGenerationId from the completed job)

Thumbnail flow

  1. Complete a transcription job first — thumbnail jobs require input.textSourceId with segments.
  2. POST /v1/jobs (serviceType: thumbnail) — S3 storage connection; set input.options.thumbnailCount (1–3, maximum 3 per job).
  3. GET /v1/jobs/{jobId} — poll until status: completed
  4. GET /v1/transcriptions/{transcriptionId} (includes thumbnails) or GET /v1/thumbnails/{transcriptionId} — fetch per-image metadata

Metadata flow

  1. Complete a transcription job — note the textSourceId from resultSummary
  2. POST /v1/jobs (serviceType: metadata) with input.textSourceId Create a metadata job
  3. GET /v1/jobs/{jobId} — poll until status: completed
  4. GET /v1/transcriptions/{transcriptionId}/metadata — fetch generated fields

Semantic search flow

  1. Complete a transcription job — note the textSourceId from resultSummary
  2. POST /v1/jobs (serviceType: indexer) with input.textSourceIds — poll until status: completed
  3. POST /v1/catalog/search — query across all indexed transcriptions in the project (requires ai:search permission)

Summarization flow

  1. Complete a transcription job — note the jobId from resultSummary. The transcription pipeline automatically generates video analysis chunks.
  2. POST /v1/jobs (serviceType: summarization) with input.textSourceJobId
  3. GET /v1/jobs/{jobId} — poll the summarization job until status: completed
  4. GET /v1/summarizations/{summarizationId} — fetch the Summarization record using resultSummary.summarizationId from the completed job

Requires a project API key (sk_prj_…) with job:* permissions. Queue priority is set from your workspace plan — do not pass it on create.

See also: Jobs & transcripts workflows for end-to-end patterns (poll vs list, export, custom editor). Quotas, max video length (1 hour), and failure codes: Plans & limits.

The Job object

jobIdstring
Unique identifier (job_…).
serviceTypestring
Service to run: transcription, clip_generation, thumbnail, metadata, indexer, or summarization.
statusstring
pending (not yet on the queue), queued, processing, cancelling (graceful stop in progress), completed, failed, or cancelled.
prioritystring
Queue lane: low, normal, or high. Set from workspace plan at creation — not client-controlled.
workspaceIdstring
Owning workspace.
projectIdstring
Project the job belongs to.
workspacePlanstring, optional
Workspace plan snapshotted at job creation (free, pro, etc.). Drives queue priority.
inputobject
Validated transcription input via s3_directconnectionId, bucket, sourceKey, optional outputPrefix (uses a project storage connection). The create response includes input.textSourceId and resolved output paths (resolvedOutputPrefix, resolvedOutputKeys).Both modes accept optional options.language. Stored jobs also include platform-managed options.preprocessing. For s3_direct, resolved S3 output paths are returned on the job input at create.
resultSummaryobject | null, optional
Shape depends on serviceType:
  • Transcriptionnull at create and while the job is in flight. When complete: textSourceId, textPreview, segmentCount, durationSec. For s3_direct, input.textSourceId is already set at create (same id as resultSummary.textSourceId after success). Save jobId from the create response and poll GET /v1/jobs/{jobId} (or use webhooks) to obtain resultSummary.textSourceId once the transcript record exists.
  • Clips — when complete: clipGenerationId, clipCount, sourceDurationSec, transcriptPreview.
  • Thumbnail — when complete: textSourceId, thumbnailCount (accumulated total), newThumbnailCount (added by this run), nonAiThumbnailCount, generated.
  • Metadata — when complete: textSourceId, metadataId, title, chapterCount, generated.
  • Indexer — when complete: textSourceId, collection, indexedChunks, denseEngine, sparseEngine, optional chunkType.
  • Summarization — when complete: summarizationId and shortSynopsisPreview (first 100 characters of the short synopsis). Fetch the full Summarization record via GET /v1/summarizations/{summarizationId}.
null while pending, queued, processing, or cancelling.
cancelRequestedAttimestamp | null, optional
When cancellation was requested; null until a cancel call.
cancelRequestedByLabelstring | null, optional
Who requested cancel (@user, API key name, or Console).
cancelRequestedByKindstring | null, optional
user, api_key, or console when cancel was requested.
cancelRequestedByUserIdstring | null, optional
User id when cancel was requested from the dashboard.
cancelRequestedByApiKeyIdstring | null, optional
API key id when cancel was requested with a project key.
cancelModestring | null, optional
Currently graceful — processing jobs stop after the current pipeline stage.
sourceDisplayNamestring, optional
Human-readable source label derived from input (e.g. filename without extension). Used in job list search and dashboards.
webhookUrlstring | null, optional
Per-job HTTPS webhook override from create, when set. Otherwise null and the project default from Webhooks applies.
currentStagestring | null, optional
Worker stage in flight (e.g. transcribe); null when idle or finished.
attemptCountinteger, optional
Failed attempts before this run; 0 on first try. Incremented on retry.
lastErrorobject | null, optional
Snapshot of the most recent failed worker attempt: at (timestamp) and message — no code field. Set while a job is retrying (status: queued) or on terminal failed; null on success and on jobs that never failed. See Job failures & retries.
errorobject | null
Terminal failure reason when status is failed: code (e.g. duration_exceeded, transcript_too_large, processing_failed) and message. Otherwise null — including while retrying (queued with a non-null lastError). Not the HTTP envelope error field. See Job failures & retries.
createdByLabelstring, optional
Human-readable creator (@user, API key name, or Console). On retrieve/list.
createdByKindstring, optional
One of user, api_key, console.
apiKeyLabelstring | null, optional
Name of the API key that created the job, when applicable.
apiKeyIdstring, optional
API key or console pseudo-key that owns the job.
isConsoleKeyboolean, optional
True when submitted from the dashboard browser session.
createdAttimestamp
Creation time.
updatedAttimestamp
Last update time.
startedAttimestamp | null, optional
When processing began.
completedAttimestamp | null, optional
When the job finished (success, failure, or cancel).
userMetadataobject, optional
Caller-defined metadata from job creation. Transcription jobs typically include customerJobRef (your reference string).

Job policy

GET/v1/jobs/policy
Public create-time limits and supported client options for a job serviceType — sourced from platform admin settings. No authentication required. Pass the same serviceType you use on POST /v1/jobs.

Parameters

serviceTypestring
Required. One of clip_generation, transcription, thumbnail, or translation.

Returns

Returns a serviceType-specific payload. Plan-scoped services include plans.free and plans.enterprise— read the entry that matches the caller's workspace plan. Example for clip jobs below; transcription, thumbnail, and translation shapes differ.

See the the job object for attribute details.

Create a transcription job

POST/v1/jobs
Submit a transcription job using a saved project Storage connection. Provide the connection id, bucket, and source object key — the worker reads the video and writes transcript files with the connection's stored IAM credentials. No presigned URLs required.

Note: The transcription pipeline also natively performs visual frame analysis to produce chunk summaries for use in summarization.

When to use

  • Reusable bucket access — you configured S3 credentials once under Project → Storage.
  • Simpler job payloads — four fields instead of five presigned URLs per job.
  • Same outputs — full text, segments JSON, SRT, and VTT are written to your bucket under the resolved output location (from outputPrefix when set, or {defaultOutputPrefix}/{textSourceId}/transcripts/ when omitted — see parameter notes).

Requires permission

job:create

Do not send priority — queue lane is set from your workspace plan at creation. Do not send input.options.preprocessing — extract and denoise run by default; vocal separation runs only when enabled in platform settings. Transcription engine is platform-managed and not exposed on job objects. Send userMetadata.customerJobRef so you can correlate jobs in your systems and filter the job list; the console auto-fills this from your input settings.

Save jobId as your async handle. Transcription is asynchronous — the create response has resultSummary: null because the transcript record does not exist yet. For s3_direct jobs, input.textSourceId is also assigned at create (used for the default output path and downstream jobs). When the job finishes, poll GET /v1/jobs/{jobId} (see Retrieve a job) or handle a job status webhook (event: completed) and read resultSummary.textSourceId (same id as input.textSourceId on s3_direct). Use that id with GET /v1/transcriptions/{transcriptionId} for segment text, editing, and downstream jobs (clips, thumbnails, metadata, indexer, or summarization).

Getting the transcription ID
  • At create (s3_direct)input.textSourceId is returned on the job object immediately (alongside jobId); resultSummary is still null.
  • At create (s3_presigned) — only jobId is returned; resultSummary is null.
  • After completion — poll GET /v1/jobs/{jobId} until status: completed, then read resultSummary.textSourceId (or read it from a completed webhook payload).
  • Correlate jobs — pass userMetadata.customerJobRef at create and filter with GET /v1/jobs?customerJobRef=… if you lose the jobId.


S3-only result path. The worker writes full text, segments JSON, SRT, and VTT to your bucket at the resolved output location. When omitted, outputs are written under perchat-media-results/{transcriptionId}/transcripts/ (or {defaultOutputPrefix}/{transcriptionId}/transcripts/ when the bucket's defaultOutputPrefix is set on the storage connection). Files are placed directly in that folder (e.g. full_text.txt, clip_01.mp4, thumbnail_1.jpg). Set outputPrefix to override with a custom folder path — it is not combined again with defaultOutputPrefix. If your pipeline only reads those files from S3, you can use input.textSourceId from the create response to locate them. Use the Transcripts API (and downstream jobs such as clips or indexer) when you need platform-managed transcript records.

Create a storage connection first (dashboard or POST storage connection with a project API key that includes storage_connection:create). Create and update run automatic bucket permission tests; inactive connections are rejected.

Example body:
{
  "serviceType": "transcription",
  "input": {
    "sourceType": "s3_direct",
    "connectionId": "e7b2c4d8-0000-4000-a000-000000000001",
    "bucket": "my-bucket",
    "sourceKey": "media/clips/demo.mp4",
    "options": {
      "language": "en"
    }
  },
  "userMetadata": {
    "customerJobRef": "abc-123"
  }
}
Validation at create
  • connectionId must belong to the project bound to your API key; bucket must be on the connection.
  • sourceKey must reference a supported video file; the API probes the object with a ranged GET.
  • When outputPrefixis set, it is the full output location in the bucket (not combined again with the connection's defaultOutputPrefix). When omitted, the API resolves {defaultOutputPrefix}/{textSourceId}/transcripts/ using the pre-assigned input.textSourceId and the bucket's defaultOutputPrefix (defaults to perchat-media-results).
  • Output paths are validated with empty PUT probes on the resolved keys before the job is queued.
  • Failures return 422 validationError with error.details[].

Parameters

serviceTypestring
Must be transcription.
userMetadataobject, optional
Caller-defined metadata echoed on the Job object. For transcription jobs, include customerJobRef (see below). The object may be omitted on the REST API, but the Perchat console always sends it.
userMetadata.customerJobRefstring, optional
Your stable reference for this job (e.g. internal ticket id or s3_direct-my-bucket-demo-en). Used to filter jobs via GET /v1/jobs?customerJobRef=…. Required in the Perchat console; strongly recommended for API integrations (max 120 characters; letters, digits, hyphen, underscore). Echoed on webhook payloads when set.
webhookUrlstring, optional
Optional HTTPS URL for job status callbacks on this job only. Overrides the project default from Webhooks. Must use https://.
input.sourceTypestring
Must be s3_direct.
input.connectionIdstring
connectionId from a project Storage connection. The worker uses stored credentials — no presigned URLs.
input.bucketstring
S3 bucket name. Must be listed on the connection's buckets array.
input.sourceKeystring
Object key for the source video (e.g. media/clips/demo.mp4). Must have a supported video extension.
input.outputPrefixstring, optional
Optional output location for transcript files (s3_direct; API field: outputPrefix). When set, used as the full relative path in the bucket (no leading slash, no .. segments). When omitted, outputs are written under perchat-media-results/{transcriptionId}/transcripts/ (or {defaultOutputPrefix}/{transcriptionId}/transcripts/ when the bucket's defaultOutputPrefix is set on the storage connection). Files are placed directly in that folder (e.g. full_text.txt, clip_01.mp4, thumbnail_1.jpg). Set outputPrefix to override with a custom folder path — it is not combined again with defaultOutputPrefix.
input.options.languagestring, optional
Optional ISO language hint (e.g. en, ta). Omitted = auto-detect.

Returns

Returns the created Job — usually status: queued with resultSummary: null. Save jobId as your async handle. The response includes input.textSourceId (pre-assigned for output paths and downstream jobs). The worker uploads transcript files to your bucket on success — see Retrieve a job.

See the the job object for attribute details.

Create a translation job

POST/v1/jobs
Queue translation for an existing completed transcript. The client sends only the transcript id and target language code; do not send transcript segments in the request. The worker fetches the transcript by id and persists the result as a Translation record and on the transcript translation cache.

When to use

  • After transcription completes — pass the completed textSourceId.
  • Language code required — send codes such as ta, hi, ja, es, or en-US. Do not send display names such as Tamil.
  • No transcript payload — the platform fetches segments from the existing transcript using input.textSourceId.

Requires permission

job:create

Request body:
{
  "serviceType": "translation",
  "input": {
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "targetLanguage": "ta",
    "sourceLayer": "current"
  },
  "userMetadata": {
    "customerJobRef": "translation-demo-ta"
  }
}
Completed translation job:
{
  "jobId": "job_01HZ2K9V3JEK4WYZN6F0XQ2W9N",
  "serviceType": "translation",
  "status": "completed",
  "priority": "low",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "workspacePlan": "free",
  "input": {
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "targetLanguage": "ta",
    "sourceLanguage": "en",
    "sourceLayer": "current",
    "config": {
      "backend": "openai"
    }
  },
  "resultSummary": {
    "translationId": "trl_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "sourceLanguage": "en",
    "targetLanguage": "ta",
    "languageKey": "ta",
    "sourceLayer": "current",
    "segmentCount": 152,
    "durationSec": 318.08,
    "translationBackend": "openai",
    "model": "gpt-4o"
  },
  "sourceDisplayName": "demo",
  "webhookUrl": null,
  "currentStage": null,
  "attemptCount": 0,
  "lastError": null,
  "error": null,
  "createdAt": "2026-05-18T12:34:56Z",
  "updatedAt": "2026-05-18T12:36:22Z",
  "startedAt": "2026-05-18T12:34:57Z",
  "completedAt": "2026-05-18T12:36:22Z",
  "userMetadata": { "customerJobRef": "translation-demo-ta" }
}

The worker writes the translated segment array to translation.<languageKey> on the transcript and also persists the full Translation record. Re-running the same target language replaces that language array; translation revision history is not stored.

Parameters

serviceTypestring
Must be translation.
input.textSourceIdstring
Preferred — completed text source ID (txt_…) to translate. Must belong to the same project as the API key.
input.targetLanguagestring
Target language code, for example ta, hi, ja, es, or en-US.
input.sourceLanguagestring, optional
Optional source language code. Omit when the source language should be inferred from the transcript/default.
input.sourceLayerstring, optional
Optional transcript layer to translate: current, glossary, or raw. Defaults to current, which points to segments.
userMetadataobject, optional
Optional caller-defined metadata echoed on the Job object. Include customerJobRef for stable lookup in job lists.

Returns

Returns the created Job — usually status: queued with resultSummary: null. When complete, resultSummary includes translationId, language info, source layer, segment count, backend, and model. Retrieve the full Translation record from the Translations API.

See the the job object for attribute details.

Create a dubbing job

POST/v1/jobs
Queue dubbing for an existing completed transcript. The client sends the transcript id, target language, and optional voice assignment settings. The worker reuses the existing transcription and Translation record, then generates dubbed audio/video artifacts.

When to use

  • After transcription completes — use the textSourceId from the transcription job's resultSummary.

Requires permission

job:create

Request body:
{
  "serviceType": "dubbing",
  "input": {
    "transcriptionId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "targetLanguage": "ta",
    "sourceLanguage": "en",
    "sourceLayer": "current",
    "options": {
      "diarization": {
        "enabled": true,
        "minSpeakers": 1,
        "maxSpeakers": 4
      },
      "voiceAssignments": {
        "SPEAKER_00": "vce_01HX2K9V3JEK4WYZN6F0XQ2W8M"
      }
    }
  },
  "userMetadata": {
    "customerJobRef": "dubbing-demo-ta"
  }
}
Completed dubbing job:
{
  "jobId": "job_01HZ2K9V3JEK4WYZN6F0XQ2W9N",
  "serviceType": "dubbing",
  "status": "completed",
  "priority": "low",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "workspacePlan": "free",
  "input": {
    "transcriptionId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "targetLanguage": "ta",
    "sourceLanguage": "en",
    "sourceLayer": "current",
    "options": {
      "diarization": { "enabled": true, "minSpeakers": 1, "maxSpeakers": 4 },
      "voiceAssignments": { "SPEAKER_00": "vce_01HX2K9V3JEK4WYZN6F0XQ2W8M" }
    }
  },
  "resultSummary": {
    "dubbingId": "dub_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "transcriptionId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "translationId": "trl_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "sourceLanguage": "en",
    "targetLanguage": "ta",
    "languageKey": "ta",
    "textPreview": "என்ன விஷயம், FitFam?...",
    "segmentCount": 152,
    "durationSec": 318.08
  },
  "sourceDisplayName": null,
  "webhookUrl": null,
  "currentStage": null,
  "attemptCount": 1,
  "lastError": null,
  "error": null,
  "createdAt": "2026-05-18T12:34:56Z",
  "updatedAt": "2026-05-18T12:45:22Z",
  "startedAt": "2026-05-18T12:34:57Z",
  "completedAt": "2026-05-18T12:45:22Z",
  "userMetadata": { "customerJobRef": "dubbing-demo-ta" }
}

The worker writes the full Dubbing record with artifacts for raw audio, vocals, background, translated speech, final audio mix, and final dubbed video. For the first controlled test, provide manual voice assignments to reuse existing voice profiles.

Automatic voice creation inside the dubbing pipeline is internal to dubbing and does not consume the public voice_creation allowance. If no manual, source-transcript, or auto-created voice is available for a speaker, the worker marks that speaker for manual assignment and skips TTS for the affected segments instead of using a fallback voice.

Parameters

serviceTypestring
Must be dubbing.
input.transcriptionIdstring
Completed source transcript ID (txt_...) to dub. Dubbing uses transcription text sources, not narration sources.
input.targetLanguagestring
Target language code for the dubbed output, for example ta, hi, es, ja, or en-US.
input.sourceLanguagestring, optional
Optional source language code. Omit when it should be inferred from the transcript/default.
input.sourceLayerstring, optional
Optional transcript layer to use: current, glossary, or raw. Defaults to current.
input.translationIdstring, optional
Optional Translation record id to associate with this dubbing request.
input.options.diarization.enabledboolean, optional
Enable speaker diarization and speaker assignment. Defaults to true.
input.options.diarization.minSpeakersinteger, optional
Optional minimum speaker count hint.
input.options.diarization.maxSpeakersinteger, optional
Optional maximum speaker count hint.
input.options.voiceAssignmentsobject, optional
Optional speaker-to-voice mapping. Keys are diarization labels such as SPEAKER_00; values are existing Perchat voice IDs such as vce_....
input.options.voiceCloning.enabledboolean, optional
Enable automatic voice creation from extracted speaker samples when no manual voice assignment is provided. Defaults to true.
input.options.tts.enabledboolean, optional
Enable TTS generation for dubbing segments. Defaults to true.
input.options.tts.modelIdstring, optional
Optional provider TTS model override when supported by the configured voice service.
userMetadataobject, optional
Optional caller-defined metadata echoed on the Job object. Include customerJobRef for stable lookup in job lists.

Returns

Returns the created Job — usually status: queued with resultSummary: null. When complete, resultSummary includes dubbingId, transcriptionId, language info, segment count, and duration. Retrieve the full Dubbing record from the Dubbings API.

See the the job object for attribute details.

Create an indexer job

POST/v1/jobs
Index a completed transcription into the vector store so it can be searched via Semantic search. The worker fetches the transcript segments, splits them into overlapping text chunks, computes dense (OpenAI) and sparse (BM25) embeddings, then upserts all vectors into a hybrid collection scoped to the project.

When to use

  • After transcription completes — use the transcriptionIdfrom the transcription job's resultSummary.

Requires permission

job:create

Single transcript:
{
  "serviceType": "indexer",
  "input": {
    "textSourceIds": [
      "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M"
    ]
  },
  "userMetadata": {
    "customerJobRef": "indexer-01HX2K9V3JEK4WYZ"
  }
}
Batch (up to 50 IDs):
{
  "serviceType": "indexer",
  "input": {
    "textSourceIds": [
      "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
      "txt_02HX2K9V3JEK4WYZN6F0XQ2W8N"
    ]
  },
  "userMetadata": {
    "customerJobRef": "indexer-batch-q3-library"
  }
}
Completed indexer job:
{
  "jobId": "job_02HX2K9V3JEK4WYZN6F0XQ2W8M",
  "serviceType": "indexer",
  "status": "completed",
  "priority": "low",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "input": {
    "textSourceIds": [
      "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M"
    ]
  },
  "resultSummary": {
    "indexedTranscriptions": [
      {
        "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
        "segmentCount": 80,
        "indexedChunks": 8,
        "removedStaleChunks": 0,
        "reindexed": false
      }
    ],
    "collection": "perchat_proj_prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "indexedChunks": 8,
    "denseEngine": "openai/text-embedding-3-large",
    "sparseEngine": "bm25/fastembed",
  },
  "currentStage": null,
  "attemptCount": 0,
  "lastError": null,
  "error": null,
  "createdAt": "2026-05-18T12:40:00Z",
  "updatedAt": "2026-05-18T12:40:06Z",
  "startedAt": "2026-05-18T12:40:01Z",
  "completedAt": "2026-05-18T12:40:06Z",
  "userMetadata": null
}

One collection is maintained per project (perchat_proj_{projectId}). Re-indexing writes new vectors first, then removes the previous index on success. If indexing or cutover fails, the worker rolls back partial new vectors and the job fails with error.code: index_rollback — the prior index remains searchable. Pass input.textSourceIds(array, including one ID). Each ID consumes one indexing credit when the job is created (see Plans & limits).


Transcription reference validation (at create)
  • The API checks that each ID exists in the current project (API key / console scope) and has at least one stored segment before the job is queued. Invalid references are rejected synchronously — no credits are consumed.
  • 404 notFound — unknown ID or transcript in another project. Message: transcription not found: …
  • 422 validationError — transcript exists but has no segments (segmentCount is 0 and no segment payload). Message: transcription txt_01HX… has no segments.
  • error.details[] includes path and issue for integration fixes. See Errors.

Example (not found):

{
  "error": {
    "code": "notFound",
    "message": "transcription not found: txt_01HX…",
    "details": [ { "path": "input.textSourceIds", "issue": "notFound:txt_01HX…" } ]
  }
}

Example (no segments):

{
  "error": {
    "code": "validationError",
    "message": "transcription txt_01HX… has no segments",
    "details": [ { "path": "input.textSourceIds", "issue": "noSegments:txt_01HX…" } ]
  }
}

Parameters

serviceTypestring
Must be indexer.
input.transcriptionIdsarray
One or more transcription IDs to index (txt_...), up to 50 per job. Deduped server-side. Use a one-element array for a single transcript.
userMetadataobject, optional
Optional caller-defined metadata echoed on the Job object. Include customerJobRef so search hits and index inventory can show your reference on each chunk.
userMetadata.customerJobRefstring, optional
Stable reference for this indexer job (max 120 characters). Echoed on search hits and index inventory as customerJobRef. If omitted, the platform may suggest a value such as indexer-{transcriptionIdSuffix}.

Returns

Returns the created Job — usually status: queued. When complete, resultSummary includes transcriptionId, collection, indexedChunks, denseEngine, and sparseEngine.

See the the job object for attribute details.

Create a metadata job

POST/v1/jobs
Generate title, description, IAB categories, and chapter markers from a completed transcript. The worker loads stored segments, calls the configured LLM, and persists results on the transcription. Use the Metadata resource to fetch or edit fields after the job completes.

When to use

  • Prerequisite — a completed transcription job; pass its textSourceId on create.
  • No S3 input — metadata jobs only require input.textSourceId; the worker reads transcript text from the API.

Requires permission

job:create

Request body:
{
  "serviceType": "metadata",
  "input": {
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M"
  },
  "userMetadata": {
    "customerJobRef": "metadata-01HX2K9V3JEK4WYZ"
  }
}
Completed metadata job:
{
  "jobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "serviceType": "metadata",
  "status": "completed",
  "resultSummary": {
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "metadataId": "mdt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "title": "Introduction to Web Application Development",
    "chapterCount": 3,
    "generated": true
  },
  "input": {
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M"
  },
  "createdAt": "2026-05-29T10:00:00Z",
  "startedAt": "2026-05-29T10:00:01Z",
  "completedAt": "2026-05-29T10:00:18Z"
}

One metadata credit is consumed when the job is created (see Plans & limits). LLM model and step timeouts are configured in platform admin (Jobs → Metadata) and snapshotted on the worker at runtime — do not pass model options on create.


Transcription reference validation (at create)
  • The API checks that each ID exists in the current project (API key / console scope) and has at least one stored segment before the job is queued. Invalid references are rejected synchronously — no credits are consumed.
  • 404 notFound — unknown ID or transcript in another project. Message: transcription not found: …
  • 422 validationError — transcript exists but has no segments (segmentCount is 0 and no segment payload). Message: transcription txt_01HX… has no segments.
  • error.details[] includes path and issue for integration fixes. See Errors.

Example (not found):

{
  "error": {
    "code": "notFound",
    "message": "transcription not found: txt_01HX…",
    "details": [ { "path": "input.textSourceId", "issue": "notFound:txt_01HX…" } ]
  }
}

Example (no segments):

{
  "error": {
    "code": "validationError",
    "message": "transcription txt_01HX… has no segments",
    "details": [ { "path": "input.textSourceId", "issue": "noSegments:txt_01HX…" } ]
  }
}

Parameters

serviceTypestring
Must be metadata.
input.textSourceIdstring
Completed text source ID (txt_…) to generate metadata for. Must belong to the same project as the API key.
userMetadataobject, optional
Optional caller-defined metadata echoed on the Job object. Include customerJobRef for stable lookup in job lists.
userMetadata.customerJobRefstring, optional
Stable reference for this metadata job (max 120 characters). Typical pattern: metadata-{textSourceIdSuffix}.

Returns

Returns the created Job — usually status: queued. When complete, resultSummary includes metadataId, title, and chapterCount.

See the the job object for attribute details.

Create a summarization job

POST/v1/jobs
Synthesize a complete Summarization record from a completed transcription and its visual analysis chunks. Requires input.textSourceJobId (job_…).

When to use

  • Prerequisite — a completed transcription job in the same project.
  • No S3 input — the worker reads transcript text and visual chunks from stored records.

Requires permission

job:create

Request body:
{
  "serviceType": "summarization",
  "input": {
    "textSourceJobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M"
  },
  "userMetadata": {
    "customerJobRef": "summarization-job_01HY2K9V3JEK4WYZ"
  }
}
Completed summarization job:
{
  "jobId": "job_01HZ2K9V3JEK4WYZN6F0XQ2W9N",
  "serviceType": "summarization",
  "status": "completed",
  "priority": "low",
  "attemptCount": 0,
  "lastError": null,
  "input": {
    "textSourceJobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M"
  },
  "resultSummary": {
    "summarizationId": "sum_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "shortSynopsisPreview": "A high-energy music video featuring vibrant choreography across neon-lit urban locations..."
  },
  "error": null,
  "startedAt": "2026-05-29T10:00:01Z",
  "completedAt": "2026-05-29T10:00:22Z",
  "processingLeaseUntil": null,
  "cancelRequestedAt": null,
  "cancelRequestedByUserId": null,
  "cancelRequestedByApiKeyId": null,
  "cancelRequestedByLabel": null,
  "cancelRequestedByKind": null,
  "cancelMode": null,
  "currentStage": null,
  "userMetadata": {
    "customerJobRef": "summarization-job_01HY2K9V3JEK4WYZ"
  },
  "webhookUrl": null,
  "workspacePlan": "free",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "apiKeyId": "pak_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "createdAt": "2026-05-29T10:00:00Z",
  "updatedAt": "2026-05-29T10:00:22Z",
  "createdByLabel": "Test Summarization",
  "createdByKind": "api_key",
  "apiKeyLabel": "Test Summarization",
  "isConsoleKey": false,
  "sourceDisplayName": "test"
}

Stored job input.config reflects platform settings at create time (read-only on the job object). Summarization credits are charged when the job is accepted — 1 credit per summarization. Failed jobs after queueing still count. The synthesis model and timeouts are platform-managed — do not pass model options on create.


Transcription reference validation (at create)
  • input.textSourceJobId is required and must match a transcript in this workspace and project:
    • txt_… — lookup by transcription ID → 404 transcription not found: txt_… if missing.
    • job_… — lookup by transcription job ID → 404 transcription not found for job: job_… if missing.
  • 422 validationError when input.textSourceJobId is missing or empty.
  • 422 validationError when the resolved transcript has no segments.
  • error.details[] uses path: textSourceJobId and issue: notFound or noSegments.

Parameters

serviceTypestring
Must be summarization.
input.textSourceJobIdstring
Completed text source job ID (job_…). Must belong to the same project as the API key.
userMetadata.customerJobRefstring, optional
Stable reference for this summarization job (max 120 characters). Typical pattern: summarization-{textSourceJobIdSuffix}.

Returns

Returns the created Job — usually status: queued. When complete, resultSummary includes summarizationId and shortSynopsisPreview. Fetch the full record via GET /v1/summarizations/{summarizationId}.

See the the job object for attribute details.

Create a clips job

POST/v1/jobs
Analyze a long-form video and render short clips in a configurable layout from a completed transcript. The worker uploads MP4 renders to your S3 bucket; Perchat stores clip timing, layout, category, and transcript metadata only. Use Clips to fetch results after the job completes.

When to use

  • Prerequisite — a completed transcription job; pass its textSourceId (txt_…) on create.
  • Policy — call GET /v1/jobs/policy?serviceType=clip_generation before create to read maxClipsDefault, maxClipsCap, supported layouts, fill styles, categories, allowAutoLayout, and whether categoryOverride is allowed.
  • S3 storage connection — use a saved storage connection; the API resolves clip output keys under {defaultOutputPrefix}/{textSourceId}/clips/ unless outputPrefix is set. Source fields can be omitted when textSourceId is set — they are resolved from the transcription job.

Requires permission

job:create

Complete a transcription job first and pass its textSourceId (txt_…) — clip jobs reuse stored segments and never run Whisper.

Client options (all optional — must be allowed on your plan; live allowlists: GET /v1/jobs/policy?serviceType=clip_generation):

  • maxClips — defaults to maxClipsDefault, capped at maxClipsCap (1 credit per clip at job create).
  • layout — one output layout for all clips in the job: vertical_9_16 (short-form vertical), horizontal_in_vertical (landscape in vertical frame), horizontal_16_9 (widescreen), or square_1_1 (square). Omit to use the platform default or auto-select from detected content category when allowAutoLayout is true on your plan.
  • fillStyle — letterbox fill when layout is horizontal_in_vertical: black, blur, or branding. Defaults to the platform render default.
  • categoryOverride — force content type (for example interview, podcast, gaming) instead of auto-detection; skips detect_category when set.

Do not send input.options.analysisvision and category toggles are platform-managed and snapshotted into the stored job at create.

S3 direct — explicit source fields on the storage connection:
{
  "serviceType": "clip_generation",
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "connectionId": "stc_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "bucket": "my-media-bucket",
    "sourceKey": "media/clips/demo.mp4",
    "options": {
      "maxClips": 5,
      "layout": "horizontal_in_vertical",
      "fillStyle": "blur",
      "categoryOverride": "interview"
    }
  },
  "userMetadata": {
    "customerJobRef": "s3_direct-my-media-bucket-clips-demo"
  }
}
S3 direct — source resolved from transcript (only textSourceId required; optional outputPrefix and client options):
{
  "serviceType": "clip_generation",
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "options": {
      "maxClips": 5,
      "layout": "horizontal_in_vertical",
      "fillStyle": "blur"
    }
  },
  "userMetadata": {
    "customerJobRef": "clips-from-txn-demo"
  }
}
Completed clips job:
{
  "jobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "serviceType": "clip_generation",
  "status": "completed",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "resultSummary": {
    "clipGenerationId": "clp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "clipCount": 3,
    "sourceDurationSec": 842.5,
    "transcriptPreview": "Welcome back to the channel…"
  },
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "options": {
      "maxClips": 3,
      "layout": "vertical_9_16",
      "fillStyle": "blur",
      "analysis": { "vision": true, "category": true }
    }
  },
  "createdAt": "2026-05-18T12:34:56Z",
  "startedAt": "2026-05-18T12:34:57Z",
  "completedAt": "2026-05-18T12:45:42Z"
}

Stored job input.options includes resolved client options (maxClips, layout, fillStyle, optional categoryOverride) plus platform-managed analysis.vision and analysis.category (read-only on the job object). Disabled analysis stages appear as skipped in progress[]. The completed ClipGeneration record includes layout, category, and categoryConfidence.

Clips credits are charged when the job is accepted — 1 credit per input.options.maxClips. Failed jobs after queueing still count. Free plan default: 1,000 clip credits per billing period (see Plans & limits).


Transcription reference validation (at create)
  • The API checks that each ID exists in the current project (API key / console scope) and has at least one stored segment before the job is queued. Invalid references are rejected synchronously — no credits are consumed.
  • 404 notFound — unknown ID or transcript in another project. Message: transcription not found: … (clip/thumbnail may also return transcription job not found: job_… if the linked job row is missing).
  • 422 validationError — transcript exists but has no segments (segmentCount is 0 and no segment payload). Message: transcription has no segments.
  • 422 validationError — source transcription job exists but status is not completed (issue: jobNotCompleted on input.textSourceId).
  • error.details[] includes path and issue for integration fixes. See Errors.

Example (not found):

{
  "error": {
    "code": "notFound",
    "message": "transcription not found: txt_01HX…",
    "details": [ { "path": "input.textSourceId", "issue": "notFound:txt_01HX…" } ]
  }
}

Example (no segments):

{
  "error": {
    "code": "validationError",
    "message": "transcription has no segments",
    "details": [ { "path": "input.textSourceId", "issue": "emptySegments" } ]
  }
}

Parameters

serviceTypestring
Must be clip_generation.
input.sourceTypestring, optional
Must be s3_direct when set explicitly.
input.connectionIdstring, optional
Storage connection id (<DocCode>s3_direct</DocCode> only).
input.bucketstring, optional
Bucket on the connection (<DocCode>s3_direct</DocCode> only).
input.sourceKeystring, optional
Object key for the source video (<DocCode>s3_direct</DocCode> only).
input.outputPrefixstring, optional
Optional output location for clip MP4s (s3_direct; API field name: outputPrefix). When omitted, outputs are written under perchat-media-results/{transcriptionId}/clips/ (or {defaultOutputPrefix}/{transcriptionId}/clips/ when the bucket's defaultOutputPrefix is set on the storage connection). Files are placed directly in that folder (e.g. full_text.txt, clip_01.mp4, thumbnail_1.jpg). Set outputPrefix to override with a custom folder path — it is not combined again with defaultOutputPrefix.
input.textSourceIdstring
Required completed transcription id (txt_…) in this project, with segments. Rejected at create with notFound or validationError if invalid (see validation notes on this endpoint). The worker loads stored segments from this text source — Whisper is not run when generating clips. For s3_direct, source connection fields can be omitted; they are resolved from the completed transcription job.
input.options.maxClipsinteger, optional
Clips to generate. Defaults to maxClipsDefault and is capped at maxClipsCap for the workspace plan (live values: GET /v1/jobs/policy?serviceType=clip_generation). Consumes the same number of clip credits at job create (1 credit per clip). Do not send input.options.analysis — vision and category detection are platform-managed (see notes).
input.options.layoutstring, optional
Output layout for all clips in this job. One of vertical_9_16 (short-form vertical), horizontal_in_vertical (landscape in vertical frame), horizontal_16_9 (widescreen), or square_1_1 (square). Omit to use the platform default or auto-select from detected content category. Must appear in supportedLayouts for your plan (GET /v1/jobs/policy?serviceType=clip_generation).
input.options.fillStylestring, optional
Letterbox fill for horizontal_in_vertical layout: black, blur, or branding. Defaults to the platform render default. Must appear in supportedFillStyles for your plan.
input.options.categoryOverridestring, optional
Force content category instead of auto-detection (for example interview, podcast, gaming). Allowed values are listed in supportedCategories when allowCategoryOverride is true on your plan. Skips the detect_category progress stage when set.
userMetadata.customerJobRefstring, optional
Stable reference for filtering jobs (max 120 characters).

Returns

Returns the created Job — usually status: queued. When complete, resultSummary includes clipGenerationId and clipCount; fetch full clip metadata (including resolved layout, category, and per-clip spans) via GET /v1/clip-generations/{clipGenerationId}.

See the the job object for attribute details.

Create a thumbnail job

POST/v1/jobs
Generate up to 3 AI thumbnail images for a completed transcription (input.options.thumbnailCount must be 1–3). The worker reads transcript segments from input.textSourceId, uploads JPEG images to your S3 bucket, and stores metadata on the transcription. Use Thumbnails or GET /v1/transcriptions/{id} to fetch results after the job completes.

When to use

  • Prerequisite — a completed transcription job with segments; pass its transcriptionId. Source video location is resolved from that job when omitted on s3_direct.
  • S3 storage connection — use a saved storage connection; the API resolves thumbnail output keys under {defaultOutputPrefix}/{transcriptionId}/thumbnails/ unless outputPrefix is set.

Requires permission

job:create

Each thumbnail job generates at most 3 JPEG variants. Set input.options.thumbnailCount to 1, 2, or 3 — values above 3 are rejected at validation. Outputs are written to your bucket via the job's storage connection.

Complete a transcription job first and pass its textSourceId — thumbnail jobs reuse stored segments and never run Whisper. Do not send pipeline tuning fields — imageModel, imageQuality (low, medium, high, or auto for gpt-image-1), frame limits, and vision model are configured in platform admin (Jobs → Thumbnail) and injected at job creation. Clients may set input.options.thumbnailCount (1–3), nonAiThumbnailCount, and the output size via ratioWidth / ratioHeight / resolution.

S3 storage connection:
{
  "serviceType": "thumbnail",
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "connectionId": "e7b2c4d8-0000-4000-a000-000000000001",
    "bucket": "my-media-bucket",
    "sourceKey": "media/videos/product-demo.mp4",
    "options": {
      "thumbnailCount": 3
    }
  }
}
S3 direct — source resolved from transcript (only textSourceId required; optional outputPrefix):
{
  "serviceType": "thumbnail",
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "connectionId": "e7b2c4d8-0000-4000-a000-000000000001",
    "bucket": "my-media-bucket",
    "sourceKey": "media/videos/product-demo.mp4",
    "options": {
      "thumbnailCount": 3
    }
  }
}
S3 direct — source resolved from transcript (only transcriptionId required; optional outputPrefix):
{
  "serviceType": "thumbnail",
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "options": {
      "thumbnailCount": 2
    }
  }
}
Completed thumbnail job:
{
  "jobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "serviceType": "thumbnail",
  "status": "completed",
  "resultSummary": {
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M",
    "thumbnailCount": 2,
    "newThumbnailCount": 2,
    "nonAiThumbnailCount": 1,
    "generated": true
  },
  "input": {
    "sourceType": "s3_direct",
    "textSourceId": "txt_01HX2K9V3JEK4WYZN6F0XQ2W8M"
  },
  "createdAt": "2026-05-29T10:00:00Z",
  "startedAt": "2026-05-29T10:00:01Z",
  "completedAt": "2026-05-29T10:03:06Z"
}

Stored job input.options reflects platform thumbnail settings at create time (read-only on the job object), including imageQuality (low | medium | high | auto for gpt-image-1).

Thumbnail credits are charged when the job is accepted — 1 credit per input.options.thumbnailCount (maximum 3 per job). Failed jobs after queueing still count. Free plan default: 100 thumbnail credits per billing period (see Plans & limits).


Transcription reference validation (at create)
  • The API checks that each ID exists in the current project (API key / console scope) and has at least one stored segment before the job is queued. Invalid references are rejected synchronously — no credits are consumed.
  • 404 notFound — unknown ID or transcript in another project. Message: transcription not found: … (clip/thumbnail may also return transcription job not found: job_… if the linked job row is missing).
  • 422 validationError — transcript exists but has no segments (segmentCount is 0 and no segment payload). Message: transcription has no segments.
  • 422 validationError — source transcription job exists but status is not completed (issue: jobNotCompleted on input.textSourceId).
  • error.details[] includes path and issue for integration fixes. See Errors.

Example (not found):

{
  "error": {
    "code": "notFound",
    "message": "transcription not found: txt_01HX…",
    "details": [ { "path": "input.textSourceId", "issue": "notFound:txt_01HX…" } ]
  }
}

Example (no segments):

{
  "error": {
    "code": "validationError",
    "message": "transcription has no segments",
    "details": [ { "path": "input.textSourceId", "issue": "emptySegments" } ]
  }
}

Parameters

serviceTypestring
Must be thumbnail.
input.transcriptionIdstring
Completed transcription to analyze (txt_…) in this project, with segments. Required. Rejected at create if the ID is unknown or has no segments (see validation notes on this endpoint). Source video fields are resolved from the completed transcription job when not supplied.
input.options.thumbnailCountinteger, optional
Number of AI thumbnail variants to generate (1–3, maximum 3 per job). Values > 3 fail validation. Defaults to the platform defaultThumbnailCount for the workspace plan (also capped at 3). One credit per variant.
input.options.nonAiThumbnailCountinteger, optional
Number of non-AI (raw frame) thumbnails to generate (1–5). Defaults to the platform maxNonAiThumbnailCount for the workspace plan. For s3_presigned jobs, provide a matching number of presigned PUT URLs in the outputUploadUrls.nonAi array.
input.options.ratioWidthinteger, optional
Output aspect-ratio width — paired with ratioHeight (e.g. 16:9). The image model is set by your plan: free uses gpt-image-1 (only 1:1, 3:2, or 2:3 at resolution 1024), enterprise uses gpt-image-2 (any ratio up to 3:1). Send all three of ratioWidth, ratioHeight, resolution together, or omit them to use the platform default. Invalid combinations are rejected at job creation.
input.options.ratioHeightinteger, optional
Output aspect-ratio height — paired with ratioWidth. See ratioWidth for the per-plan rules.
input.options.resolutioninteger, optional
Output resolution as the shorter edge in pixels (e.g. 720, 1080, 1440, 2160). The longer edge is derived from the ratio; both are snapped to a multiple of 16. On gpt-image-2 the resulting WxH must have edges ≤ 3840, an aspect ratio ≤ 3:1, and total pixels between 655,360 and 8,294,400 (~0.66–8.29 MP); on gpt-image-1 the only valid value is 1024. Defaults to the platform setting for the plan.
input.sourceTypestring, optional
Must be s3_direct when set explicitly.
input.connectionIdstring, optional
Storage connection id (s3_direct only).
input.bucketstring, optional
Bucket on the connection (<DocCode>s3_direct</DocCode> only).
input.sourceKeystring, optional
Object key for the source video (<DocCode>s3_direct</DocCode> only).
input.outputPrefixstring, optional
Optional output location for thumbnail JPEGs (s3_direct; API field: outputPrefix). When omitted, outputs are written under perchat-media-results/{transcriptionId}/thumbnails/ (or {defaultOutputPrefix}/{transcriptionId}/thumbnails/ when the bucket's defaultOutputPrefix is set on the storage connection). Files are placed directly in that folder (e.g. full_text.txt, clip_01.mp4, thumbnail_1.jpg). Set outputPrefix to override with a custom folder path — it is not combined again with defaultOutputPrefix.

Returns

Returns the created Job — usually status: queued. When complete, resultSummary includes textSourceId, thumbnailCount (accumulated total), newThumbnailCount (added by this run), nonAiThumbnailCount, and generated. Re-running for the same textSourceId adds new, visually different thumbnails to the existing set (it does not replace them). Fetch full image metadata via GET /v1/transcriptions/{transcriptionId} or GET /v1/thumbnails/{transcriptionId}.

See the the job object for attribute details.

Retrieve a job

GET/v1/jobs/{jobId}
Returns one job with live status, currentStage, retry counters, errors, and — when complete — a service-specific resultSummary (transcription, clips, thumbnail, or metadata). Stage timelines are not included — use GET /v1/jobs/{jobId}/progress from a console session for progress[] and attemptHistory[].

When to use

  • Polling loop after create — call every few seconds until status is terminal (completed, failed, or cancelled). Keep polling while cancelling.
  • Lightweight polling — use status and currentStage until terminal. For a full stage timeline, call GET /v1/jobs/{jobId}/progress (console session only).
  • Job detail page — show attribution (createdByLabel, apiKeyLabel), retry history, and terminal error.
  • Does not return segment text — use GET /v1/transcriptions/{transcriptionId} once resultSummary.textSourceId is set, GET /v1/clip-generations/{clipGenerationId} once resultSummary.clipGenerationId is set, GET /v1/thumbnails/{transcriptionId} once resultSummary.thumbnailCount is set, or GET /v1/transcriptions/{transcriptionId}/metadata once resultSummary.metadataId is set.

Requires permission

job:read

Terminal failure example (duration_exceeded on download):
{
  "jobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "serviceType": "transcription",
  "status": "failed",
  "priority": "low",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "input": {
    "sourceType": "s3_direct",
    "connectionId": "e7b2c4d8-0000-4000-a000-000000000001",
    "bucket": "my-bucket",
    "sourceKey": "media/clips/demo.mp4",
    "options": {
        "language": "en"
      }
  },
  "resultSummary": null,
  "currentStage": null,
  "attemptCount": 1,
  "lastError": {
  "at": "2026-05-18T12:34:58Z",
  "message": "Video duration 7200.0s exceeds limit 3600s (checked at local_probe)"
},
  "error": {
  "code": "duration_exceeded",
  "message": "Video duration 7200.0s exceeds limit 3600s (checked at local_probe)"
},
  "createdAt": "2026-05-18T12:34:56Z",
  "updatedAt": "2026-05-18T12:34:58Z",
  "startedAt": "2026-05-18T12:34:57Z",
  "completedAt": "2026-05-18T12:34:58Z",
  "userMetadata": null
}

Parameters

jobIdstring
Job identifier.

Returns

Returns a Job object.

See the the job object for attribute details.

Retrieve job progress

GET/v1/jobs/{jobId}/progress
Returns the append-only progress[] timeline, currentStage, and attemptHistory[] for one job. Console session only — project API keys receive 403. Use this from the Perchat console or other browser-authenticated tooling; API integrations should poll GET /v1/jobs/{jobId} for status and currentStage instead.

When to use

  • Job detail timeline in the console — render per-stage events and prior failed attempts.
  • Not for API-key polling — unavailable to project keys by design.

Requires permission

job:read

Parameters

jobIdstring
Job identifier.

Returns

Returns progress, currentStage, and attemptHistory.

See the the job object for attribute details.

List all jobs

GET/v1/jobs
Paginated jobs for the project bound to your API key. Returns full Job objects (same shape as retrieve).

When to use

  • Jobs table / history in a custom dashboard — filter by statuses, sort by created.
  • Operations monitoring — find stuck processing jobs or recent failed runs.
  • Search by creator (user), source URL (input), job id substring, or your reference (customerJobRef).
  • Prefer retrieve when you already have a jobId from create — list is for browse/search, not polling a single in-flight job.

Requires permission

job:list

Query parameters

pageinteger, optional
1-indexed page. Default 1.
limitinteger, optional
Page size. Default 20, max 100.
jobIdstring, optional
Filter by job ID substring.
customerJobRefstring, optional
Filter by exact userMetadata.customerJobRef value from job creation.
userstring, optional
Filter by creator (@username, API key name, or key id substring).
inputstring, optional
Filter by source URL or input metadata substring.
statusesstring, optional
Comma-separated statuses (e.g. pending,queued,processing,cancelling,cancelled).
serviceTypesstring, optional
Comma-separated service types (e.g. transcription,clip_generation,indexer).
sortstring, optional
jobId, service, status, user, or created (default).
sortDirstring, optional
asc or desc. Default desc.

Returns

data array of Job objects; pagination in metadata.pagination.

See the the job object for attribute details.

Cancel a job

POST/v1/jobs/{jobId}/cancel
Cancels a job that has not finished. pending and queued jobs become cancelled immediately. A processing job enters cancelling first; the worker stops after the current pipeline stage, then the job becomes cancelled. Attribution fields record who requested cancellation. Idempotent for cancelling and cancelled.

When to use

  • User abandons work while pending, queued, or mid-run.
  • Cannot cancel completed or failed jobs (409 conflict).
  • Graceful stop — a processing job becomes cancelling until the worker finishes the current stage, then cancelled.

Requires permission

job:cancel

Immediate cancel (pending / queued) returns status: cancelled with completedAt set.

Graceful cancel while processing returns status: cancelling first — poll retrieve until cancelled:
{
  "jobId": "job_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "serviceType": "transcription",
  "status": "cancelling",
  "priority": "low",
  "workspaceId": "wsp_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "projectId": "prj_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "input": {
    "sourceType": "s3_direct",
    "connectionId": "e7b2c4d8-0000-4000-a000-000000000001",
    "bucket": "my-bucket",
    "sourceKey": "media/clips/demo.mp4",
    "options": {
        "language": "en"
      }
  },
  "resultSummary": null,
  "currentStage": "separate_vocals",
  "attemptCount": 0,
  "lastError": null,
  "error": null,
  "cancelRequestedAt": "2026-05-18T12:35:05Z",
  "cancelRequestedByUserId": "usr_01HX2K9V3JEK4WYZN6F0XQ2W8M",
  "cancelRequestedByApiKeyId": null,
  "cancelRequestedByLabel": "@vishnu · Console",
  "cancelRequestedByKind": "console",
  "cancelMode": "graceful",
  "createdAt": "2026-05-18T12:34:56Z",
  "updatedAt": "2026-05-18T12:35:05Z",
  "startedAt": "2026-05-18T12:34:57Z",
  "completedAt": null,
  "userMetadata": null
}

Parameters

jobIdstring
Job identifier.

Returns

Returns the Job with status cancelled or cancelling.

See the the job object for attribute details.