> For the documentation index, fetch https://sync.so/docs/llms.txt. Append .md to a page URL for Markdown. Documentation-search MCP: https://sync.so/docs/_mcp/server.

# Get speaker analysis

GET https://api.sync.so/v2/transcriptions/{id}/speaker-analysis

Returns which face belongs to each transcript speaker. Each speaker is matched, with an avatar, evidence and a face track, or uncertain, with a reason. Analysis may already be available for video transcriptions of your own media. Read this endpoint before starting an analysis. Poll while `status` is `processing`, until it is `completed` or `unavailable`. Requires a completed transcription.

Reference: https://sync.so/docs/api-reference/api/transcriptions-api/get-speaker-analysis

## Authentication

- `x-api-key` header (required) — API Key authentication via header

## Servers

- `https://api.sync.so` (Default, default)
- `https://dev-api.sync.so` (dev)
- `http://localhost:3001` (local)

## Request

### Path parameters

- `id` (string, required) — Transcription job id.

## Response

### 200

Speaker analysis retrieved successfully

- `revision` (string, required) — The transcription revision this analysis belongs to.
- `sourceStartMs` (integer, required) — Start of the analyzed window, in source milliseconds.
- `status` (enum, required) — Status of the speaker analysis. processing is not terminal; completed and unavailable are.
  - Allowed values: `processing`, `completed`, `unavailable`
- `speakers` (list of SpeakerAnalysisSpeaker, required) — One entry per transcript speaker, up to 8.
- `sourceVideoUrl` (string, optional) — The analyzed source video. Null for jobs created from sourceAudioUrl.
- `sourcePlaybackUrl` (string, optional) — A temporary URL for playing the source video.
- `sourceEndMs` (integer, optional) — End of the analyzed window, in source milliseconds. Null when the window runs to the end of the source.
- `reason` (string, optional) — Why the analysis is unavailable or wasn't run.
- `model` (string, optional) — The model that ran the analysis.

## Errors

### 401 Unauthorized Error

Unauthorized - Invalid or missing authentication

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `billingUrl` (string, optional) — Billing page for resolving a payment, plan, or allowance issue, when applicable.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission or a DUPLICATE_SUBMISSION. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.
- `lineNumber` (integer, optional) — For batch creation, on dialogue edit refusals only: the 1-based line of the refused record, counting non-empty JSONL lines. With a `rows` body, line N is `rows[N-1]`. Other record errors name the line only in `message`.

### 404 Not Found Error

Job not found

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `billingUrl` (string, optional) — Billing page for resolving a payment, plan, or allowance issue, when applicable.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission or a DUPLICATE_SUBMISSION. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.
- `lineNumber` (integer, optional) — For batch creation, on dialogue edit refusals only: the 1-based line of the refused record, counting non-empty JSONL lines. With a `rows` body, line N is `rows[N-1]`. Other record errors name the line only in `message`.

### 409 Conflict Error

Conflict - The resource is in a state that does not allow the requested action (e.g. deleting a generation that is still processing). For creation, generation_conflict indicates a duplicate internal backend submission. Keyed creates return IDEMPOTENCY_KEY_CONFLICT for a changed payload or IDEMPOTENCY_IN_PROGRESS while the original request prepares or the previous attempt is still being finalized (Retry-After: 2). Retry only with the same key and original inputs after an ambiguous network failure. An API-key create without Idempotency-Key that is identical to one your organization sent in the last 60 seconds returns DUPLICATE_SUBMISSION with the original generationId once it was accepted; poll that generation, or send a unique Idempotency-Key with each intended generation. See [Idempotent Requests](/docs/api-reference/guides/idempotency).

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `billingUrl` (string, optional) — Billing page for resolving a payment, plan, or allowance issue, when applicable.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission or a DUPLICATE_SUBMISSION. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.
- `lineNumber` (integer, optional) — For batch creation, on dialogue edit refusals only: the 1-based line of the refused record, counting non-empty JSONL lines. With a `rows` body, line N is `rows[N-1]`. Other record errors name the line only in `message`.

### 500 Internal Server Error

Internal Server Error - An unexpected failure on our side. The body carries errorCode internal_error and a requestId; include the requestId when contacting support.

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `billingUrl` (string, optional) — Billing page for resolving a payment, plan, or allowance issue, when applicable.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission or a DUPLICATE_SUBMISSION. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.
- `lineNumber` (integer, optional) — For batch creation, on dialogue edit refusals only: the 1-based line of the refused record, counting non-empty JSONL lines. With a `rows` body, line N is `rows[N-1]`. Other record errors name the line only in `message`.

## Types

### SpeakerAnalysisSpeaker

One transcript speaker and the face matched to them.

- `id` (string, required) — Speaker id from the transcript.
- `status` (enum, required) — Whether the analysis matched the speaker to one face (matched) or could not decide (uncertain).
  - Allowed values: `matched`, `uncertain`
- `reason` (string, optional) — Why the speaker is uncertain.
- `avatar` (SpeakerAvatar, optional) — Present only for a matched speaker.
- `faceTrack` (SpeakerFaceTrack, optional) — Present only for a matched speaker. Cut one segment from it with POST /v2/transcriptions/\{id}/speaker-analysis/face-track-slices.
- `evidence` (SpeakerMatchEvidence, optional) — How strongly the matched face was talking during the speaker's words.
- `faceSupport` (list of SpeakerFaceSupport, optional) — The two faces with the most support, the leading one first.

### GenerationErrorMessage

A message describing the error.

- string
- list of string

### DialogueEditRetimeFailureSection

The dialogue-edit section that removes too much speech to stay in sync.

- `slotIndex` (integer, required) — Zero-based index of the section in the preview's `resultSlots`.
- `sourceStartMs` (integer, optional) — Start of the section in the source video, in absolute milliseconds.
- `sourceDurationMs` (integer, optional) — Length of the section in the source video, in milliseconds.

### SpeakerAvatar

A frame where the matched speaker's face is visible, with its box.

- `sourceTimeMs` (double, required) — Time of the frame, in source milliseconds.
- `box` (SpeakerAvatarBox, required) — A face box as fractions (0 to 1) of the source frame width and height, measured from the top-left corner.
- `sourceWidth` (integer, required) — Source frame width, in pixels.
- `sourceHeight` (integer, required) — Source frame height, in pixels.

### SpeakerFaceTrack

The matched speaker's face across the whole source file. url points to a JSON file with a bounding_boxes array: one [x1, y1, x2, y2] pixel box per frame at fps, counted from the start of the file, or null where the face isn't on screen.

- `url` (string, required) — URL of the face track file.
- `fps` (double, required) — Frame rate of the face track.
- `frameCount` (integer, required) — Number of frames in the face track.
- `sourceWidth` (integer, required) — Source frame width, in pixels.
- `sourceHeight` (integer, required) — Source frame height, in pixels.

### SpeakerMatchEvidence

How strongly the matched face was talking during the speaker's words.

- `supportMs` (double, required) — Milliseconds of the speaker's speech during which the face was talking.
- `margin` (double, required) — Lead of the matched face over the next most likely face.
- `meanScore` (double, optional) — Mean active speaker score. Null when no score is available.
- `coverage` (double, optional) — Share of the speaker's speech the matched face covers. Can exceed 1, because talking spans run past word edges.

### SpeakerFaceSupport

A face seen talking during the speaker's speech, and when.

- `identity` (string, required) — Identifier of the detected face.
- `supportMs` (double, required) — Milliseconds of the speaker's speech during which this face was talking.
- `intervals` (list of SpeakerFaceSupportInterval, required) — Stretches of the speaker's speech during which this face was talking.
- `meanScore` (double, optional) — Mean active speaker score. Null when no score is available.

### SpeakerAvatarBox

A face box as fractions (0 to 1) of the source frame width and height, measured from the top-left corner.

- `x` (double, required)
- `y` (double, required)
- `width` (double, required)
- `height` (double, required)

### SpeakerFaceSupportInterval

- `startMs` (double, required) — Start, in source milliseconds.
- `endMs` (double, required) — End, in source milliseconds.

## Examples

**Response**

```json
{
  "revision": "3f9a1c0e7b2d4a6f8e1c3b5d7f9a0c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f2a",
  "sourceStartMs": 0,
  "status": "completed",
  "speakers": [
    {
      "id": "speaker_0",
      "status": "matched",
      "avatar": {
        "sourceTimeMs": 4200,
        "box": {
          "x": 0.18,
          "y": 0.22,
          "width": 0.14,
          "height": 0.25
        },
        "sourceWidth": 1920,
        "sourceHeight": 1080
      },
      "faceTrack": {
        "url": "https://assets.sync.so/docs/example-face-track.json",
        "fps": 25,
        "frameCount": 750,
        "sourceWidth": 1920,
        "sourceHeight": 1080
      }
    },
    {
      "id": "speaker_1",
      "status": "uncertain",
      "reason": "insufficient_support"
    }
  ],
  "sourceVideoUrl": "https://assets.sync.so/docs/example-video.mp4",
  "sourceEndMs": 30000
}
```

**SDK Code**

```python
import requests

url = "https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis"

headers = {"x-api-key": "<apiKey>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis';
const options = {method: 'GET', headers: {'x-api-key': '<apiKey>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("x-api-key", "<apiKey>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<apiKey>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis")
  .header("x-api-key", "<apiKey>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis', [
  'headers' => [
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis");
var request = new RestRequest(Method.GET);
request.AddHeader("x-api-key", "<apiKey>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["x-api-key": "<apiKey>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.sync.so/v2/transcriptions/6533643b-aceb-4c40-967e-d9ba9baac39e/speaker-analysis")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```