# Sherpa Vietnamese ASR — API Guide (cho đối tác)

Tài liệu này đủ để bạn (hoặc một AI/codegen) tự viết client gọi API chuyển **giọng nói tiếng Việt → văn bản**. Dịch vụ chạy offline trên CPU, tối ưu cho cuộc gọi telesales.

- **Base URL:** `https://tsst.ghn.studio`
- **Xác thực:** mọi request gửi header `X-API-Key: sk_live_...` (khoá tạo tại `https://tsst.ghn.studio/keys`).
- **Kiểu gọi:** server-to-server qua HTTPS.
- **OpenAPI (máy đọc):** `GET https://tsst.ghn.studio/openapi.json` · **Swagger UI:** `https://tsst.ghn.studio/docs`

---

## Luồng xử lý — 4 bước bất đồng bộ

Xử lý theo hàng đợi, nên là: **upload → process → poll status → get result**.

| # | Method & Path | Ý nghĩa |
|---|---|---|
| 1 | `POST /api/upload` | Tải file âm thanh (multipart) → trả `file_id` |
| 2 | `POST /api/process/{file_id}` | Cấu hình + đưa vào hàng đợi xử lý |
| 3 | `GET /api/files/{file_id}/status` | Lặp tới khi `status = "completed"` |
| 4 | `GET /api/files/{file_id}/result` | Nhận JSON transcript |

### Bước 1 — Upload
```bash
curl -s -X POST https://tsst.ghn.studio/api/upload \
  -H "X-API-Key: $KEY" \
  -F "file=@call.wav"
# → {"file_id": 123, "filename": "call.wav", "size": 480123}
```
- Định dạng: `mp3, wav, m4a, opus, flac, aac, ogg, wma` (và một số định dạng video có tiếng). Tối đa **500MB**.

### Bước 2 — Process
```bash
curl -s -X POST https://tsst.ghn.studio/api/process/123 \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"telesales_channel_split": true, "domain_correction": true}'
# → {"success": true, "position": 0, ...}
```

### Bước 3 — Poll status
```bash
curl -s https://tsst.ghn.studio/api/files/123/status -H "X-API-Key: $KEY"
# → {"file_id":123,"status":"processing","queue_position":0,"queue_total":1}
```
`status` ∈ `uploaded | queued | processing | completed | error | cancelled`. Lặp tới khi `completed`.
Khuyến nghị poll mỗi **3–5 giây** (mỗi request tính vào giới hạn/phút của khoá).

### Bước 4 — Get result
```bash
curl -s https://tsst.ghn.studio/api/files/123/result -H "X-API-Key: $KEY"
```

---

## Ví dụ hoàn chỉnh (bash)
```bash
BASE=https://tsst.ghn.studio
KEY=sk_live_...

FID=$(curl -s -X POST $BASE/api/upload -H "X-API-Key: $KEY" -F "file=@call.wav" | jq -r .file_id)

curl -s -X POST $BASE/api/process/$FID \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"telesales_channel_split": true, "domain_correction": true}'

while [ "$(curl -s $BASE/api/files/$FID/status -H "X-API-Key: $KEY" | jq -r .status)" != "completed" ]; do
  sleep 4
done

curl -s $BASE/api/files/$FID/result -H "X-API-Key: $KEY" | jq .
```

## Ví dụ hoàn chỉnh (Python)
```python
import time, requests

BASE = "https://tsst.ghn.studio"
KEY  = "sk_live_..."
H = {"X-API-Key": KEY}

# 1) upload
with open("call.wav", "rb") as f:
    fid = requests.post(f"{BASE}/api/upload", headers=H, files={"file": f}).json()["file_id"]

# 2) process
requests.post(f"{BASE}/api/process/{fid}", headers=H,
              json={"telesales_channel_split": True, "domain_correction": True})

# 3) poll
while True:
    st = requests.get(f"{BASE}/api/files/{fid}/status", headers=H).json()["status"]
    if st in ("completed", "error"):
        break
    time.sleep(4)

# 4) result
result = requests.get(f"{BASE}/api/files/{fid}/result", headers=H).json()
text = " ".join(s.get("text", "") for s in result["segments"] if s.get("type") == "text")
print(text)
```

---

## Tham số của bước Process (body JSON, tất cả tuỳ chọn)

| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
| `model` | string | model VI mặc định | ID model ASR (dùng mặc định nếu bỏ trống) |
| `telesales_channel_split` | bool | `false` | Tách 2 kênh Agent/Khách cho call Pitel (stereo 2 kênh); bỏ diarization |
| `agent_channel` | 0 \| 1 | `0` | Kênh của Agent (0 = trái) |
| `domain_correction` | bool | `false` | Sửa lỗi nghe nhầm theo từ điển ngành GHN (tầng A, an toàn) |
| `domain_correction_full` | bool | `false` | Thêm chuẩn hoá thuật ngữ (tầng B): gộp đồng nghĩa, vd "thu hộ" → "COD" |
| `speaker_diarization` | bool | `true` | Phân tách người nói (khi KHÔNG tách kênh) |
| `num_speakers` | int | `0` | Số người nói (0 = tự đoán) |
| `punctuation_confidence` | int 1–10 | 7 | Ngưỡng thêm dấu câu |
| `case_confidence` | int 1–10 | 6 | Ngưỡng viết hoa |

> Gợi ý cho call telesales GHN (ghi âm Pitel 2 kênh): `{"telesales_channel_split": true, "domain_correction": true}`.

---

## Định dạng kết quả (`GET /result`)
```jsonc
{
  "segments": [
    { "type": "speaker", "speaker": "Agent" },
    { "type": "text", "speaker_id": 0, "text": "Em chào anh ...",
      "start": 0.0, "end": 3.2, "duration": 3.2, "confidence": 0.94 },
    { "type": "gap", "start": 3.2, "end": 3.8 }
  ],
  "speaker_names":  { "0": "Agent", "1": "Khách" },
  "speaker_colors": { "0": "#b8460c", "1": "#2f6ea5" },
  "model": "sherpa-onnx-zipformer-vi-2025-04-20",
  "duration_sec": 42.0,
  "language": "vi",
  "sample_rate": 16000
}
```
- Lấy transcript thuần: nối `text` của các segment `type == "text"` (theo thứ tự), phân đoạn theo segment `type == "speaker"`.

---

## Mã lỗi

| Mã | Nghĩa |
|---|---|
| `400` | Thiếu/sai key, sai định dạng file, hoặc body không hợp lệ |
| `401` | Không xác thực (endpoint yêu cầu đăng nhập, không phải API key) |
| `404` | `file_id` không tồn tại **hoặc không thuộc quyền của khoá này** |
| `413` | File hoặc body quá lớn |
| `429` | Vượt giới hạn số request/phút của khoá |

## Giới hạn & lưu ý
- Mỗi khoá có **giới hạn request/phút riêng** (đặt khi tạo khoá). Vượt → `429`; chờ tới đầu phút sau.
- Mỗi bước (kể cả mỗi lần poll status) đều tính vào giới hạn — poll thưa (3–5s) để tiết kiệm quota.
- Khoá **chỉ đọc được file do chính khoá đó tạo** (cách ly theo chủ khoá).
- Dịch vụ **không lưu/không trả lại audio** cho bên thứ ba khác; kết quả gắn với tài khoản chủ khoá.
- Xử lý theo **hàng đợi tuần tự**: nhiều cuộc gửi cùng lúc sẽ xếp hàng, không chạy song song.

---
*Sinh bởi Sherpa Vietnamese ASR. Cập nhật khi API thay đổi.*
