> ## 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.

# search_notes

> 매칭된 노트를 주요 문서와 함께 반환하는 심층 keyword 검색

## 개요

`search_notes`는 노트 디스커버리의 심층 단계예요. **필수 keyword**를 받아 주요 문서(one-pager, custom)와 함께 채워진 매칭 노트를 반환해요. 어떤 노트가 매칭되는지만 보는 게 아니라, LLM이 주제 이면의 결정, action item, 결론 등 **맥락을 이해해야** 할 때 사용하세요.

가벼운 메타데이터만 필요한 목록 조회(예: "폴더 X에 어떤 노트가 있지")는 대신 [`list_notes`](./list-notes)를 사용하세요.

**주요 사용 사례:**

* "OKR Q2에 대해 팀이 무엇을 결정했지?" — 주제로 검색하고 문서를 inline으로 읽어요.
* "이 결정의 출처 노트가 어디 있지?" — keyword로 노트를 찾고 문서가 맥락을 줘요.
* "네이버에 대해 우리가 쓴 걸 다 보여줘" — 내용까지 포함한 한국어 keyword 검색.

**핵심 기능:**

* keyword 필수. 결과는 전문 검색 관련도(동점 시 `createdAt` 내림차순)로 정렬돼요.
* 매칭된 각 노트는 주요 문서(one-pager, custom)를 inline으로 담아요.
* 문서 내용에서 HTML이 제거돼요. 5,000자보다 큰 문서는 잘리고 표시돼요.
* 검색 인덱스를 사용할 수 없을 때 우아하게 성능 저하(graceful degradation)해요.

<Warning>
  **`list_notes`보다 무거워요.** 각 결과에 문서 내용이 포함돼요(노트당 \~1,500 토큰). 기본 페이지 크기는 `50`, 최대 `200`이에요. 메타데이터만 필요하면 [`list_notes`](./list-notes)를 사용하세요.
</Warning>

## 파라미터

| Parameter              | Type   | Required | Description                                                                                                                                   |
| ---------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `keyword`              | string | **Yes**  | 검색 keyword(1자 이상). 제목과 단락 내용을 기준으로 전문 검색 매칭.                                                                                                  |
| `pagination.cursor`    | string | Optional | 향후 사용 예약. 심층 검색 endpoint는 현재 `null`을 방출해요.                                                                                                    |
| `pagination.size`      | number | Optional | 페이지당 결과 수(기본값 `50`, 최대 `200`).                                                                                                                |
| `workspaceGuid`        | string | Optional | 워크스페이스 GUID(`list_workspaces`에서 확인). 생략하면 접근 가능한 모든 워크스페이스에서 검색해요 — workspace 범위 key는 자동으로 자신의 워크스페이스를 검색해요. 하나를 전달하면 해당 워크스페이스로 검색 범위를 한정해요. |
| `filter.folderId`      | string | Optional | 폴더로 제한(재귀적 — 하위 폴더 포함).                                                                                                                       |
| `filter.createdAtFrom` | string | Optional | ISO 8601 datetime, `createdAt`의 포함 하한.                                                                                                        |
| `filter.createdAtTo`   | string | Optional | ISO 8601 datetime, `createdAt`의 배제 상한.                                                                                                        |

### keyword (필수)

검색어예요. 서버 측에서 토큰화돼요. 한국어 텍스트는 형태소 분석돼요(예: `네이버` → `[네, 버]`). 비었거나 공백만 있는 keyword는 `400`을 반환해요.

keyword 검색은 user 범위 API key와 workspace 범위 API key를 모두 받아요. `workspaceGuid`를 생략하면 접근 가능한 모든 워크스페이스에 걸쳐 검색해요 — workspace 범위 key는 자동으로 자신의 워크스페이스를 검색해요. 특정 워크스페이스로 검색 범위를 한정하려면 명시적인 `workspaceGuid`([파라미터](#파라미터) 참고)를 전달하세요.

### filter.folderId (선택)

`list_notes`와 의미가 같아요. 재귀적 폴더 범위이며 서버 측에서 인가돼요.

### pagination (선택)

`{ cursor, size }`. 기본값 `size: 50`, 최대 `200`. 이 상한은 결과당 더 높은 비용(각 노트가 문서를 담음)을 반영해요.

## 응답 형식

### 성공 응답

```json theme={"system"}
{
  "notes": [
    {
      "noteGuid": "abc-123-def",
      "title": "OKR Q2 Planning",
      "webUrl": "https://platform.tiro.ooo/notes/abc-123-def",
      "createdAt": "2026-04-15T10:00:00Z",
      "updatedAt": "2026-04-15T11:30:00Z",
      "recordingDurationSeconds": 3625,
      "sourceType": "live-voice",
      "participants": [
        { "name": "Alice Kim", "email": "alice@example.com" }
      ],
      "matchedSnippets": null,
      "documents": [
        {
          "documentGuid": "7821",
          "templateId": 12,
          "templateTitle": "One Pager",
          "content": "## 결정 사항\n- 자동 ingest 우선...\n## Action Items\n- ...",
          "truncated": false,
          "updatedAt": "2026-04-15T11:30:00Z"
        }
      ]
    }
  ],
  "nextCursor": null,
  "degraded": false,
  "degradedReason": null
}
```

**필드 설명:**

| Field                               | Type              | Description                                                                                     |
| ----------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
| `notes[]`                           | array             | 매칭된 노트, 전문 검색 관련도(동점 시 `createdAt` 내림차순)로 정렬.                                                   |
| `notes[].matchedSnippets`           | string\[] \| null | 하이라이트 스니펫용 예약. 현재 `null`로, "하이라이트 미구현"과 "검색했으나 매칭 하이라이트 없음"을 구분해요.                              |
| `notes[].documents[]`               | array             | 노트의 주요 문서. 노트에 문서가 없으면 빈 배열.                                                                    |
| `notes[].documents[].documentGuid`  | string            | 안정적인 문서 식별자.                                                                                    |
| `notes[].documents[].templateId`    | number \| null    | 숫자 template id — 유일하게 안정적인 구분자.                                                                 |
| `notes[].documents[].templateTitle` | string            | 표시 라벨(예: `"One Pager"`, `"회의록"`). **locale에 따라 달라지고 custom 템플릿은 사용자가 편집 가능** — 이 문자열로 구분하지 마세요. |
| `notes[].documents[].content`       | string            | HTML 제거된 markdown 친화 텍스트. 5,000자로 제한. `truncated`를 참고하세요.                                       |
| `notes[].documents[].truncated`     | boolean           | 내용이 잘렸으면 `true`. 나머지가 필요하면 [`get_note(include: ['documents'])`](./get-note)로 전체 문서를 가져오세요.      |
| `nextCursor`                        | string \| null    | 향후 사용 예약. 현재 항상 `null`.                                                                         |
| `degraded`                          | boolean           | 검색 인덱스를 사용할 수 없거나 실패했으면 `true`.                                                                 |
| `degradedReason`                    | string \| null    | `degraded=true`일 때: `"search_index_unavailable"` 또는 `"search_index_degraded"`.                  |

<Warning>
  **`templateTitle`로 구분하지 마세요.** 이건 enum이 아니라 표시 라벨이에요. boolean 확인("이게 one-pager인가?")에는 알려진 관리형 템플릿 id에 대해 `templateId`를 사용하거나, 두 필드를 모두 노출하고 UI가 라벨로 고르게 하세요.
</Warning>

### 성능 저하 응답

검색 인덱스를 사용할 수 없으면 응답은 `degraded=true`로 설정되고 빈 `notes` 배열을 반환해요:

```json theme={"system"}
{
  "notes": [],
  "nextCursor": null,
  "degraded": true,
  "degradedReason": "search_index_unavailable"
}
```

`degradedReason`은 다음 중 하나예요:

* `search_index_unavailable` — 이 환경에서 검색 인덱스가 비활성화됨.
* `search_index_degraded` — 쿼리 도중 검색 인덱스가 오류를 던짐.

재시도를 미루거나 결과를 적절히 해석할 수 있도록 이를 LLM/사용자에게 노출하세요.

## 사용 예시

### 예시 1: 주제 검색

**요청:**

```json theme={"system"}
{
  "keyword": "OKR"
}
```

OKR을 언급하는 가장 관련도 높은 노트 10개를 각각의 주요 문서와 함께 inline으로 반환해요.

### 예시 2: 폴더 범위 한국어 keyword

**요청:**

```json theme={"system"}
{
  "keyword": "네이버",
  "filter": {
    "folderId": "455765"
  },
  "pagination": {
    "size": 5
  }
}
```

폴더 `455765`(및 하위)에서만 `네이버`를 검색해요. 한국어 형태소 분석이 서버 측에서 실행돼요.

### 예시 3: 날짜 범위 심층 검색

**요청:**

```json theme={"system"}
{
  "keyword": "release",
  "filter": {
    "createdAtFrom": "2026-04-01T00:00:00Z",
    "createdAtTo": "2026-05-01T00:00:00Z"
  }
}
```

## 권장 사례

<AccordionGroup>
  <Accordion title="list_notes로 시작하고, 내용이 필요하면 search_notes로 전환하기">
    먼저 [`list_notes`](./list-notes)로 어떤 노트가 매칭되는지 찾으세요. LLM이 답하기 위해 여전히 문서 내용이 필요하면 같은 keyword로 `search_notes`로 전환하세요. 가장 저렴한 점진적 공개 경로예요.
  </Accordion>

  <Accordion title="folderId로 검색 범위 좁히기">
    폴더 범위 검색은 관련도 공간이 더 작아서 더 빠르고 순위가 더 좋은 결과를 반환해요. 폴더 후보를 먼저 찾으려면 `list_notes(filter.folderId)`와 함께 쓰세요.
  </Accordion>

  <Accordion title="`nextCursor`로 반복하지 마세요">
    심층 검색 endpoint는 현재 `nextCursor: null`을 방출해요. 결과 30개로 부족하면 페이지네이션 대신 keyword를 더 구체적으로 다듬거나 folderId/dateRange로 범위를 좁히세요.
  </Accordion>

  <Accordion title="`degraded` 플래그를 주시하세요">
    `degraded=true` 응답은 솔직한 신호예요. 검색 인덱스가 실행되지 못했어요. 빈 `notes` 배열을 "매칭 없음"으로 취급하지 말고, 재시도 타이밍을 적절히 잡을 수 있도록 성능 저하를 사용자/LLM에게 노출하세요.
  </Accordion>
</AccordionGroup>

## 자주 발생하는 오류

### 빈 keyword

<Error>
  ```json theme={"system"}
  {
    "error": "keyword must not be blank",
    "code": "BAD_REQUEST",
    "statusCode": 400
  }
  ```
</Error>

## 토큰 사용량

**노트당 (문서 포함):** 평균 약 1,500 토큰. 잘린 문서는 원본 크기와 관계없이 약 1,500 토큰으로 제한돼요.

<Tip>
  **`get_note_transcript`와 비교하면**(회의 시간당 \~3,000–5,000 토큰), `search_notes`는 보통 10배 더 압축적이면서 같은 핵심 결정을 다루는 정제된 문서를 반환해요. 정확한 발화가 필요할 때만 transcript를 사용하세요.
</Tip>
