Python SDK
SDK Download
Setup
Download the Python SDK archive from SDK Overview, extract it, then install it into a virtual environment.
Configure the shared environment variables from Getting Started, and point KOLDAN_FILE_PATH to the audio file you want to transcribe.
Upload And Transcribe With upload_and_transcribe
The example below keeps the request intentionally minimal: upload one file, start transcription with the general model alias, poll until the job finishes, and print either the transcript or the error details.
import os
import time
import koldan_sdk
from koldan_sdk.api.speech_transcriptions_api import SpeechTranscriptionsApi
from koldan_sdk.models.speech_service_diarization_options import SpeechServiceDiarizationOptions
from koldan_sdk.models.speech_service_transcription_language_options import SpeechServiceTranscriptionLanguageOptions
from koldan_sdk.models.speech_service_transcription_options import SpeechServiceTranscriptionOptions
from koldan_sdk.models.transcription_job_status import TranscriptionJobStatus
config = koldan_sdk.Configuration(host=os.environ["KOLDAN_BASE_URL"])
config.api_key["apiKey"] = os.environ["KOLDAN_API_KEY"]
with koldan_sdk.ApiClient(config) as client:
api = SpeechTranscriptionsApi(client)
with open(os.environ["KOLDAN_FILE_PATH"], "rb") as source:
transcription_options = SpeechServiceTranscriptionOptions(
model="general",
language=SpeechServiceTranscriptionLanguageOptions(),
punctuation=False,
capitalization=False
)
diarization_options = SpeechServiceDiarizationOptions(
enabled=False
)
response = api.upload_and_transcribe(
name="meeting-recording",
transcription=transcription_options.to_json(),
file=(os.path.basename(source.name), source.read()),
diarization=diarization_options.to_json(),
)
job = response.job
while True:
job = api.get_job(job.id)
print(f"Job status: {job.status}")
if job.status in {TranscriptionJobStatus.COMPLETED, TranscriptionJobStatus.FAILED, TranscriptionJobStatus.CANCELLED}:
break
time.sleep(2)
if job.status == TranscriptionJobStatus.COMPLETED:
print(job.result.text)
else:
print(job.errors)
Run
Expected Result
A successful run prints Job status: ... updates and then the transcription text from your uploaded audio.
If the job fails, the example prints the error array returned by the API.
Next Step: Webhooks and Automatic Cleanup
For long-running or production workloads, use a webhook to receive completion events without polling. The example below verifies signed requests, persists the result, handles retries, and can optionally purge the uploaded file and its derived resources.
Persist before purge
Purging is irreversible. Save every result your application needs outside Koldan before enabling automatic purge. After the source file is purged, its media, listening audio, transcription results, and summary results cannot be retrieved from Koldan.
How the Flow Works
sequenceDiagram
participant U as Upload client
participant K as Koldan
participant W as Webhook server
participant S as Application storage
U->>K: Upload and transcribe with webhook
K-->>U: File ID and job ID
K->>W: transcription.completed
W->>W: Verify signature and event ID
W->>S: Persist the result
opt Automatic purge enabled
W->>K: DELETE file?purge=true
K-->>W: 204 No Content
end
W-->>K: 204 No Content
Koldan also sends transcription.failed when processing fails. The receiver below stores failed events but only purges automatically after transcription.completed. You can apply a different cleanup policy if failed uploads must also be removed immediately.
Prerequisites
- An API key with
speech:files:write,speech:transcriptions:write, andspeech:transcriptions:read - The
speech:files:deletescope when automatic purge is enabled - A webhook URL that Koldan can reach
Webhook URLs must use HTTPS by default. For local development, expose the webhook server through an HTTPS tunnel or use an appropriately configured development deployment. A webhook URL such as http://localhost:8000 usually refers to the Koldan server itself and is also blocked by the default webhook security settings.
Install the webhook server dependencies in the same environment as the generated SDK:
Configure the Webhook
Keep the three shared environment variables from Getting Started, then add:
Start with KOLDAN_PURGE_AFTER_RECEIPT=false. Change it to true only after verifying that your application persists everything it needs.
Create the Webhook Server
Save this as webhook_server.py:
import asyncio
import hashlib
import hmac
import json
import os
from pathlib import Path
from uuid import UUID
import koldan_sdk
from fastapi import FastAPI, HTTPException, Request, Response
from koldan_sdk.api.speech_files_api import SpeechFilesApi
from koldan_sdk.api.speech_transcriptions_api import SpeechTranscriptionsApi
from koldan_sdk.exceptions import ApiException
app = FastAPI()
event_lock = asyncio.Lock()
WEBHOOK_SECRET = os.environ["KOLDAN_WEBHOOK_SECRET"]
PURGE_AFTER_RECEIPT = os.getenv(
"KOLDAN_PURGE_AFTER_RECEIPT", "false"
).lower() == "true"
RESULTS_DIR = Path(os.getenv("KOLDAN_RESULTS_DIR", "./koldan-results"))
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
def verify_signature(raw_body: bytes, signature_header: str | None) -> None:
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
if not signature_header or not hmac.compare_digest(
expected, signature_header
):
raise HTTPException(status_code=401, detail="Invalid webhook signature")
def save_json_atomically(path: Path, value: dict) -> None:
temporary_path = path.with_suffix(path.suffix + ".tmp")
temporary_path.write_text(
json.dumps(value, ensure_ascii=False, indent=2),
encoding="utf-8",
)
temporary_path.replace(path)
def purge_source_file(job_id: UUID) -> None:
config = koldan_sdk.Configuration(host=os.environ["KOLDAN_BASE_URL"])
config.api_key["apiKey"] = os.environ["KOLDAN_API_KEY"]
with koldan_sdk.ApiClient(config) as client:
transcriptions_api = SpeechTranscriptionsApi(client)
files_api = SpeechFilesApi(client)
try:
job = transcriptions_api.get_job(job_id)
files_api.delete_file(job.file.id, purge=True)
except ApiException as exc:
# A retry may arrive after a previous attempt already purged the file.
if exc.status not in {404, 409}:
raise
@app.post("/webhooks/koldan/transcriptions", status_code=204)
async def receive_transcription_webhook(request: Request) -> Response:
raw_body = await request.body()
verify_signature(
raw_body,
request.headers.get("X-Webhook-Signature"),
)
webhook_id = request.headers.get("X-Webhook-Id")
if not webhook_id:
raise HTTPException(status_code=400, detail="Missing X-Webhook-Id")
payload = json.loads(raw_body)
event = payload.get("event")
job_id_value = payload.get("jobId")
if event not in {"transcription.completed", "transcription.failed"}:
raise HTTPException(status_code=400, detail="Unsupported event")
if not job_id_value:
raise HTTPException(status_code=400, detail="Missing jobId")
try:
job_id = UUID(job_id_value)
webhook_uuid = UUID(webhook_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid event ID") from exc
result_path = RESULTS_DIR / f"{job_id}.json"
completed_path = RESULTS_DIR / f"{webhook_uuid}.done"
# The lock is sufficient for this single-process example. Production
# deployments should enforce webhook-ID uniqueness in durable storage.
async with event_lock:
if completed_path.exists():
return Response(status_code=204)
# Persist first. Never place the purge call before this operation.
save_json_atomically(result_path, payload)
if PURGE_AFTER_RECEIPT and event == "transcription.completed":
purge_source_file(job_id)
completed_path.touch()
return Response(status_code=204)
The signature is an HMAC-SHA256 digest of the exact raw HTTP request body. Verify it before parsing JSON. X-Webhook-Id remains the same when Koldan retries a delivery, so it can be used as an idempotency key.
Run the server:
Submit a Transcription with a Webhook
Save this as submit_transcription.py:
import os
import koldan_sdk
from koldan_sdk.api.speech_transcriptions_api import SpeechTranscriptionsApi
from koldan_sdk.models.speech_service_webhook import SpeechServiceWebhook
from koldan_sdk.models.speech_service_diarization_options import SpeechServiceDiarizationOptions
from koldan_sdk.models.speech_service_transcription_language_options import SpeechServiceTranscriptionLanguageOptions
from koldan_sdk.models.speech_service_transcription_options import SpeechServiceTranscriptionOptions
config = koldan_sdk.Configuration(host=os.environ["KOLDAN_BASE_URL"])
config.api_key["apiKey"] = os.environ["KOLDAN_API_KEY"]
with koldan_sdk.ApiClient(config) as client:
api = SpeechTranscriptionsApi(client)
transcription_options = SpeechServiceTranscriptionOptions(
model="general",
language=SpeechServiceTranscriptionLanguageOptions(),
punctuation=False,
capitalization=False,
)
diarization_options = SpeechServiceDiarizationOptions(enabled=False)
webhook = SpeechServiceWebhook(
url=os.environ["KOLDAN_WEBHOOK_URL"],
secret=os.environ["KOLDAN_WEBHOOK_SECRET"],
include_segments=True,
)
with open(os.environ["KOLDAN_FILE_PATH"], "rb") as source:
response = api.upload_and_transcribe(
name="meeting-recording",
transcription=transcription_options.to_json(),
file=(os.path.basename(source.name), source.read()),
diarization=diarization_options.to_json(),
webhook=webhook.to_json(),
generate_listening_audio=False,
)
print(f"Uploaded file: {response.file.id}")
print(f"Created job: {response.job.id}")
Run the submission client after the webhook server is reachable:
The client exits after Koldan accepts the job. The webhook server later writes the terminal event to KOLDAN_RESULTS_DIR.
Choose a Cleanup Policy
| Policy | API operation | Result |
|---|---|---|
| Retain | No cleanup request | Koldan's configured retention policy applies. |
| Discard source content | POST /files/{id}/discard-content |
Removes stored media while keeping file metadata and transcription results available. |
| Purge the complete file tree | DELETE /files/{id}?purge=true |
Permanently removes media, listening audio, transcription result/error data, and summary result/error data associated with the file. |
The example implements the retain and purge the complete file tree policies. Set KOLDAN_PURGE_AFTER_RECEIPT=true to enable the latter.
Webhook payload with segments
When include_segments=True is set in the webhook configuration (as shown above), the completed webhook payload includes the full result with segments, words, and timing — saving a roundtrip to retrieve the full result. When include_segments is False (default), the payload contains only the transcript text and detected languages.
Production Considerations
- Store results and webhook IDs in durable storage rather than local files.
- Enforce a unique constraint on
X-Webhook-Idso multiple workers cannot process the same delivery concurrently. - Return a 2xx response only after the event is durably recorded. For longer processing, durably enqueue the event and acknowledge it afterward.
- Expect retries after timeouts or non-2xx responses. Cleanup and result persistence must be idempotent.
- Monitor
transcription.failedevents and decide whether failed uploads should be retained, discarded, or purged. - Keep the webhook secret separate from the Koldan API key and rotate both according to your security policy.
See Transcription Webhook Events for the payload and header contract, and Files for complete purge semantics.