> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nanoclip.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Analysis jobs

> Start transcript, vision, and retake-removal commands, then poll for results.

Every analysis starts from an uploaded source. After upload completion, transcript, vision, and retake-removal commands are asynchronous: a start request returns quickly, and the result endpoint returns the current status plus any completed outputs.

You can upload **video** or **audio**. Audio sources are transcript-only — `vision` and `retake-removal` return `422 Unprocessable Entity`. Set `content_type` to the audio type you are uploading (e.g. `audio/mpeg`); see [Allowed upload content types](/limits#allowed-upload-content-types). For audio, the upload-complete response reports `width` and `height` as `null` and a `source.media_kind` of `audio`.

## 1. Upload source video or audio

Create an upload project:

```bash theme={null}
curl -X POST "https://api.nanoclip.ai/v1/projects/upload" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "demo.mp4",
    "content_type": "video/mp4"
  }'
```

The response includes:

| Field                       | Description                                                                                                                               |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `project_id`                | Project ID used for transcript and vision requests.                                                                                       |
| `source`                    | Customer-facing metadata for the uploaded source, such as filename, content type, `media_kind` (`video` or `audio`), and size when known. |
| `upload_url`                | Signed URL that accepts the source bytes (video or audio).                                                                                |
| `upload_headers`            | Headers to include when uploading.                                                                                                        |
| `upload_expires_in_seconds` | Time until the signed URL expires.                                                                                                        |

Upload the source bytes with the `content_type` you declared (the create response echoes it in `upload_headers`), then complete the upload:

```bash theme={null}
# CONTENT_TYPE must match the content_type you declared above (e.g. video/mp4 or audio/mpeg)
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: $CONTENT_TYPE" \
  --data-binary "@demo.mp4"

curl -X POST "https://api.nanoclip.ai/v1/projects/proj_abc123/upload/complete" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY"
```

Analysis commands return `409 Conflict` until upload completion succeeds. The completed upload response includes source metadata so you can confirm which source the project points to. For audio, `width` and `height` are `null` and `source.media_kind` is `"audio"`.

`GET /v1/projects/{project_id}` returns the same billing-basis fields — `duration`, `source_size_bytes`, `width`, `height` — so you can re-check them at any point in a project's life without repeating the upload/complete call:

```bash theme={null}
curl "https://api.nanoclip.ai/v1/projects/proj_abc123" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY"
# -> { "status": "ready", "duration": 612.3, "source_size_bytes": 48213099,
#      "width": 1920, "height": 1080, "source": { ... } }
```

All four are `null` until upload/complete has probed the source; `width` and `height` stay `null` for audio.

### Audio example (transcript-only)

Upload an mp3 and transcribe it. Audio sources accept transcript only — `vision` and `retake-removal` return `422`.

```bash theme={null}
# 1. create the upload project with an audio content type
curl -X POST "https://api.nanoclip.ai/v1/projects/upload" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "episode.mp3", "content_type": "audio/mpeg" }'

# 2. PUT the bytes with the matching audio content type
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: audio/mpeg" \
  --data-binary "@episode.mp3"

# 3. complete — width/height come back null for audio
curl -X POST "https://api.nanoclip.ai/v1/projects/proj_abc123/upload/complete" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY"
# -> { "status": "ready", "duration": 612.3, "width": null, "height": null,
#      "source": { "media_kind": "audio", ... } }
```

Then start a transcript exactly as below (`vision` / `retake-removal` are not available for audio).

## 2. Transcript

Start transcript analysis after the project has a completed source upload.

```bash theme={null}
curl -X POST "https://api.nanoclip.ai/v1/projects/proj_abc123/transcript" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "language": "en",
    "requested_outputs": ["text", "speakers", "utterances", "words"]
  }'
```

```json theme={null}
{
  "project_id": "proj_abc123",
  "analysis": "transcript",
  "status": "queued",
  "requested_outputs": ["text", "speakers", "utterances", "words"],
  "created_at": "2026-05-18T10:05:00Z"
}
```

Poll the transcript result endpoint:

```bash theme={null}
curl "https://api.nanoclip.ai/v1/projects/proj_abc123/transcript" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY"
```

Completed transcript responses include agent context for timestamp units, speaker IDs, and word index ranges.

```json theme={null}
{
  "project_id": "proj_abc123",
  "analysis": "transcript",
  "status": "completed",
  "language": "en",
  "agent_context": {
    "time_unit": "seconds",
    "time_origin": "start of source video",
    "speaker_ids": "stable within this transcript response",
    "word_indices": "word_start_idx is inclusive; word_end_idx is exclusive"
  },
  "text": "Welcome to the demo...",
  "words": [
    {
      "text": "Welcome",
      "start": 0.0,
      "end": 0.42,
      "speaker": 0,
      "confidence": 0.98
    }
  ],
  "utterances": [
    {
      "speaker": 0,
      "start": 0.0,
      "end": 2.1,
      "text": "Welcome to the demo...",
      "word_start_idx": 0,
      "word_end_idx": 5
    }
  ],
  "speakers": [
    {"speaker": 0, "total_duration": 12.4, "utterance_count": 3}
  ]
}
```

### Transcript language

`language` is required. Use one of the supported base tags.

Supported base tags:

| Value | Language   |
| ----- | ---------- |
| `bg`  | Bulgarian  |
| `cs`  | Czech      |
| `da`  | Danish     |
| `de`  | German     |
| `el`  | Greek      |
| `en`  | English    |
| `es`  | Spanish    |
| `et`  | Estonian   |
| `fi`  | Finnish    |
| `fr`  | French     |
| `he`  | Hebrew     |
| `hr`  | Croatian   |
| `hu`  | Hungarian  |
| `it`  | Italian    |
| `lt`  | Lithuanian |
| `lv`  | Latvian    |
| `mt`  | Maltese    |
| `nl`  | Dutch      |
| `pl`  | Polish     |
| `pt`  | Portuguese |
| `ro`  | Romanian   |
| `ru`  | Russian    |
| `sk`  | Slovak     |
| `sl`  | Slovenian  |
| `sv`  | Swedish    |
| `uk`  | Ukrainian  |

Unsupported language tags return `422 Unprocessable Entity`.

### Speaker diarization

Pass `diarize: true` in the transcript request to run speaker diarization. It is optional and defaults to `false`.

```json theme={null}
{
  "language": "en",
  "requested_outputs": ["text", "speakers", "utterances", "words"],
  "diarize": true
}
```

Speaker data is only returned when `diarize: true`. With diarization off (the default), the
`speakers` array is empty and the `speaker` field is omitted from `words` and `utterances` — you
still get `text`, word timings, and utterance segmentation. Request `diarize: true` to receive
per-word speaker labels and speaker summaries.

Diarization is billed separately as an add-on to the transcript charge. See [Credit billing](/limits#credit-billing) for the current rate.

## 3. Vision

Start vision analysis with the outputs your app needs.

```bash theme={null}
curl -X POST "https://api.nanoclip.ai/v1/projects/proj_abc123/vision" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requested_outputs": ["faces", "face_tracks", "scenes"]
  }'
```

Poll the vision result endpoint:

```bash theme={null}
curl "https://api.nanoclip.ai/v1/projects/proj_abc123/vision" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY"
```

Completed vision responses include agent context, face observations over time, track summaries, scene timestamps, and an `asd` record describing whether active-speaker detection ran.

```json theme={null}
{
  "project_id": "proj_abc123",
  "analysis": "vision",
  "status": "completed",
  "error": null,
  "agent_context": {
    "coordinate_system": "normalized [x1, y1, x2, y2] in source video frame",
    "time_unit": "seconds",
    "scene_timestamp": "scene start time after t=0",
    "track_ids": "stable within this vision response"
  },
  "faces": [
    {
      "t": 0.0,
      "detections": [
        {
          "box": [0.2109, 0.2861, 0.2719, 0.4111],
          "score": 0.91,
          "track_id": 0,
          "cluster_id": 0
        }
      ]
    }
  ],
  "face_tracks": [
    {
      "track_id": 0,
      "cluster_id": 0,
      "frame_count": 13,
      "start_t": 0.0,
      "end_t": 2.4024,
      "median_box_area": 0.0067
    }
  ],
  "scenes": [
    {"timestamp": 2.6026}
  ],
  "asd": {
    "ran": true,
    "reason": null,
    "assigned": 12,
    "carried": 340
  }
}
```

### Active-speaker detection (`asd`)

When active-speaker detection scores a face, that detection carries a `speak_conf`
between 0 and 1. Detections that were never scored **omit the field entirely** — so
without `asd`, "active-speaker detection never ran", "it ran and scored nothing" and
"it failed" all look identical in the response.

The `asd` object tells them apart:

| Field      | Meaning                                                  |
| ---------- | -------------------------------------------------------- |
| `ran`      | Whether active-speaker detection executed for this job.  |
| `reason`   | Why it did not run, when `ran` is `false`.               |
| `assigned` | How many tracks it scored.                               |
| `carried`  | How many face detections carry a resulting `speak_conf`. |

How to read it:

* **`ran: true`, `carried > 0`** — active-speaker confidences are present; use them.
* **`ran: true`, `carried: 0`** — it ran and scored nothing on this footage.
* **`ran: false`** — it never ran, and `reason` says why.
* **`asd` absent (`null`)** — the job predates this record. Absent is not the same
  answer as `ran: false`; do not treat them as equivalent.

<Note>
  Active-speaker detection needs at least two simultaneously tracked faces to compare.
  Footage with a single speaker on screen therefore produces no `speak_conf`, and that
  is expected rather than a failure — `asd` is how you tell the difference.
</Note>

## 4. Retake removal

Retake removal requires a `language`, one of the same [languages as transcript](#transcript-language). It analyzes transcript-like word timing and returns the word spans to remove plus keep intervals for a clean read.

```bash theme={null}
curl -X POST "https://api.nanoclip.ai/v1/projects/proj_abc123/retake-removal" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "language": "en"
  }'
```

```json theme={null}
{
  "project_id": "proj_abc123",
  "analysis": "retake_removal",
  "status": "queued",
  "created_at": "2026-06-08T10:05:00Z"
}
```

Poll the retake-removal result endpoint:

```bash theme={null}
curl "https://api.nanoclip.ai/v1/projects/proj_abc123/retake-removal" \
  -H "Authorization: Bearer $NANOCLIP_API_KEY"
```

Completed responses include the original words, removal spans, and the keep intervals.

```json theme={null}
{
  "project_id": "proj_abc123",
  "status": "completed",
  "error": null,
  "words": [
    {
      "i": 0,
      "start": 0.0,
      "end": 0.42,
      "text": "Welcome",
      "confidence": 0.98,
      "source": "transcript"
    }
  ],
  "remove_spans": [
    {
      "remove_word_start": 12,
      "remove_word_end": 18
    }
  ],
  "keep_intervals": [
    {
      "start": 0.0,
      "end": 7.2
    }
  ]
}
```

## Requested outputs

| Analysis       | Output           | Description                                                                                      |
| -------------- | ---------------- | ------------------------------------------------------------------------------------------------ |
| Transcript     | `text`           | Combined transcript text.                                                                        |
| Transcript     | `words`          | Word-level transcript entries with `text`, `start`, `end`, `speaker`, and optional `confidence`. |
| Transcript     | `utterances`     | Speaker turns with `speaker`, `start`, `end`, `text`, `word_start_idx`, and `word_end_idx`.      |
| Transcript     | `speakers`       | Speaker summaries with `speaker`, `total_duration`, and `utterance_count`.                       |
| Vision         | `faces`          | Individual detected face observations.                                                           |
| Vision         | `face_tracks`    | Face observations grouped across time.                                                           |
| Vision         | `scenes`         | Scene-level visual segments.                                                                     |
| Retake removal | `words`          | Word-level inputs used by the retake-removal model.                                              |
| Retake removal | `remove_spans`   | Word index ranges that should be removed.                                                        |
| Retake removal | `keep_intervals` | Time ranges to keep after removing retakes.                                                      |

The transcript and vision outputs above are the values accepted in each command's `requested_outputs`. Retake removal has no `requested_outputs` selector and always returns `words`, `remove_spans`, and `keep_intervals`. `agent_context` and the vision response's [`asd`](#active-speaker-detection-asd) record are not selectable — they are always included in completed responses.

## Polling pattern

Poll until `status` is `completed` or `failed`.

```bash theme={null}
while true; do
  response=$(curl -s "https://api.nanoclip.ai/v1/projects/proj_abc123/transcript" \
    -H "Authorization: Bearer $NANOCLIP_API_KEY")

  echo "$response"
  status=$(echo "$response" | jq -r '.status')

  if [ "$status" = "completed" ] || [ "$status" = "failed" ]; then
    break
  fi

  sleep 5
done
```

If an analysis is already `queued`, `processing`, or `completed`, starting the same analysis again returns the existing analysis response.
