# Getting Started Get started with Lesan AI services in minutes. Welcome to Lesan AI! This guide will help you get started with our API in just a few minutes. ## Prerequisites - A Lesan AI account - An API key (see [Authentication](/guides/authentication)) - Basic knowledge of HTTP requests ## Step 1 — Verify your API key Before anything else, confirm your key works. Replace `YOUR_API_KEY` with your real key and run the request below. A valid key returns `200 OK` with your (possibly empty) list of jobs; an invalid or missing key returns `401`. ```curl curl -i "https://asr.lesan.ai/v1/transcriptions?limit=1" \ -H "Authorization: Bearer YOUR_API_KEY" # 200 OK → your key is valid # 401 → {"error":{"code":"INVALID_API_KEY", ...}} — check the key ``` ```python import requests API_KEY = "YOUR_API_KEY" # paste your key here response = requests.get( "https://asr.lesan.ai/v1/transcriptions", headers={"Authorization": f"Bearer {API_KEY}"}, params={"limit": 1}, ) if response.status_code == 200: print("✅ Your API key is valid.") else: print(f"❌ Key check failed ({response.status_code}):", response.json()) ``` ```javascript const API_KEY = "YOUR_API_KEY"; // paste your key here const response = await fetch("https://asr.lesan.ai/v1/transcriptions?limit=1", { headers: { Authorization: `Bearer ${API_KEY}` }, }); if (response.ok) { console.log("✅ Your API key is valid."); } else { console.log(`❌ Key check failed (${response.status}):`, await response.json()); } ``` > **💡 Tip** > > The `/health` endpoint is public and does **not** check your key — it always returns `200`. Use an authenticated endpoint like `GET /v1/transcriptions` (above) to actually validate a key. ## Step 2 — Your first transcription Now transcribe audio in a single request. The block below uses a sample Amharic clip we host, so the **only thing you need to change is your API key**. Using `mode: "sync"` tells the API to wait and return the finished transcript in one response — no polling required. ```curl curl "https://asr.lesan.ai/v1/transcriptions" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "audio_url": "https://docs.lesan.ai/audio/am/01.wav", "language": "am", "mode": "sync" }' # Returns the completed job, e.g.: # { # "id": "...", # "status": "completed", # "language": "am", # "text": "ሰላም ለዓለም ...", # "duration_seconds": 6.4, # ... # } ``` ```python import requests API_KEY = "YOUR_API_KEY" # ← the only thing you need to change response = requests.post( "https://asr.lesan.ai/v1/transcriptions", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }, json={ "audio_url": "https://docs.lesan.ai/audio/am/01.wav", # sample Amharic clip "language": "am", "mode": "sync", # wait for the result in one request }, ) response.raise_for_status() result = response.json() print(result["text"]) ``` ```javascript const API_KEY = "YOUR_API_KEY"; // ← the only thing you need to change const response = await fetch("https://asr.lesan.ai/v1/transcriptions", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ audio_url: "https://docs.lesan.ai/audio/am/01.wav", // sample Amharic clip language: "am", mode: "sync", // wait for the result in one request }), }); if (!response.ok) throw new Error(`Request failed: ${response.status}`); const result = await response.json(); console.log(result.text); ``` > **ℹ️ Note** > > Want to transcribe your own audio? Swap `audio_url` for any public URL, or upload a file directly with `multipart/form-data`. See the [ASR guide](/guides/asr) for upload methods and supported formats. > **⚠️ Warning** > > Sync mode waits up to **5 minutes** (300 seconds) for the result, then returns `408 Request Timeout` (the job keeps processing in the background, so you can still fetch it by its id). Use sync for short clips — for longer audio or high volume, use **async** mode. ## Which mode should I use? All three paths share the same transcription pipeline and produce the same result — they differ only in how you receive it. For production systems, prefer **async with webhooks**. | Mode | Best for | How you get the result | | --------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | sync | Trying it out; short clips that finish within 5 minutes | Returned inline in the same request — no extra calls | | async + webhooks (recommended for production) | Production workloads, long audio, high volume | Lesan POSTs the result to your endpoint when the job completes — no blocking, no polling | | async + polling | Production when you cannot receive inbound webhooks | You poll `GET /v1/transcriptions/{job_id}` until status is completed | ## Production: async transcription For production, submit jobs in `async` mode. The **recommended** pattern is to register a [webhook](/guides/webhooks) and let Lesan notify your endpoint when each job completes — no open connections, no polling, and it scales cleanly to long audio and high volume. > **💡 Tip** > > **Recommended for production:** async + [webhooks](/guides/webhooks). Reach for the polling loop below only when your client cannot receive inbound webhook calls (for example, a script behind a firewall). If you cannot receive webhooks, poll the job until it completes. This example shows the complete polling workflow including error handling and getting the final result: ```curl # Step 1: Submit transcription job curl "https://asr.lesan.ai/v1/transcriptions" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "audio_url": "https://example.com/audio.mp3", "language": "am", "mode": "async" }' # Response: {"id": "job_123", "status": "queued", ...} # Step 2: Check job status (poll until completed) curl -X GET "https://asr.lesan.ai/v1/transcriptions/job_123" \ -H "Authorization: Bearer YOUR_API_KEY" # Response when complete: # { # "id": "job_123", # "status": "completed", # "text": "ሰላም እንዴት ነህ ዛሬ", # "duration_seconds": 5.2, # "segments": [...] # } ``` ```python import requests import time import os # Configuration API_KEY = os.getenv("LESAN_API_KEY", "YOUR_API_KEY") BASE_URL = "https://asr.lesan.ai" def transcribe_audio(audio_url, language="am", max_retries=3, poll_interval=2): """ Complete transcription workflow with error handling and retries """ headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } # Step 1: Submit transcription job print(f"Submitting transcription for: {audio_url}") try: response = requests.post( f"{BASE_URL}/v1/transcriptions", headers=headers, json={ "audio_url": audio_url, "language": language, "mode": "async" } ) if response.status_code != 200: error = response.json() raise Exception(f"Submission failed: {error.get('error', {}).get('message', 'Unknown error')}") job = response.json() job_id = job["id"] print(f"Job submitted successfully: {job_id}") # Step 2: Poll for completion with timeout start_time = time.time() timeout = 300 # 5 minutes timeout while True: # Check timeout if time.time() - start_time > timeout: raise Exception(f"Job {job_id} timed out after {timeout} seconds") # Get job status status_response = requests.get( f"{BASE_URL}/v1/transcriptions/{job_id}", headers=headers ) if status_response.status_code != 200: raise Exception(f"Failed to check job status: {status_response.text}") status = status_response.json() print(f"Status: {status['status']} (Progress: {status.get('progress', 0)}%)") # Check if completed if status["status"] == "completed": print("✅ Transcription completed successfully!") return { "text": status["text"], "duration": status["duration_seconds"], "segments": status["segments"], "language": status["language"] } elif status["status"] == "failed": error_msg = status.get('error', {}).get('message', 'Unknown error') raise Exception(f"Transcription failed: {error_msg}") # Wait before next poll time.sleep(poll_interval) except Exception as e: print(f"❌ Error: {str(e)}") return None # Example usage if __name__ == "__main__": result = transcribe_audio("https://example.com/audio.mp3", language="am") if result: print(f"\n📝 Transcription Result:") print(f"Text: {result['text']}") print(f"Duration: {result['duration']} seconds") print(f"Language: {result['language']}") if result['segments']: print(f"\n📊 Segments:") for i, segment in enumerate(result['segments'][:3]): # Show first 3 segments print(f" {i+1}. [{segment['start']:.1f}s - {segment['end']:.1f}s] {segment['text']} (confidence: {segment.get('confidence', 0):.2f})") ``` ```javascript // Configuration const API_KEY = process.env.LESAN_API_KEY || 'YOUR_API_KEY'; const BASE_URL = 'https://asr.lesan.ai'; /** * Complete transcription workflow with error handling and retries */ async function transcribeAudio(audioUrl, language = 'am', options = {}) { const { timeout = 300000, // 5 minutes pollInterval = 2000, // 2 seconds maxRetries = 3 } = options; const headers = { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }; try { // Step 1: Submit transcription job console.log(`🎤 Submitting transcription for: ${audioUrl}`); const submitResponse = await fetch(`${BASE_URL}/v1/transcriptions`, { method: 'POST', headers, body: JSON.stringify({ audio_url: audioUrl, language: language, mode: 'async' }) }); if (!submitResponse.ok) { const error = await submitResponse.json(); throw new Error(`Submission failed: ${error.error?.message || 'Unknown error'}`); } const job = await submitResponse.json(); const jobId = job.id; console.log(`✅ Job submitted successfully: ${jobId}`); // Step 2: Poll for completion with timeout const startTime = Date.now(); while (true) { // Check timeout if (Date.now() - startTime > timeout) { throw new Error(`Job ${jobId} timed out after ${timeout / 1000} seconds`); } // Get job status const statusResponse = await fetch(`${BASE_URL}/v1/transcriptions/${jobId}`, { headers }); if (!statusResponse.ok) { throw new Error(`Failed to check job status: ${statusResponse.statusText}`); } const status = await statusResponse.json(); console.log(`📊 Status: ${status.status} (Progress: ${status.progress || 0}%)`); // Check if completed if (status.status === 'completed') { console.log('🎉 Transcription completed successfully!'); return { text: status.text, duration: status.duration_seconds, segments: status.segments, language: status.language, speakers: status.speakers }; } else if (status.status === 'failed') { const errorMsg = status.error?.message || 'Unknown error'; throw new Error(`Transcription failed: ${errorMsg}`); } // Wait before next poll await new Promise(resolve => setTimeout(resolve, pollInterval)); } } catch (error) { console.error(`❌ Error: ${error.message}`); return null; } } // Example usage async function main() { const result = await transcribeAudio('https://example.com/audio.mp3', 'am'); if (result) { console.log('\n📝 Transcription Result:'); console.log(`Text: ${result.text}`); console.log(`Duration: ${result.duration} seconds`); console.log(`Language: ${result.language}`); if (result.segments && result.segments.length > 0) { console.log('\n📊 Segments:'); result.segments.slice(0, 3).forEach((segment, i) => { console.log(` ${i + 1}. [${segment.start.toFixed(1)}s - ${segment.end.toFixed(1)}s] ${segment.text} (confidence: ${(segment.confidence || 0).toFixed(2)})`); }); } // Save to file or database // await saveTranscription(result); } } // Run the example main().catch(console.error); ``` ## Understanding the Response In `async` mode the API immediately returns a job object with a `queued` status that you poll until completion. (In `sync` mode, as in Step 2 above, the same fields come back already populated with `completed` status and the transcript text.) ```json { "id": "cc1f2764-89a8-4808-a843-993ca0e3fb3d", "object": "transcription", "status": "queued", "language": "am", "text": null, "segments": null, "speakers": null, "progress": null, "duration_seconds": null, "processing_time_seconds": null, "error": null, "metadata": null, "created_at": "2026-03-05T11:59:47.100183Z", "completed_at": null, "result_url": null, "audio_url": null, "url": "/v1/transcriptions/cc1f2764-89a8-4808-a843-993ca0e3fb3d" } ``` - **id** — Unique identifier for the transcription job - **status** — Job status: `queued`, `processing`, `completed`, or `failed` - **text** — Full transcription text (null until completed) - **progress** — Completion percentage (null until processing starts) - **duration\_seconds** — Audio duration in seconds (null until completed) - **url** — Endpoint to poll for job status and results ## Troubleshooting Common Issues ### 400 Bad Request on Status Check If you get a 400 Bad Request error when checking job status, verify: - **HTTP Method** — Use `GET` not POST for status checks - **Authorization Header** — Ensure `Authorization: Bearer YOUR_API_KEY` is included - **Valid Job ID** — Use the exact ID returned from the submission response - **URL Format** — Correct format: `/v1/transcriptions/{job_id}` ### Example Debugging Commands ```bash # Test with explicit GET method curl -v -X GET "https://asr.lesan.ai/v1/transcriptions/cc1f2764-89a8-4808-a843-993ca0e3fb3d" \ -H "Authorization: Bearer YOUR_API_KEY" # Check if the job exists and get current status curl -v "https://asr.lesan.ai/v1/transcriptions/cc1f2764-89a8-4808-a843-993ca0e3fb3d" \ -H "Authorization: Bearer YOUR_API_KEY" # List all your jobs to see if the job ID is valid curl -v "https://asr.lesan.ai/v1/transcriptions" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Production Best Practices When using Lesan AI in production, consider these important practices: ### Handling Transcription Results - **Store Results** — Save transcriptions to your database with metadata (duration, language, confidence scores) - **Process Segments** — Use segment data for timestamps, speaker identification, and confidence analysis - **Format Output** — Convert raw text to your desired format (SRT, VTT, plain text, JSON) - **Quality Control** — Implement confidence thresholds and human review for low-confidence transcriptions ### Error Handling & Reliability - **Retry Logic** — Implement exponential backoff for failed requests - **Timeout Management** — Set appropriate timeouts (5-10 minutes for most audio files) - **Rate Limiting** — Respect API rate limits and implement queueing for high volume - **Monitoring** — Track success rates, processing times, and error patterns ### Security & Performance - **API Key Management** — Store API keys securely (environment variables, secret managers) - **Input Validation** — Validate audio URLs and file formats before submission - **Async Processing** — Use webhooks for long-running jobs instead of polling - **Caching** — Cache transcriptions for repeated audio content ## Next Steps - Read the [Authentication guide](/guides/authentication) to learn about API key types and scopes - Explore the [ASR guide](/guides/asr) for async processing, batch uploads, and job management - Try the [MT guide](/guides/mt) to translate text between languages - Set up [real-time streaming](/guides/streaming) for live transcription - Configure [webhooks](/guides/webhooks) to get notified when async jobs complete - Review [error codes](/guides/error-codes) and [rate limits](/guides/rate-limits) for production readiness - Check out the [API Reference](/api-reference) for detailed endpoint documentation