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

# 운세 스트리밍 (SSE)

<Info>
  **v1 Enterprise API (Coming Soon)**

  이 엔드포인트는 Enterprise 버전에서 제공될 예정입니다.
  현재는 [v0 API](/api-reference/v0/overview)를 사용하세요.
</Info>

Server-Sent Events(SSE)를 사용하여 운세를 실시간으로 스트리밍합니다. AI 모델의 응답이 생성되는 대로 클라이언트에 전달되어 사용자 경험을 향상시킵니다.

<Note>
  스트리밍 API는 텍스트가 생성되는 즉시 전달되므로, 전체 응답을 기다리는 것보다 체감 응답 속도가 빠릅니다. 채팅 UI나 실시간 표시가 필요한 경우 이 API를 사용하세요.
</Note>

***

## Request Body 파라미터

<ParamField body="profile_id" type="string" required>
  운세를 생성할 프로필의 ID입니다. `prf_` 접두사로 시작합니다.
</ParamField>

<ParamField body="fortune_type" type="string" required>
  운세 유형입니다. `daily`, `yearly`, `monthly` 중 하나입니다.
</ParamField>

<ParamField body="fortune_date" type="string">
  운세 날짜입니다. ISO 8601 형식(YYYY-MM-DD)입니다. 기본값은 오늘입니다.
</ParamField>

<ParamField body="model" type="string" default="haiku">
  사용할 AI 모델입니다. `haiku`, `sonnet`, `gpt4o` 중 하나입니다.
</ParamField>

***

## Response

스트리밍 응답은 `text/event-stream` 형식으로 반환됩니다. 각 이벤트는 다음 형식을 따릅니다.

### 이벤트 타입

| 이벤트        | 설명                                |
| ---------- | --------------------------------- |
| `start`    | 스트리밍 시작. 운세 ID와 메타데이터를 포함합니다.     |
| `delta`    | 텍스트 조각. 생성된 텍스트의 일부입니다.           |
| `category` | 카테고리별 운세 완료. 각 카테고리가 완료될 때 발생합니다. |
| `done`     | 스트리밍 완료. 최종 통계를 포함합니다.            |
| `error`    | 에러 발생. 에러 정보를 포함합니다.              |

### 실패

| 상태 코드 | 에러 타입                  | 설명              |
| ----- | ---------------------- | --------------- |
| 400   | `validation_error`     | 요청 데이터가 유효하지 않음 |
| 401   | `authentication_error` | API 키가 유효하지 않음  |
| 404   | `not_found`            | 프로필을 찾을 수 없음    |
| 429   | `rate_limited`         | 요청 한도 초과        |

***

## 요청 예시

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.sajuapi.dev/v1/fortunes/stream \
    -H "X-API-Key: bs_live_xxx" \
    -H "Content-Type: application/json" \
    -H "Accept: text/event-stream" \
    -d '{
      "profile_id": "prf_abc123def456",
      "fortune_type": "daily",
      "model": "sonnet"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.sajuapi.dev/v1/fortunes/stream', {
    method: 'POST',
    headers: {
      'X-API-Key': 'bs_live_xxx',
      'Content-Type': 'application/json',
      'Accept': 'text/event-stream'
    },
    body: JSON.stringify({
      profile_id: 'prf_abc123def456',
      fortune_type: 'daily',
      model: 'sonnet'
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));
        console.log(data.event, data);
      }
    }
  }
  ```

  ```python Python theme={null}
  import requests
  import json

  response = requests.post(
      'https://api.sajuapi.dev/v1/fortunes/stream',
      headers={
          'X-API-Key': 'bs_live_xxx',
          'Accept': 'text/event-stream'
      },
      json={
          'profile_id': 'prf_abc123def456',
          'fortune_type': 'daily',
          'model': 'sonnet'
      },
      stream=True
  )

  for line in response.iter_lines():
      if line:
          line = line.decode('utf-8')
          if line.startswith('data: '):
              data = json.loads(line[6:])
              print(data['event'], data)
  ```
</CodeGroup>

***

## 응답 예시

```
event: start
data: {"event":"start","id":"ftn_xyz789abc123","profile_id":"prf_abc123def456","fortune_type":"daily","model":"sonnet"}

event: delta
data: {"event":"delta","text":"오늘은 "}

event: delta
data: {"event":"delta","text":"새로운 기회가 "}

event: delta
data: {"event":"delta","text":"찾아오는 날입니다."}

event: category
data: {"event":"category","category":"overall","score":85,"description":"전반적으로 긍정적인 하루가 예상됩니다."}

event: category
data: {"event":"category","category":"wealth","score":75,"description":"재물운이 안정적입니다."}

event: category
data: {"event":"category","category":"love","score":90,"description":"인간관계가 원만합니다."}

event: category
data: {"event":"category","category":"health","score":80,"description":"건강은 양호합니다."}

event: category
data: {"event":"category","category":"career","score":88,"description":"업무에서 좋은 성과가 기대됩니다."}

event: done
data: {"event":"done","id":"ftn_xyz789abc123","score":85,"latency_ms":2340,"tokens_used":456}
```

***

## React 예시

```jsx theme={null}
import { useState, useEffect } from 'react';

function FortuneStream({ profileId }) {
  const [fortune, setFortune] = useState('');
  const [categories, setCategories] = useState({});
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchFortune = async () => {
      const response = await fetch('/v1/fortunes/stream', {
        method: 'POST',
        headers: {
          'X-API-Key': 'bs_live_xxx',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          profile_id: profileId,
          fortune_type: 'daily'
        })
      });

      const reader = response.body.getReader();
      const decoder = new TextDecoder();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const lines = decoder.decode(value).split('\n');
        for (const line of lines) {
          if (!line.startsWith('data: ')) continue;
          const data = JSON.parse(line.slice(6));

          switch (data.event) {
            case 'delta':
              setFortune(prev => prev + data.text);
              break;
            case 'category':
              setCategories(prev => ({
                ...prev,
                [data.category]: data
              }));
              break;
            case 'done':
              setIsLoading(false);
              break;
          }
        }
      }
    };

    fetchFortune();
  }, [profileId]);

  return (
    <div>
      <p>{fortune}</p>
      {isLoading && <span>생성 중...</span>}
    </div>
  );
}
```

***

## 연결 관리

* 연결 타임아웃: 30초
* 최대 스트리밍 시간: 60초
* 클라이언트가 연결을 끊으면 서버도 생성을 중단합니다
* 네트워크 오류 시 `error` 이벤트가 발생합니다
