> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tiro.ooo/llms.txt
> Use this file to discover all available pages before exploring further.

# 빠른 시작

> 실시간 전사 세션을 만들고 WebSocket으로 PCM 오디오를 보내 전사와 번역 결과를 받은 뒤 세션을 닫는 과정을 Python 예제로 따라 해요.

엔터프라이즈 실시간 전사 API는 엔터프라이즈 플랜 고객에게 별도 문의로 제공하는 기능이에요. Tiro가 워크스페이스에 이 API를 켜야 따라 할 수 있어요.

이 가이드에서는 오디오 파일 하나를 실시간 속도로 보내고, 전사와 번역 결과를 터미널에 출력한 뒤 세션을 닫아요.

## 시작하기 전에

* 실시간 전사 API가 켜진 워크스페이스. 조건은 [개요](https://docs.tiro.ooo/ko/developers/realtime-transcription/overview.md#who-can-use)를 참고하세요.
* 그 워크스페이스에 묶인 API Key. Scope를 지정했다면 `realtime_transcription:write`가 있어야 해요. 키를 `TIRO_API_KEY` 환경 변수에 넣어 두세요.
* Python 3.9 이상과 `websocket-client` 패키지. `pip install websocket-client`로 설치하세요.
* 오디오 파일을 PCM으로 바꿀 `ffmpeg`.

**1. 오디오를 16 kHz mono PCM으로 바꾸기**

실시간 전사 API는 16,000 Hz, mono, signed 16-bit little-endian PCM만 받아요. 헤더 없는 raw 데이터여야 해요. `ffmpeg`로 아무 오디오 파일이나 이 형식으로 바꾸세요.

```bash theme={"system"}
ffmpeg -i meeting.m4a -ac 1 -ar 16000 -f s16le -acodec pcm_s16le meeting.pcm
```

이 형식은 1초에 32,000바이트예요. 마이크 입력을 그대로 보낼 때도 같은 형식으로 맞추세요.

**2. 세션 만들기**

고객 서버에서 세션을 만드세요. 이 예제는 한국어를 전사하고 영어로 번역해요. 번역이 필요 없으면 `translationLocale`을 빼세요.

```bash cURL theme={"system"}
curl -X POST https://api.tiro.ooo/v1/external/realtime-transcriptions \
  -H "Authorization: Bearer $TIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transcriptionLocale": "ko_KR",
    "translationLocale": "en_US"
  }'
```

```python Python theme={"system"}
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://api.tiro.ooo/v1/external/realtime-transcriptions",
    data=json.dumps({"transcriptionLocale": "ko_KR", "translationLocale": "en_US"}).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['TIRO_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    session = json.load(response)
print(session["id"], session["wsUrl"])
```

성공하면 `201 Created`와 함께 세션을 받아요.

```json theme={"system"}
{
  "id": "Rt7Yq2PbX9cDk",
  "workspaceGuid": "Wk8sQ2mLx1aB7",
  "status": "OPEN",
  "endReason": null,
  "wsUrl": "wss://api.tiro.ooo/v1/external/realtime-transcriptions/Rt7Yq2PbX9cDk/stream",
  "streamToken": "rtt_5Hq3...",
  "maxDurationMs": 10800000,
  "audioDurationMs": 0,
  "transcriptionLocale": "ko_KR",
  "translationLocale": "en_US",
  "apiKeyId": "tk_8f2a91c0d3e4",
  "createdAt": "2026-11-05T09:00:00Z",
  "endedAt": null
}
```

`streamToken`은 이 응답에서 한 번만 받을 수 있어요. 세션이 `OPEN`인 동안 다시 연결할 때도 같은 토큰을 쓰니 안전하게 보관하세요. `wsUrl`은 직접 조립하지 말고 받은 값을 그대로 쓰세요.

**3. WebSocket 연결하기**

클라이언트에서 `wsUrl`에 연결하고 `Authorization` 헤더에 `streamToken`을 넣으세요. 쿼리 문자열이나 쿠키로는 토큰을 보낼 수 없어요.

```python theme={"system"}
import websocket

ws = websocket.create_connection(
    session["wsUrl"],
    header=[f"Authorization: Bearer {session['streamToken']}"],
    suppress_origin=True,
)
print(ws.recv())
```

연결되면 첫 메시지로 `session.started`를 받아요. 보통 1초 안팎에 오고, 드물게 수십 초 걸려요. 이 메시지를 기다리지 않고 오디오를 먼저 보내도 돼요.

```json theme={"system"}
{ "type": "session.started", "id": "Rt7Yq2PbX9cDk", "resumed": false, "audioOffsetMs": 0 }
```

**4. 오디오를 보내고 결과 받기**

오디오를 3,200바이트(100 ms) 단위로 잘라 바이너리 프레임으로 보내세요. 녹음된 파일을 보낼 때도 실제 시간 속도에 맞춰 보내야 해요. 실제 경과 시간보다 60초 넘게 앞서면 연결이 close `4029`로 닫혀요.

결과는 `transcript`와 `translation` 텍스트 메시지로 와요. 같은 `segmentId`의 `isFinal: false` 메시지는 화면의 이전 텍스트를 통째로 바꾸고, `isFinal: true`가 오면 그 문장이 확정돼요.

```json theme={"system"}
{ "type": "transcript", "segmentId": "Ab3kZ9qLm2XyW-1-17", "isFinal": true, "text": "오늘 회의는 일정 점검부터 하겠습니다.", "locale": "ko_KR" }
{ "type": "translation", "translationId": "t3-9", "isFinal": true, "locale": "en_US", "text": "Today's meeting will start with a schedule review.", "sourceSegmentId": null }
```

**5. 세션 닫기**

마지막 오디오를 보낸 뒤 `{"type": "session.end"}`를 보내세요. Tiro가 남은 결과를 `isFinal: true`로 모두 보내고 `session.ended`를 보낸 다음 close `1000`으로 연결을 닫아요. `session.ended`를 받을 때까지 연결을 유지하세요. 남은 결과를 보내는 데는 최대 30초가 걸려요.

```json theme={"system"}
{ "type": "session.ended", "id": "Rt7Yq2PbX9cDk", "endReason": "CLIENT_REQUESTED", "audioDurationMs": 3605120, "endedAt": "2026-11-05T10:01:30Z" }
```

WebSocket을 쓸 수 없는 상황이면 고객 서버에서 `POST /v1/external/realtime-transcriptions/{id}/end`를 호출해 세션을 닫으세요.

> [!WARNING]
>
> WebSocket 연결만 닫고 세션을 끝내지 않으면 세션은 `OPEN`으로 남아요. 오디오 없이 15분이 지나 자동으로 끝날 때까지 워크스페이스 동시 세션 10개 중 한 자리를 차지해요.

## 전체 예제

아래 스크립트는 위 단계를 하나로 묶은 거예요. PCM 파일을 실시간 속도로 보내고, 결과를 출력하고, 세션을 닫아요. 연결이 끊기면 재접속하지 않고 REST로 세션을 닫아요. 재접속을 구현하려면 [재접속 규칙](https://docs.tiro.ooo/ko/developers/realtime-transcription/protocol.md#reconnect)을 따르세요.

> [accordion] stream\_pcm.py 전체 코드 보기
>
> ```python stream_pcm.py theme={"system"}
> import json
> import os
> import ssl
> import sys
> import time
> import urllib.request
>
> import websocket
>
> BASE_URL = "https://api.tiro.ooo"
> API_KEY = os.environ["TIRO_API_KEY"]
> FRAME_BYTES = 3200
> FRAME_SECONDS = 0.1
>
>
> def rest(method, path, body=None):
>     request = urllib.request.Request(
>         f"{BASE_URL}{path}",
>         data=json.dumps(body).encode() if body is not None else None,
>         headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
>         method=method,
>     )
>     with urllib.request.urlopen(request) as response:
>         return json.load(response)
>
>
> def handle(message):
>     kind = message.get("type")
>     if kind in ("transcript", "translation"):
>         marker = "FINAL  " if message["isFinal"] else "partial"
>         print(f"{marker} {kind} ({message.get('locale')}) {message['text']}")
>     elif kind == "error":
>         print(f"error reason={message['reason']} retryable={message['retryable']} {message['message']}")
>         if message["fatal"]:
>             raise ConnectionError(message["reason"])
>     elif kind == "session.ended":
>         print(f"session.ended endReason={message['endReason']} audioDurationMs={message['audioDurationMs']}")
>
>
> def recv_json(ws):
>     raw = ws.recv()
>     if not raw:
>         raise ConnectionError("connection closed")
>     return json.loads(raw)
>
>
> def drain(ws):
>     ws.settimeout(0.0)
>     try:
>         while True:
>             handle(recv_json(ws))
>     except (websocket.WebSocketTimeoutException, BlockingIOError, ssl.SSLWantReadError):
>         pass
>     finally:
>         ws.settimeout(None)
>
>
> def stream(session, pcm):
>     ws = websocket.create_connection(
>         session["wsUrl"],
>         header=[f"Authorization: Bearer {session['streamToken']}"],
>         suppress_origin=True,
>     )
>     started_at = time.monotonic()
>     sent_seconds = 0.0
>     for offset in range(0, len(pcm), FRAME_BYTES):
>         ws.send_binary(pcm[offset : offset + FRAME_BYTES])
>         sent_seconds += FRAME_SECONDS
>         drain(ws)
>         wait = started_at + sent_seconds - time.monotonic()
>         if wait > 0:
>             time.sleep(wait)
>
>     ws.send(json.dumps({"type": "session.end"}))
>     ws.settimeout(60)
>     while True:
>         message = recv_json(ws)
>         handle(message)
>         if message.get("type") == "session.ended":
>             break
>     ws.close()
>
>
> def main():
>     with open(sys.argv[1], "rb") as file:
>         pcm = file.read()
>     session = rest("POST", "/v1/external/realtime-transcriptions", {"transcriptionLocale": "ko_KR", "translationLocale": "en_US"})
>     print(f"created id={session['id']}")
>     try:
>         stream(session, pcm)
>     except (websocket.WebSocketException, ConnectionError, OSError) as error:
>         print(f"websocket failed: {error}. Ending the session over REST.")
>         rest("POST", f"/v1/external/realtime-transcriptions/{session['id']}/end")
>         sys.exit(1)
>
>
> if __name__ == "__main__":
>     main()
> ```

PCM 파일 경로를 인자로 넘겨 실행하세요.

```bash theme={"system"}
python stream_pcm.py meeting.pcm
```

## 다음 단계

> [card] [WebSocket 프로토콜](https://docs.tiro.ooo/ko/developers/realtime-transcription/protocol.md)
>
> 메시지 필드, 에러, 재접속, close code를 확인하세요.

> [card] [API 레퍼런스](https://docs.tiro.ooo/api-reference/realtime-transcription/create-realtime-transcription.md)
>
> 세션 생성, 조회, 목록, 종료 REST API를 확인하세요.

Enterprise Plan

이 기능은 엔터프라이즈 플랜에서 제공돼요.  조직에 필요한 보안 구성을 영업팀이 함께 설계해 드려요.

[도입 문의하기](https://tiro.ax/ko/contact?utm_source=docs\&utm_medium=plan_callout\&utm_campaign=plan_upsell_enterprise\&utm_content=developers-realtime-transcription-quickstart)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.