Skip to content

Quickstart

Get an API key, set one environment variable, and turn a local audio file into word-level timestamps — with the AssemblyAI SDK, the native SDK, or curl.

Open .md

By the end of this page you will have a Sandchest API key in your environment and a completed transcript printed as words with millisecond timings. Pick one of the three paths in step 3; they all produce the same transcript.

1. Get an API key#

  1. Open sandchest.com/dashboard and sign in. Sign-in is a six-digit code sent to your email — there is no password.
  2. Go to API keys and create a key. Give it a name you will recognise later (local dev, staging worker).
  3. Copy it now. Keys start with sc_live_ and are shown exactly once; Sandchest stores only a hash, so a lost key must be replaced rather than recovered.

2. Put the key in your environment#

shell
export SANDCHEST_API_KEY="sc_live_..."

For a project, put it in .env.local (or .env) and make sure that file is git-ignored:

.env.local
SANDCHEST_API_KEY=sc_live_...

Check it is set:

shell
echo "${SANDCHEST_API_KEY:0:8}"
expected output
sc_live_

3. Transcribe a file#

You need a local audio or video file. Anything FFmpeg can decode works — .mp3, .m4a, .wav, .mp4, .webm. The examples below use meeting.mp3.

With the AssemblyAI SDK#

If your project already uses assemblyai, keep it. Change the key and the base URL.

shell
npm install assemblyai   # or: bun add assemblyai
transcribe.ts
import { readFile } from "node:fs/promises";
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: await readFile("meeting.mp3"),
});

if (transcript.status === "error") {
  throw new Error(transcript.error ?? "Transcription failed.");
}

console.log(transcript.text);
for (const word of transcript.words ?? []) {
  console.log(`${word.start}\t${word.end}\t${word.text}`);
}

transcribe() uploads the bytes, creates the transcript, and polls until it is finished. The AssemblyAI SDK owns the polling loop — Sandchest's GET returns the current state immediately rather than holding the connection open.

With @sandchest/sdk#

shell
bun add @sandchest/sdk   # or: npm install @sandchest/sdk
transcribe.ts
import { readFile } from "node:fs/promises";
import { Sandchest } from "@sandchest/sdk";

const client = new Sandchest({ apiKey: process.env.SANDCHEST_API_KEY as string });

const transcript = await client.transcripts.transcribe({
  audio: await readFile("meeting.mp3"),
});

if (transcript.status === "error") {
  throw new Error(transcript.error ?? "Transcription failed.");
}

console.log(transcript.text);
for (const word of transcript.words ?? []) {
  console.log(`${word.start}\t${word.end}\t${word.text}`);
}

The base URL defaults to https://stt-api.sandchest.com; pass baseUrl only when you are pointing at your own deployment.

With curl#

Three calls: upload the bytes, create the transcript, poll until it settles.

transcribe.sh
API="https://stt-api.sandchest.com"

# 1. Upload raw bytes. This is not a multipart form.
UPLOAD_URL=$(curl -sS "$API/v2/upload" \
  -H "Authorization: $SANDCHEST_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @meeting.mp3 | jq -r .upload_url)

# 2. Create the transcript.
ID=$(curl -sS "$API/v2/transcript" \
  -H "Authorization: $SANDCHEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"audio_url\":\"$UPLOAD_URL\"}" | jq -r .id)

# 3. Poll. GET returns the current state immediately, so you drive the loop.
while true; do
  STATUS=$(curl -sS "$API/v2/transcript/$ID" \
    -H "Authorization: $SANDCHEST_API_KEY" | jq -r .status)
  echo "$STATUS"
  [ "$STATUS" = completed ] || [ "$STATUS" = error ] && break
  sleep 2
done

4. Read the words#

Every word carries start and end in milliseconds and a confidence between 0 and 1.

shell
curl -sS "$API/v2/transcript/$ID" \
  -H "Authorization: $SANDCHEST_API_KEY" \
  | jq -r '.words[] | "\(.start)\t\(.end)\t\(.text)"' | head
expected output
0	280	Okay
300	520	so,
980	1090	the
1100	1480	deploy
1500	1690	went

The full transcript object also carries text, confidence, audio_duration (in seconds), language_code, language_confidence and speech_model_used. See Transcript options for every field.

Verify#

Run this. It should print completed and a non-empty transcript id.

shell
curl -sS "https://stt-api.sandchest.com/v2/transcript/$ID" \
  -H "Authorization: $SANDCHEST_API_KEY" \
  | jq '{id, status, language_code, audio_duration, speech_model_used, words: (.words | length)}'
expected output
{
  "id": "tr_...",
  "status": "completed",
  "language_code": "en",
  "audio_duration": 4.8,
  "speech_model_used": "nvidia/parakeet-tdt-0.6b-v2",
  "words": 14
}

If status is error, read the error field — it says exactly what went wrong. Errors and limits maps every failure to what to do about it.

Next#

Set up with an AI agent — hand these docs to a coding agent and let it wire Sandchest in for you.