# Using the AssemblyAI SDK

Change apiKey and baseUrl and your existing AssemblyAI code runs on Sandchest. Here is exactly which endpoints and options are supported, and which are rejected.

Sandchest speaks the AssemblyAI prerecorded API at `/v2/*`. If you already have
AssemblyAI code, keep the official SDK and change two settings. This page shows the
swap in TypeScript and Python, then lists precisely what works, what is refused, and
where the behaviour differs.

## The swap

### TypeScript

```ts title="transcribe.ts"
import { AssemblyAI } from "assemblyai";

const client = new AssemblyAI({
  apiKey: process.env.SANDCHEST_API_KEY as string,
  baseUrl: "https://stt-api.sandchest.com",
});

const transcript = await client.transcripts.transcribe({
  audio: audioBuffer,
  disfluencies: true,
});

console.log(transcript.speech_model_used);
console.log(transcript.words);
```

### Python

```python title="transcribe.py"
import os
import assemblyai as aai

aai.settings.api_key = os.environ["SANDCHEST_API_KEY"]
aai.settings.base_url = "https://stt-api.sandchest.com"

transcript = aai.Transcriber().transcribe("meeting.mp3")

print(transcript.text)
for word in transcript.words:
    print(word.start, word.end, word.text)
```

That is the whole migration. Uploads, transcript creation, polling, listing,
deletion, word search, sentences, paragraphs, SRT and VTT all go through unchanged.

## Supported endpoints

| Method | Path | Notes |
| --- | --- | --- |
| `POST` | `/v2/upload` | Raw bytes in the body |
| `POST` | `/v2/transcript` | `Idempotency-Key` accepted |
| `GET` | `/v2/transcript/:id` | Returns the current state immediately |
| `GET` | `/v2/transcript` | `limit`, `status`, `created_on`, `before_id`, `after_id` |
| `DELETE` | `/v2/transcript/:id` | Erases words, text and source URLs |
| `GET` | `/v2/transcript/:id/sentences` | Completed transcripts only |
| `GET` | `/v2/transcript/:id/paragraphs` | Completed transcripts only |
| `GET` | `/v2/transcript/:id/word-search` | `words=` required |
| `GET` | `/v2/transcript/:id/srt` | `chars_per_caption` supported |
| `GET` | `/v2/transcript/:id/vtt` | `chars_per_caption` supported |

Those ten paths are the complete surface. Streaming/real-time transcription, LeMUR,
and the AssemblyAI audio-intelligence endpoints are not implemented. Full details in
the [API reference](/docs/api).

## Supported options

`audio_url`, `speech_model`, `speech_models`, `language_code`, `language_detection`,
`language_confidence_threshold`, `language_detection_options`, `punctuate`,
`format_text`, `disfluencies`, `custom_spelling`, `multichannel`, `audio_start_from`,
`audio_end_at`, `webhook_url`, `webhook_auth_header_name` and
`webhook_auth_header_value`.

Every field is documented in [Transcript options](/docs/transcripts).

## Options that are rejected

Sandchest never acknowledges a request whose options it silently dropped. If it
cannot do the thing you asked for, it says so with HTTP 400 and this exact body
shape:

| You sent | Response |
| --- | --- |
| `speaker_labels: true` or any `speakers_expected` | `{"error":"Speaker identification is not supported."}` |
| a non-empty `word_boost`, or any `boost_param` | `{"error":"Custom word boosting is not supported."}` |
| any option Sandchest does not implement | `{"error":"The request contains an unsupported option or an invalid option value."}` |

That last row is the one to plan for. The AssemblyAI audio-intelligence options —
`summarization`, `auto_chapters`, `auto_highlights`, `entity_detection`,
`content_safety`, `iab_categories`, `sentiment_analysis`, `redact_pii`,
`dual_channel`, `language_codes` and friends — are not in Sandchest's schema, so a
request carrying them is refused rather than quietly stripped. Remove them before
you switch a workload over.

> [!NOTE]
> `speaker_labels: false`, `speakers_expected: null` and an empty `word_boost: []`
> are all accepted: they ask for nothing, so there is nothing to refuse.

The language options have their own rules:

| You sent | Response |
| --- | --- |
| no `language_code` and `language_detection: false` | `` {"error":"Either `language_detection` must be set to True, or one of `language_code` or `language_codes` must must be specified."} `` |
| a `language_code` and `language_detection: true` | `` {"error":"`language_detection` is not available when `language_code` is specified."} `` |
| `language_confidence_threshold` without detection | `{"error":"language_confidence_threshold requires language_detection."}` |

Omit both and you get automatic detection, which is what most callers want. See
[Languages](/docs/languages).

## Differences to expect

**`GET` returns immediately.** AssemblyAI's `GET /v2/transcript/:id` and Sandchest's
both return the current state without waiting. The SDK's `transcribe()` and
`waitUntilReady()` own the polling loop, at their configured interval. Nothing to
change — just do not write code that assumes the request blocks until completion.

**`speech_model_used` tells the truth.** You can keep sending AssemblyAI model names
in `speech_models` or `speech_model` for compatibility; they are accepted and echoed
back in the response's `speech_models`. `speech_model_used` reports the model that
genuinely produced the transcript, for example `nvidia/parakeet-tdt-0.6b-v2`.

**`disfluencies` defaults to `false`.** New transcripts drop English filled pauses
("um", "uh") unless you ask for them. Send `disfluencies: true` for verbatim output.

**Deletion is a tombstone.** `DELETE` erases words, text, source URLs, custom
vocabulary and private error detail, then returns an object with
`status: "completed"`, `text: "Deleted by user."` and `audio_url:
"http://deleted_by_user"`. Usage and billing history survive.

**Unknown transcript ids are a 400, not a 404.** The compatibility endpoints match
AssemblyAI here: `{"error":"Transcript lookup error, transcript id not found"}` with
HTTP 400. The native endpoint at `/api/v1/transcripts/:id` returns a plain 404
instead.

**Non-English coverage is narrower than the code list.** The API accepts the full
AssemblyAI language-code list, but the deployed model decodes a smaller set. A code
outside it fails at inference time with a clear message on the transcript's `error`
field. [Languages](/docs/languages) has the exact list.

## Verify

Run your existing AssemblyAI code against Sandchest and check the model identifier —
if it names a Parakeet model, you are on Sandchest and not on AssemblyAI.

```bash
curl -sS "https://stt-api.sandchest.com/v2/transcript/$ID" \
  -H "Authorization: $SANDCHEST_API_KEY" \
  | jq '{status, speech_model_used, speech_models}'
```

```json title="expected output"
{
  "status": "completed",
  "speech_model_used": "nvidia/parakeet-tdt-0.6b-v2",
  "speech_models": ["universal-2"]
}
```

## Next

[Transcript options](/docs/transcripts) — every request field and every field on the
object that comes back.
