speech-api
Base URL
Every endpoint below is a path appended to this. GET /health needs no key and reports which features are on.
Authentication
Send an Authorization: Bearer … header on every request. Two token types work:
| Token | Looks like | Use it for |
|---|---|---|
| API key | sk_… | Servers, scripts, integrations. Created in the admin console or via /admin/keys. Never embed in a public frontend or mobile app. |
| Access token (JWT) | eyJ… | Humans logging in. Get one from /auth/login; expires in 1 hour; renew with /auth/refresh. |
Every key/user has a role: client can call every /v1/* endpoint and see its own usage; admin can also call /admin/*.
Requests & errors
JSON bodies need content-type: application/json. File uploads (audio, images) are multipart/form-data with the file in a field named file. Successful JSON responses include model and latency_ms. Binary endpoints (audio, image) return raw bytes with x-model / x-latency-ms headers.
{ "error": "text is required" } → 400
{ "error": "unauthorized — send Authorization: Bearer " } → 401
{ "error": "admin only" } → 403
{ "error": "analysis failed", "detail": "…" } → 502 (model/upstream failure)
{ "error": "search not configured" } → 503 (feature off)
Auth
POST/auth/register
Create a user account. The very first account ever registered becomes admin automatically; everyone after that needs an invite code from an admin. No auth header needed.
| Field | Type | Notes |
|---|---|---|
| string | required | |
| password | string | min 8 chars |
| invite | string | required unless first user |
{ "user": {"id", "email", "role"}, "api_key": "sk_…", "access_token", "refresh_token", "expires_in": 3600 }
POST/auth/login
Body {email, password} → {user, access_token, refresh_token, expires_in}. 5 failed attempts locks the email for 15 minutes.
POST/auth/refresh · POST/auth/logout
Both take {refresh_token}. Refresh returns a fresh token pair (and invalidates the old refresh token); logout just invalidates it.
GET/auth/me
Who the current token belongs to: {principal: {label, role, key_id}, user}. Handy for a "test my key" button.
GETPOST/auth/keys · DELETE/auth/keys/:id
Manage your own API keys (JWT login required for create). POST body {label}; the key is returned once.
Speech
POST/v1/transcribe
Audio → text. Whisper large-v3-turbo. Urdu, English, Punjabi, Sindhi, Pashto and code-switched speech.
| Field (multipart) | Notes |
|---|---|
| file | audio, ≤ 25 MB (mp3, wav, m4a, webm, ogg…) |
| language | optional but always send ur for Urdu — auto-detect often picks Hindi script |
{ "text": "…", "language": "ur", "duration": 42.3,
"segments": [{ "start": 0, "end": 2.9, "text": "…", "words": [...] }] }
curl -X POST $URL/v1/transcribe -H "Authorization: Bearer $KEY" \ -F file=@call.mp3 -F language=ur
POST/v1/synthesize Urdu off
Text → MP3 audio. English works now (Deepgram Aura). Urdu returns 503 until Azure Speech secrets are configured.
| Field (JSON) | Notes |
|---|---|
| text | ≤ 2,000 chars |
| language | en (default) or ur |
| voice | en: asteria, luna, stella, athena, hera, orion, arcas, perseus, angus, orpheus, helios, zeus · ur: uzma, asad |
curl -X POST $URL/v1/synthesize -H "Authorization: Bearer $KEY" \
-H "content-type: application/json" -d '{"text":"Hello farmer","voice":"orion"}' -o out.mp3
Call & transcript intelligence
POST/v1/analyze
The all-in-one call analyser. Send audio (it transcribes first) or a transcript. Tuned for Pakistani farmer / agri-advisor conversations.
| Input | Fields |
|---|---|
| multipart | file, language?, context? |
| JSON | { text, language?, context? } |
context steers the analysis — if it mentions "QA", "agent" or "call centre", the response also includes qa_scores.
{ "id": 12, "source": "text", "language": "ur", "transcript": "(audio only)",
"analysis": {
"summary": "…", "language_mix": ["Urdu","English"],
"sentiment": { "label": "positive", "score": 0.8 },
"intent": "purchase | dealer_enquiry | advisory | complaint | other",
"entities": { "crops": [], "products": [], "pests_or_issues": [], "locations": [], "names": [], "phone_numbers": [] },
"action_items": ["…"],
"qa_scores": { "greeting": 5, "listening": 4, "resolution": 5, "closing": 4, "notes": "…" } // only with QA context
} }
POST/v1/qa-score
Dedicated contact-centre QA scoring against a rubric. Returns 1–5 per item with quoted evidence, an overall 0–100, strengths, improvements and compliance flags (wrong dosage, unsafe advice, rudeness, missed callback).
| Field (JSON) | Notes |
|---|---|
| text | the transcript |
| rubric | optional array of item names. Default: greeting, identity_verification, active_listening, product_knowledge, resolution, empathy_tone, closing |
| instructions | optional extra guidance for the evaluator |
Text intelligence
All take JSON with a text field (≤ 12,000 chars) and run on Llama 3.3 70B.
POST/v1/translate
| Field | Notes |
|---|---|
| text | ≤ 5,000 chars |
| target_lang | ur, en, pa, sd, ps, skr (Saraiki), bal, hno (Hindko), ur-roman, ar, hi |
| source_lang | optional; auto-detects if omitted |
| engine | llm (default, keeps agri terms) or m2m100 (faster, cheaper) |
→ { "translated_text": "…", "source_lang": "Urdu", "target_lang": "en", "engine": "llm" }
POST/v1/summarize
{text, language?, length?} → {summary, key_points[]}. length is free text like "2 sentences" or "one paragraph".
POST/v1/classify
{text, labels: ["…"], instructions?} → {label, confidence, reason}. Picks exactly one of your labels.
POST/v1/extract
{text, schema: <JSON Schema object>, instructions?} → {data: {…}} matching your schema. Missing fields come back as empty string / 0, never invented.
{ "text": "…", "schema": { "type": "object", "properties": {
"crop": { "type": "string" }, "product": { "type": "string" }, "dose": { "type": "string" } } } }
POST/v1/moderate
{text} → {flagged, categories[], severity, reason}. Categories: abuse, harassment, spam, scam, sexual, violence, self_harm, pii, off_topic. Ordinary farming questions are not flagged.
POST/v1/chat
Multi-turn assistant with server-side memory. Omit conversation_id on the first call; reuse the returned one to continue (last 20 turns kept).
| Field | Notes |
|---|---|
| message | required |
| conversation_id | optional, from a previous reply |
| persona | optional system prompt — set who the assistant is and what context it has |
| max_tokens | optional, default 600 |
→ { "conversation_id": "c_…", "reply": "…", "turns": 2 }
POST/v1/chat/completions OpenAI-compatible
Drop-in for code already written against OpenAI / Groq / OpenRouter chat completions. Send {model, messages:[{role,content}], max_tokens?, temperature?}; model is accepted and ignored (always Llama 3.3 70B). Stateless — no memory between calls.
→ { "id": "chatcmpl_…", "object": "chat.completion", "model": "…",
"choices": [{ "index": 0, "message": { "role": "assistant", "content": "…" }, "finish_reason": "stop" }] }
Vision
Image input for describe/ocr: multipart file, or JSON {image_url} or {image_base64, mime?}. ≤ 10 MB. Llama 3.2 11B Vision; expect ~10–15 s per image.
POST/v1/describe-image
Image → description. Default prompt is crop-aware: names the crop, visible symptoms, severity and what an agronomist should check. Override with prompt.
→ { "description": "…" }
POST/v1/ocr
Image → the text in it (Urdu or English script preserved). Labels, receipts, forms.
→ { "text": "…" }
POST/v1/generate-image
{prompt, steps?: 1–8} → image bytes (Flux Schnell, 1024×1024). Save the response body to a file.
curl -X POST $URL/v1/generate-image -H "Authorization: Bearer $KEY" \
-H "content-type: application/json" -d '{"prompt":"healthy wheat field, illustration"}' -o out.png
Search / knowledge off
POSTGET/v1/documents · DELETE/v1/documents/:id
POST {id?, title?, kind?, language?, text} (≤ 200k chars) chunks and indexes the text for semantic search → {id, chunks}. GET lists your documents; DELETE removes one.
POST/v1/search
{query, top_k?, kind?, language?, answer?} → {results:[{text, score, doc_id}], answer?}. With answer: true it also writes an answer grounded only in the matched passages.
Account & admin
GET/v1/usage
Your own key's totals per endpoint: calls, audio minutes, characters, average latency.
/admin/* admin role only
| Endpoint | What it does |
|---|---|
| GET /admin/usage?days=30 | Usage for every key + a per-day series |
| GET · POST /admin/keys · DELETE /admin/keys/:id | List all keys; create {label, role} (returned once); revoke |
| GET /admin/users · DELETE /admin/users/:id | List / disable users (also revokes their keys and sessions) |
| GET · POST /admin/invites | List invites; create {role} → single-use code |
| GET /admin/analyses?since=&limit= | Browse stored /v1/analyze results |
| GET /admin/health-check | Live test of D1, LLM, translate, embed, Vectorize, Urdu TTS, auth |
| POST /admin/accept-model-license | One-time acceptance for gated models (already done for vision & Flux) |
These all have a visual UI in the admin console.
Usage examples
const URL = "https://speech-api.shahzaibkashan88.workers.dev";
const KEY = process.env.SPEECH_API_KEY;
async function analyzeCall(transcript) {
const r = await fetch(`${URL}/v1/analyze`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "content-type": "application/json" },
body: JSON.stringify({ text: transcript, language: "ur", context: "call-centre agent QA" })
});
if (!r.ok) throw new Error((await r.json()).error);
return (await r.json()).analysis;
}
import requests
URL = "https://speech-api.shahzaibkashan88.workers.dev"
H = {"Authorization": f"Bearer {KEY}"}
# transcribe a file
with open("call.mp3", "rb") as f:
t = requests.post(f"{URL}/v1/transcribe", headers=H, files={"file": f}, data={"language": "ur"}).json()
# keep a conversation going
c = requests.post(f"{URL}/v1/chat", headers=H, json={"message": "gandum kab bijain?"}).json()
c2 = requests.post(f"{URL}/v1/chat", headers=H, json={"message": "aur khaad?", "conversation_id": c["conversation_id"]}).json()
URL=https://speech-api.shahzaibkashan88.workers.dev
KEY=sk_…
curl -s $URL/health
curl -s $URL/auth/me -H "Authorization: Bearer $KEY"
curl -s -X POST $URL/v1/translate -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"text":"sufaid makhi kapas per hai","target_lang":"en"}'
curl -s $URL/v1/usage -H "Authorization: Bearer $KEY"
// Anything that takes a base URL + key for OpenAI-compatible chat: baseURL: "https://speech-api.shahzaibkashan88.workers.dev/v1" apiKey: "sk_…" // then call chat.completions as usual — model name is ignored.