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

# SDK

> SDK로 애플리케이션 코드에서 Tello 통화를 제어해요.

## 할 수 있는 일

Tello SDK로 코드에서 통화를 만들고, 턴 이벤트에 응답해요. 게이트웨이가 진행 중인 통화에서
상대방이 말한 턴을 실시간으로 넘겨주고, 핸들러가 만든 답변이 다시 통화로 전달돼요. 대화의
두뇌는 여러분 코드가 맡아요. 포털의 **SDK** 화면에서 SDK용 API Key를 발급·폐기해요.

```bash SDK Server URL theme={null}
wss://api.telloai.io/sdk
```

<Note>
  전송 계층은 WebSocket뿐이에요. REST와 webhook은 제공하지 않아요.
</Note>

## 시작하기 전에

포털 로그인이 필요하고, SDK 설치 환경이 필요해요.

<CodeGroup>
  ```bash Python theme={null}
  pip install tello-sdk    # Python 3.10+. import 이름은 tello
  ```

  ```bash Node theme={null}
  npm install @tello/sdk   # 런타임 의존성은 ws 하나
  ```

  ```bash Go theme={null}
  go get github.com/tello-tft/tello-go   # Go 1.22+
  ```

  ```kotlin Java theme={null}
  // build.gradle.kts — Java 17+
  dependencies {
      implementation("ai.tello:tello-sdk:0.1.0")
  }
  ```
</CodeGroup>

## 사용 방법

포털 **SDK** 화면 위쪽의 **SDK Server URL**을 복사해 두어요(WebSocket 전용, SDK의 `TELLO_URL`
값). `+ 새 Key 발급`으로 키를 만들고, 코드에 키와 URL을 넣거나 환경변수로 지정해요.

```bash theme={null}
export TELLO_API_KEY="tello_live_xxx"
export TELLO_URL="wss://api.telloai.io/sdk"
```

인자 없이 클라이언트를 생성하면 위 두 변수를 읽어요.

### 인증

키 인증은 **애플리케이션 레벨 핸드셰이크**로 이뤄져요. 소켓이 열리면 SDK가 `token` 필드에
키를 담은 `auth` 프레임을 보내고, 서버의 `auth.ok`를 받은 뒤에야 나머지 동작이 시작돼요.

```json theme={null}
{ "event": "auth", "data": { "token": "<API_KEY>" } }
```

키는 WS 업그레이드 요청에도, URL query에도 실리지 않아요. `Authorization` 헤더도 쓰지 않으므로
URL·로그·오류 메시지에 키가 남지 않아요. 전부 내부 처리라 `auth`를 직접 호출할 일은 없고,
인증이 끝나기 전에는 연결이 성공하지 않아요. 키가 거부되면(`unauthenticated` 오류 프레임 또는
`4401` 종료) 연결이 실패해요.

### 연결 + 통화 시작

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  from tello import TelloClient, EventType

  async def main():
      async with TelloClient(
          api_key="tello_live_xxx",
          url="wss://api.telloai.io/sdk",
      ) as client:
          @client.on(EventType.USER_TURN)
          async def on_user_turn(event):
              await client.answer(text="확인했습니다. 계속 말씀해주세요.")

          await client.create_call(to="+821012345678", prompt="예약 확인")
          await client.wait_closed()

  asyncio.run(main())
  ```

  ```ts Node theme={null}
  import { EventType, TelloClient } from "@tello/sdk";

  const client = await new TelloClient({
    apiKey: process.env.TELLO_API_KEY,
    url: process.env.TELLO_URL ?? "wss://api.telloai.io/sdk",
  }).connect();

  client.on(EventType.UserTurn, async (event) => {
    await client.answer(`heard: ${event.text ?? ""}`);
  });

  await client.createCall("+821012345678", "예약 확인");
  await client.waitClosed();
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"log"

  	"github.com/tello-tft/tello-go/tello"
  )

  func main() {
  	ctx := context.Background()
  	client, err := tello.NewClient("")
  	if err != nil {
  		log.Fatal(err)
  	}

  	client.On(tello.EventTypeUserTurn, func(ctx context.Context, event tello.Event) error {
  		return client.Answer(ctx, "heard: "+event.Text, "", "")
  	})

  	if err := client.Connect(ctx); err != nil {
  		log.Fatal(err)
  	}
  	defer client.Close()

  	if err := client.CreateCall(ctx, "+821012345678", "예약 확인", nil, ""); err != nil {
  		log.Fatal(err)
  	}
  	if err := client.WaitClosed(ctx); err != nil {
  		log.Fatal(err)
  	}
  }
  ```

  ```java Java theme={null}
  try (TelloClient client =
          new TelloClient("tello_live_xxx", "wss://api.telloai.io/sdk").connectBlocking()) {
      client.on(EventType.USER_TURN, e -> {
          TurnEvent turn = (TurnEvent) e;
          client.answer("확인했습니다. 계속 말씀해주세요.");
      });
      client.createCall("+821012345678", "예약 확인").join();
      client.waitClosed();
  }
  ```
</CodeGroup>

<Warning>
  핸들러는 **연결 전에** 등록하세요. 연결 후 등록하면 초기 프레임을 놓쳐요.
</Warning>

## 이벤트

게이트웨이가 보내는 수신 프레임은 `type`으로 디스패치돼요.

| 이벤트                  | 채워지는 필드                                                                            |
| -------------------- | ---------------------------------------------------------------------------------- |
| `call.created`       | `callId`, `sessionId`                                                              |
| `user.turn`          | `turnIndex`, `text`                                                                |
| `agent.turn`         | `turnIndex`, `text`                                                                |
| `answer.accepted`    | `requestId`, `messageId`                                                           |
| `dtmf.accepted`      | `requestId`, `messageId`, `digits`                                                 |
| `call.summary`       | `requestId`, `status`, `durationSeconds`, `transcript`, `summary`, `creditCharged` |
| `call.statusChanged` | `status`, `previousStatus`                                                         |

필드명 표기는 언어별 관례를 따라요(Python은 `snake_case`, Go는 `PascalCase`).

## 명령

| 명령           | 용도                     |
| ------------ | ---------------------- |
| `createCall` | 발신 시작                  |
| `answer`     | 현재 턴에 답변 전송            |
| `sendDtmf`   | DTMF 숫자 전송 (`"1234#"`) |
| `cancel`     | 진행 중인 통화 취소            |
| `getSummary` | 통화 요약 조회               |

## 주의사항

* 전체 키는 발급 직후 한 번만 볼 수 있어요. 분실하면 폐기 후 재발급해요.
* 엔드포인트는 WebSocket 전용이에요.
* 폐기하면 즉시 사용할 수 없어요.

프레임 계약 전문은 각 SDK 저장소의 `docs/protocol/sdk-ws.v1.md`에 있어요.

## 관련 문서

* [MCP](/developer/mcp)
* [A2A](/developer/a2a)
