Base URL: https://api.whisper.aspicho.me/api/v1
Most endpoints require authentication via Bearer token (API key) or session ID:
Authorization: Bearer <your-api-key>
Authorization: Basic <session-id>
Note: API keys are UUIDs without hyphens (32 hex characters). Sessions expire 24 hours after creation.
Authentication: None
Check server and database status.
{
"status": "ok"
} Register new user account
Authentication: None
{
"username": "john_doe",
"password": "SecureP@ssw0rd!"
} {
"message": "User registered and logged in successfully",
"username": "john_doe",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f"
} 400 Bad Request: "Invalid username. Must be 3-20 characters..." 400 Bad Request: "Password must be at least 12 characters long" 400 Bad Request: "Password must contain at least 1 uppercase letter(s)" 409 Conflict: "Username already exists"
Login with credentials
Authentication: None
{
"username": "john_doe",
"password": "SecureP@ssw0rd!"
} {
"message": "Login successful",
"username": "john_doe",
"is_admin": false,
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f"
} 400 Bad Request: "Missing username" 401 Unauthorized: "Authentication failed"
End current or specified session
Authentication: Basic (session only, not API key)
{
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f"
} {
"message": "Logged out successfully"
} 400 Bad Request: "Cannot logout with API key. Use session authentication" 403 Forbidden: "Cannot logout from another user's session" 404 Not Found: "Session not found or already ended"
Get current user profile
Authentication: Bearer or Basic
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"username": "john_doe",
"is_admin": false,
"registered_at": "2025-01-15 10:30:45",
"token_balance": 1000,
"avatar_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70"
} Update user profile
Authentication: Bearer or Basic
Content-Type: multipart/form-data
{
"message": "Profile updated successfully",
"updated_fields": ["username", "avatar"]
} 400 Bad Request: "No fields to update..." 400 Bad Request: "old_password is required to update password" 400 Bad Request: "Avatar file too large. Maximum size is 10MB" 401 Unauthorized: "Incorrect old password" 409 Conflict: "Username already taken"
Delete account and all associated data
Authentication: Bearer or Basic
{
"password": "SecureP@ssw0rd!"
} {
"message": "Account deleted successfully"
} 400 Bad Request: "Password is required to delete account" 401 Unauthorized: "Incorrect password"
List all API tokens for current user
Authentication: Bearer or Basic
{
"tokens": [
{
"token_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"token": "018c...5d6f",
"name": "Production API Key",
"created_at": "2025-01-15 10:30:45"
},
{
"token_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70",
"token": "018c...5d71",
"name": "Development Key",
"created_at": "2025-01-16 14:22:10"
}
]
} Create new API token
Authentication: Bearer or Basic
{
"name": "Production API Key"
} {
"message": "Token issued successfully",
"token_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"token": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f",
"name": "Production API Key",
"note": "Save this token securely. You won't be able to see it again."
} ⚠️ Important: The full token is only shown once. Store it securely!
400 Bad Request: "Missing 'name' field in request body" 400 Bad Request: "Token name must be between 1 and 255 characters"
Revoke API token
Authentication: Bearer or Basic
{
"message": "Token revoked successfully",
"token_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"token_name": "Production API Key"
} 400 Bad Request: "Invalid token ID format" 404 Not Found: "Token not found or access denied"
List active sessions for current user
Authentication: Bearer or Basic
{
"sessions": [
{
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f",
"started_at": "2025-01-15 10:30:45",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
},
{
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70",
"started_at": "2025-01-16 08:15:22",
"user_agent": "curl/7.84.0"
}
]
} Note: Only returns sessions that haven't ended and are within 24 hours of creation.
Upload audio/video files for transcription
Authentication: Bearer or Basic
Content-Type: multipart/form-data
Audio: mp3, wav, flac, m4a, aac, ogg, opus, webm, wma, aiff, alac
Video: mp4, mov, avi, wmv, mpeg, 3gp, mkv, webm, flv, ts, mts, vob, rmvb, divx
{
"files": [
{
"message": "File uploaded successfully",
"filename": "interview.mp3",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"hash": "abc123def456...",
"status": {
"existed": false,
"uploaded": true
}
},
{
"message": "File already exists",
"filename": "podcast.wav",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70",
"hash": "def789ghi012...",
"status": {
"existed": true,
"uploaded": false
}
},
{
"message": "Unsupported content type",
"filename": "document.pdf",
"status": {
"existed": false,
"uploaded": false,
"error": "unsupported_content_type"
}
}
]
} List user's files with pagination
Authentication: Bearer or Basic
{
"files": [
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"filename": "interview.mp3",
"content_type": "audio/mpeg",
"size_bytes": 5242880,
"uploaded_at": "2025-01-15 10:30:45"
},
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70",
"filename": "podcast_episode_1.mp3",
"content_type": "audio/mpeg",
"size_bytes": 12582912,
"uploaded_at": "2025-01-14 08:22:10"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total_count": 25,
"total_pages": 3
}
} Download file by UUID
Authentication: None (public access via UUID)
Content-Type: audio/mpeg (or appropriate MIME type) Content-Length: 5242880 Content-Disposition: inline; filename="interview.mp3" Accept-Ranges: bytes
Binary file data with appropriate content type.
400 Bad Request: "Invalid UUID format" 404 Not Found: "File not found or access denied" 404 Not Found: "File not found on disk"
Update filename
Authentication: Bearer or Basic
{
"filename": "new_interview_name.mp3"
} {
"message": "Filename updated successfully",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"old_filename": "interview.mp3",
"new_filename": "new_interview_name.mp3"
} 400 Bad Request: "Missing 'filename' field in request body" 400 Bad Request: "Filename must be between 1 and 255 characters" 404 Not Found: "File not found or access denied"
Delete file and WAV conversion
Authentication: Bearer or Basic
{
"message": "File deleted successfully",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"filename": "interview.mp3"
} 400 Bad Request: "Invalid UUID format" 404 Not Found: "File not found or access denied"
Note: Deletes both the converted file (MP3/MP4) and the WAV version used for transcription.
List available Whisper models
Authentication: Bearer or Basic
{
"models": [
{
"name": "ggml-base.en"
},
{
"name": "ggml-medium"
},
{
"name": "ggml-large-v3"
}
]
} {
"models": [
{
"name": "ggml-base.en",
"size": 147964211
},
{
"name": "ggml-medium",
"size": 1533833503
},
{
"name": "ggml-large-v3",
"size": 3094625823
}
]
} Queue transcription job(s)
Authentication: Bearer or Basic
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"model": "ggml-base.en",
"language": "en",
"format": ["verbose_json", "srt", "vtt"]
} verbose_json - Full JSON with timestamps and metadatasrt - SubRip subtitle formatvtt - WebVTT subtitle formatCost = ceil(duration_seconds) × price_multiplier
{
"message": "Transcription(s) queued successfully",
"transcriptions": [
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"format": "verbose_json",
"status": "pending"
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e30",
"format": "srt",
"status": "pending"
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e31",
"format": "vtt",
"status": "pending"
}
]
} 400 Bad Request: "Missing file uuid" 400 Bad Request: "Invalid format: txt. Must be one of: verbose_json, srt, vtt" 400 Bad Request: "At least one format must be specified" 404 Not Found: "File not found or access denied"
Get transcription status and result
Authentication: None (public access)
Behavior:
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"file_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"model": "ggml-base.en",
"language": "en",
"format": "verbose_json",
"status": "completed",
"processed_at": "2025-01-15 10:35:20",
"data": {
"text": "Hello, this is a test transcription.",
"segments": [
{
"start": 0.0,
"end": 3.5,
"text": "Hello, this is a test transcription."
}
],
"language": "en"
}
} {
"transcriptions": [
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"file_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"model": "ggml-base.en",
"language": "en",
"format": "verbose_json",
"status": "completed",
"processed_at": "2025-01-15 10:35:20",
"data": { ... }
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e30",
"file_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"model": "ggml-base.en",
"language": "en",
"format": "srt",
"status": "processing",
"processed_at": "1970-01-01 00:00:00",
"data": null
}
]
} {
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"file_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"model": "ggml-base.en",
"language": "en",
"format": "verbose_json",
"status": "failed",
"error": "Insufficient token balance. Required: 120, Available: 50",
"processed_at": "1970-01-01 00:00:00"
} verbose_json format:
{
"text": "Full transcription text here...",
"segments": [
{
"start": 0.0,
"end": 3.5,
"text": "First segment text."
},
{
"start": 3.5,
"end": 8.2,
"text": "Second segment text."
}
],
"language": "en"
} srt format:
{
"text": "1\n00:00:00,000 --> 00:00:03,500\nFirst segment text.\n\n2\n00:00:03,500 --> 00:00:08,200\nSecond segment text."
} vtt format:
{
"text": "WEBVTT\n\n00:00:00.000 --> 00:00:03.500\nFirst segment text.\n\n00:00:03.500 --> 00:00:08.200\nSecond segment text."
} 400 Bad Request: "Invalid UUID format" 404 Not Found: "Transcription not found"
Delete transcription record
Authentication: Bearer or Basic
{
"message": "Transcription deleted successfully",
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"model": "ggml-base.en",
"format": "verbose_json"
} 400 Bad Request: "Invalid UUID format" 404 Not Found: "Transcription not found or access denied"
List all users with detailed statistics
Authentication: Bearer or Basic (admin only)
{
"users": [
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"username": "john_doe",
"is_admin": false,
"registered_at": "2025-01-15 10:30:45",
"token_balance": 1000,
"price_multiplier": 1.0,
"avatar_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70",
"stats": {
"last_30_days": {
"transcriptions_count": 15,
"total_audio_seconds": 3600,
"files_uploaded": 12
},
"total_storage_bytes": 524288000,
"active_sessions": 2,
"api_keys_count": 3
}
},
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d71",
"username": "jane_smith",
"is_admin": false,
"registered_at": "2025-01-10 08:15:22",
"token_balance": 500,
"price_multiplier": 0.8,
"avatar_uuid": null,
"stats": {
"last_30_days": {
"transcriptions_count": 8,
"total_audio_seconds": 1800,
"files_uploaded": 6
},
"total_storage_bytes": 157286400,
"active_sessions": 1,
"api_keys_count": 1
}
}
],
"pagination": {
"page": 1,
"limit": 10,
"total_count": 42,
"total_pages": 5
}
} 403 Forbidden: "Access denied. Admin privileges required"
Update user settings
Authentication: Bearer or Basic (admin only)
{
"token_balance": 5000,
"price_multiplier": 0.5
} {
"message": "User updated successfully",
"updated_fields": ["token_balance", "price_multiplier"],
"user": {
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"username": "john_doe",
"token_balance": 5000,
"price_multiplier": 0.5
}
} 400 Bad Request: "Invalid UUID format" 400 Bad Request: "token_balance must be an integer" 400 Bad Request: "price_multiplier must be non-negative" 400 Bad Request: "price_multiplier must be at most 1000" 400 Bad Request: "No valid fields to update..." 403 Forbidden: "Access denied. Admin privileges required" 404 Not Found: "User not found"
All error responses return plain text with appropriate HTTP status codes.
| Code | Meaning | Common Causes |
|---|---|---|
200 | OK | Request succeeded |
400 | Bad Request | Invalid input, validation errors, missing required fields |
401 | Unauthorized | Missing/invalid authentication, incorrect password |
403 | Forbidden | Insufficient permissions (admin required) |
404 | Not Found | Resource doesn't exist or access denied |
409 | Conflict | Resource already exists (username taken, etc.) |
500 | Internal Server Error | Server-side error, database failures |
Errors are returned as plain text strings (not JSON).
HTTP/1.1 400 Bad Request Invalid username. Must be 3-20 characters and contain only alphanumeric characters, underscores, or hyphens
HTTP/1.1 401 Unauthorized Missing valid Authorization header with session or API key
HTTP/1.1 403 Forbidden Access denied. Admin privileges required
HTTP/1.1 404 Not Found File not found or access denied
HTTP/1.1 409 Conflict Username already exists
All UUIDs use UUIDv7 format (time-ordered for better database indexing):
018c5f9a7b2e7a8c9d0e1f2a3b4c5d6eAll timestamps use ISO 8601 format in UTC:
YYYY-MM-DD HH:MM:SS2025-01-15 10:30:45Upload → MP3 Conversion → WAV Conversion → Storage
(192kbps, 44.1kHz) (16kHz, mono, PCM s16le) Upload → MP4 Conversion → WAV Conversion (audio) → Storage
(H.264, AAC 192kbps) (16kHz, mono, PCM s16le) Files are deduplicated per user using BLAKE3 hash:
Authorization: Basic <session-id>/user/logout or automatic after 24 hoursAuthorization: Bearer <api-key>cost = ceil(duration_seconds) × price_multiplier# Step 1: Register new account
POST https://api.whisper.aspicho.me/api/v1/user/register
Content-Type: application/json
{
"username": "developer",
"password": "SecureP@ssw0rd123!"
}
# Response:
{
"message": "User registered and logged in successfully",
"username": "developer",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f"
}
# Step 2: Create API token (using session)
POST https://api.whisper.aspicho.me/api/v1/user/token
Authorization: Basic 018c5f9a7b2e7a8c9d0e1f2a3b4c5d6f
Content-Type: application/json
{
"name": "My First API Key"
}
# Response:
{
"message": "Token issued successfully",
"token_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d70",
"token": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d71",
"name": "My First API Key",
"note": "Save this token securely. You won't be able to see it again."
}
# Step 3: Upload audio file (using API token)
POST https://api.whisper.aspicho.me/api/v1/file
Authorization: Bearer 018c5f9a7b2e7a8c9d0e1f2a3b4c5d71
Content-Type: multipart/form-data
file=@podcast_episode.mp3
# Response:
{
"files": [
{
"message": "File uploaded successfully",
"filename": "podcast_episode.mp3",
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d72",
"hash": "abc123...",
"status": {
"existed": false,
"uploaded": true
}
}
]
}
# Step 4: Queue transcription
POST https://api.whisper.aspicho.me/api/v1/transcribe
Authorization: Bearer 018c5f9a7b2e7a8c9d0e1f2a3b4c5d71
Content-Type: application/json
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d72",
"model": "ggml-base.en",
"language": "en",
"format": "verbose_json"
}
# Response:
{
"message": "Transcription(s) queued successfully",
"transcriptions": [
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"format": "verbose_json",
"status": "pending"
}
]
}
# Step 5: Check transcription status (public, no auth needed)
GET https://api.whisper.aspicho.me/api/v1/transcribe/018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f
# Response (when completed):
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"file_uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d72",
"model": "ggml-base.en",
"language": "en",
"format": "verbose_json",
"status": "completed",
"processed_at": "2025-01-15 10:35:20",
"data": {
"text": "Welcome to episode 42 of our podcast...",
"segments": [...]
}
} # Request transcription in multiple formats at once
POST https://api.whisper.aspicho.me/api/v1/transcribe
Authorization: Bearer <api-key>
Content-Type: application/json
{
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d72",
"model": "ggml-medium",
"language": "en",
"format": ["verbose_json", "srt", "vtt"]
}
# Response: 3 separate transcription jobs created
{
"message": "Transcription(s) queued successfully",
"transcriptions": [
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"format": "verbose_json",
"status": "pending"
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e30",
"format": "srt",
"status": "pending"
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e31",
"format": "vtt",
"status": "pending"
}
]
}
# Check all transcriptions for a file (using file UUID)
GET https://api.whisper.aspicho.me/api/v1/transcribe/018c5f9a7b2e7a8c9d0e1f2a3b4c5d72
# Response: Returns all transcriptions for that file
{
"transcriptions": [
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e2f",
"format": "verbose_json",
"status": "completed",
"data": {...}
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e30",
"format": "srt",
"status": "completed",
"data": {...}
},
{
"uuid": "018c5f9b1a2b3c4d5e6f7a8b9c0d1e31",
"format": "vtt",
"status": "processing",
"data": null
}
]
} # Step 1: Login as admin
POST https://api.whisper.aspicho.me/api/v1/user/login
Content-Type: application/json
{
"username": "admin",
"password": "admin_password"
}
# Response:
{
"message": "Login successful",
"username": "admin",
"is_admin": true,
"session_id": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d80"
}
# Step 2: Create admin API token
POST https://api.whisper.aspicho.me/api/v1/user/token
Authorization: Basic 018c5f9a7b2e7a8c9d0e1f2a3b4c5d80
Content-Type: application/json
{
"name": "Admin API Key"
}
# Step 3: List all users
GET https://api.whisper.aspicho.me/api/v1/admin/users?page=1&limit=20
Authorization: Bearer <admin-api-key>
# Response includes detailed stats for each user
# Step 4: Grant tokens to user
PATCH https://api.whisper.aspicho.me/api/v1/admin/users/018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e
Authorization: Bearer <admin-api-key>
Content-Type: application/json
{
"token_balance": 10000,
"price_multiplier": 0.5
}
# Response:
{
"message": "User updated successfully",
"updated_fields": ["token_balance", "price_multiplier"],
"user": {
"uuid": "018c5f9a7b2e7a8c9d0e1f2a3b4c5d6e",
"username": "developer",
"token_balance": 10000,
"price_multiplier": 0.5
}
} # Register user
curl -X POST https://api.whisper.aspicho.me/api/v1/user/register \
-H "Content-Type: application/json" \
-d '{"username":"myuser","password":"SecureP@ss123!"}'
# Upload file
curl -X POST https://api.whisper.aspicho.me/api/v1/file \
-H "Authorization: Bearer <api-key>" \
-F "file=@audio.mp3"
# Start transcription
curl -X POST https://api.whisper.aspicho.me/api/v1/transcribe \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"uuid":"<file-uuid>",
"model":"ggml-base.en",
"format":"verbose_json"
}'
# Check status
curl https://api.whisper.aspicho.me/api/v1/transcribe/<transcription-uuid>
# Download file (no auth needed)
curl -O https://api.whisper.aspicho.me/api/v1/file/<file-uuid> import requests
import time
# Configuration
API_BASE = "https://api.whisper.aspicho.me/api/v1"
API_KEY = "your-api-key-here"
# Upload file
with open("audio.mp3", "rb") as f:
response = requests.post(
f"{API_BASE}/file",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": f}
)
file_uuid = response.json()["files"][0]["uuid"]
print(f"Uploaded file: {file_uuid}")
# Queue transcription
response = requests.post(
f"{API_BASE}/transcribe",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"uuid": file_uuid,
"model": "ggml-base.en",
"format": "verbose_json"
}
)
transcription_uuid = response.json()["transcriptions"][0]["uuid"]
print(f"Transcription queued: {transcription_uuid}")
# Poll for completion
while True:
response = requests.get(f"{API_BASE}/transcribe/{transcription_uuid}")
data = response.json()
status = data["status"]
print(f"Status: {status}")
if status == "completed":
print("Transcription:", data["data"]["text"])
break
elif status == "failed":
print("Error:", data.get("error"))
break
time.sleep(5) # Wait 5 seconds before checking again