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

# 認証

> ワークスペースのユーザーキーとシステムキーの違い、Tiro APIの認証方法、外部エンドポイントごとに必要なスコープを説明します。

## APIキー

Tiro APIは認証にAPIキーを使用します。すべてのAPIリクエストには、Bearerトークン形式を用いて、Authorizationヘッダーに有効なAPIキーを含める必要があります。

各キーは 1 つの**ワークスペース**に属します。キーはそのワークスペースのリソース（ノート、トランスクリプト、サマリー、フォルダ）にのみ到達し、その外には届きません。先にワークスペースを選んでから、キーを作成してください。

### キーの種類と権限

Tiro API keyは、リクエストの主体によって2種類に分かれます。

| キーの種類             | 識別方法                            | できること                                                                      |
| ----------------- | ------------------------------- | -------------------------------------------------------------------------- |
| **ユーザーキー**        | キーが特定のユーザーidentityに紐づきます。       | 読み取りと書き込みの両方に使えます。ノートドキュメントの作成や共有リンクの変更など、ワークスペースのデータを変更する操作にはユーザーキーが必要です。 |
| **ワークスペースシステムキー** | キーがユーザーではなく、ワークスペースのコンテナに紐づきます。 | ワークスペース全体に共有されたリソースを読み取る用途です。Voice File JobのライフサイクルAPIに限り、書き込み操作を利用できます。   |

ワークスペースシステムキーでフォルダを読む場合、ワークスペースの全メンバーに共有されたフォルダだけが返されます。個人フォルダ、特定ユーザーにだけ共有されたフォルダ、またはVoice File Jobのライフサイクル以外で作成・更新・削除が必要な場合は、ユーザーキーを作成して使用してください。

<Note>
  **移行期間について。** この読み取り範囲が適用される前に作成されたワークスペースシステムキーは、当面これまでの動作を維持し、順次移行される予定です。そのため、新しく作成したキーが以前のキーより少ないノートを返すことがありますが、これは正常な動作です。移行が完了すると、すべてのシステムキーが共有フォルダの範囲に従います。個人フォルダのノートも読み取る必要がある場合は、上記のとおりユーザーキーを作成して使用してください。
</Note>

<Note>
  ワークスペースシステムキーがまだワークスペースに紐づいていない場合、認証は`401 Unauthorized`で失敗することがあります。キー形式が正しいのに401が続く場合は、キーが接続されているワークスペースと作成画面を先に確認してください。
</Note>

<Warning>
  **レガシー個人キーは間もなく利用できなくなります。** ワークスペース導入より前に作成した個人 API キーは、**2026年6月30日**に動作を停止します。その前にワークスペースキーを作成して差し替えてください — 下記の[レガシー個人キー](#レガシー個人キー非推奨)をご覧ください。
</Warning>

## スコープ (Scope)

スコープはキーが**どの操作を行えるか**を絞り込む最小権限の設定です。発行時に選択でき、発行後に変更することもできます。

* **スコープを指定しないキーはすべての操作を行えます。** スコープは権限を付与するのではなく**絞り込む**ためのもので、指定したスコープに対応する API のみ呼び出せます。それ以外は `403 insufficient_scope` で拒否されます。
* **`write` スコープは同じリソースの `read` を含みます。** たとえば `note:write` のみを指定したキーでも、`note:read` が必要な読み取り API を呼び出せます。
* スコープはすべてのキー種別（ユーザー・ワークスペース・組織）に適用されます。
* 選択可能なスコープ一覧は `GET /v1/api-key-scopes` で取得できます。

### スコープ一覧

| スコープ                          | 説明                               |
| ----------------------------- | -------------------------------- |
| `note:read`                   | ノートのメタデータ・一覧・トランスクリプトの取得         |
| `note:write`                  | ノートのタイトルなどメタデータの更新               |
| `note_summary:read`           | ノート要約（ワンページドキュメント）の取得            |
| `note_document:read`          | 生成済みドキュメントの取得                    |
| `note_document:write`         | カスタムテンプレートからのドキュメント生成            |
| `note_document_template:read` | ノートドキュメントテンプレートの一覧・取得            |
| `folder:read`                 | フォルダの取得                          |
| `folder:write`                | フォルダの作成・更新・削除                    |
| `wiki:read`                   | Wiki の取得・検索                      |
| `word_memory:read`            | 単語帳の取得                           |
| `word_memory:write`           | 単語帳の作成・更新・削除                     |
| `workspace:read`              | 組織のワークスペースの一覧・取得                 |
| `workspace:write`             | 組織のワークスペースの作成・名称変更・使用上限の設定・削除    |
| `voice_file_job:read`         | Voice File Jobの状態・文字起こし・翻訳・要約の取得 |
| `voice_file_job:write`        | Voice File Jobの作成・処理開始・削除        |

### API ごとに必要なスコープ

スコープを指定したキーは、そのスコープを持つ API のみ呼び出せます。スコープを指定しないキーはすべての API を呼び出せます。

| API                                                                                                                                                                                      | 必要なスコープ                       |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `GET /v1/external/notes`, `POST /v1/external/notes/search`                                                                                                                               | `note:read`                   |
| `GET /v1/external/notes/{guid}`, `GET /v1/external/notes/{guid}/folders`                                                                                                                 | `note:read`                   |
| `GET /v1/external/notes/{guid}/paragraphs`                                                                                                                                               | `note:read`                   |
| `GET /v1/external/notes/{guid}/summaries`, `.../summaries/{id}`                                                                                                                          | `note_summary:read`           |
| `GET /v1/external/notes/{guid}/share-link`                                                                                                                                               | `note:read`                   |
| `PATCH /v1/external/notes/{guid}`, `PUT /v1/external/notes/{guid}/share-link`, `DELETE .../share-link`                                                                                   | `note:write`                  |
| `GET /v1/external/organizations/me/notes`                                                                                                                                                | `note:read`                   |
| `GET /v1/external/workspaces/{ws}/notes`, `POST .../workspaces/{ws}/notes/search`                                                                                                        | `note:read`                   |
| `GET /v1/external/notes/{guid}/documents`, `.../documents/{id}`                                                                                                                          | `note_document:read`          |
| `POST /v1/external/notes/{guid}/documents`                                                                                                                                               | `note_document:write`         |
| `GET /v1/external/note-document-templates`, `.../managed`, `.../{id}`, `GET /v1/external/{users/me,workspaces/me,workspaces/{ws},workspaces/-,organizations/me}/note-document-templates` | `note_document_template:read` |
| `GET .../folders`（フォルダの取得）                                                                                                                                                               | `folder:read`                 |
| `POST/PATCH/PUT/DELETE .../folders`（フォルダの変更）                                                                                                                                             | `folder:write`                |
| `GET /v1/external/workspaces/{ws}/wiki/**`                                                                                                                                               | `wiki:read`                   |
| `GET .../word-memories`, `.../word-memories/{id}`（users・workspaces・organizations）                                                                                                        | `word_memory:read`            |
| `POST/PATCH/DELETE .../word-memories`, `POST .../word-memories/bulk`, `PATCH .../word-memories/bulk`, `POST .../word-memories/bulk-delete`                                               | `word_memory:write`           |
| `GET /v1/external/organizations/me/workspaces`, `.../workspaces/{ws}`                                                                                                                    | `workspace:read`              |
| `POST /v1/external/organizations/me/workspaces`, `PATCH .../{ws}`, `PATCH .../{ws}/quota`, `DELETE .../{ws}`                                                                             | `workspace:write`             |
| `GET /v1/external/voice-file/jobs/{jobId}`, `GET .../transcript`, `GET .../translations/**`, `GET .../paragraph-summary`                                                                 | `voice_file_job:read`         |
| `POST /v1/external/voice-file/jobs`, `PUT .../{jobId}/upload-complete`, `DELETE .../{jobId}`                                                                                             | `voice_file_job:write`        |
| `GET /v1/external/auth/me`, `.../organizations/me`, `.../workspaces`, `.../workspaces/me`                                                                                                | （スコープ不問 — すべてのキー）             |

## API keyを取得する

API keyは[Tiro Platformダッシュボード](https://platform.tiro.ooo/dashboard/api-keys)から取得できます。

<Steps>
  <Step title="Sign in">
    [platform.tiro.ooo/dashboard/api-keys](https://platform.tiro.ooo/dashboard/api-keys)にアクセスします。
  </Step>

  <Step title="Pick a workspace">
    サイドバーのワークスペース切り替えで、キーがアクセスするデータのワークスペースを選びます。キーはこのワークスペースのみに限定されます。
  </Step>

  <Step title="Create a key">
    **Create New API Key** をクリックし、名前を付けてから、ドットを含む**full key**をコピーします — `abc123.xR7mK9pL2qW4...`。
  </Step>

  <Step title="Store it">
    環境変数として保存します。secretは一度だけ表示され、ダイアログを閉じると復元できません。
  </Step>
</Steps>

<Warning>
  API keyは安全に保管し、クライアントサイドのコードに公開しないでください。API keyは
  サーバーサイドのアプリケーションでのみ使用してください。
</Warning>

## API key形式

Tiro APIのkeyは次の形式に従います。

```
{id}.{secret}
```

例: `abc123.xR7mK9pL2qW4...`

| Part                    | Example                  | Description                                             |
| ----------------------- | ------------------------ | ------------------------------------------------------- |
| **Key ID** (`{id}`)     | `abc123`                 | Platformダッシュボードに表示されます。どのkeyがリクエストを行っているかを識別するために使用します。 |
| **Secret** (`{secret}`) | `xR7mK9pL2qW4...`        | 作成時に一度だけ表示されます。サーバーはhashのみを保存するため、復元できません。              |
| **Full API Key**        | `abc123.xR7mK9pL2qW4...` | **ドットを含む文字列全体**です。これをBearer tokenとして使用します。              |

<Warning>
  **よくある間違い:** Key ID（`abc123`）だけをBearer tokenとして使用しないでください。**full key**（`abc123.xR7mK9pL2qW4...`）、つまりkey作成時に表示された完全な文字列を使用する必要があります。
</Warning>

## 認証付きリクエストを行う

すべてのリクエストの`Authorization`ヘッダーにAPI keyを含めます。

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -H "Authorization: Bearer $TIRO_API_KEY" \
       -H "Content-Type: application/json" \
       https://api.tiro.ooo/v1/external/notes
  ```

  ```javascript Node.js theme={"system"}
  const response = await fetch("https://api.tiro.ooo/v1/external/notes", {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${process.env.TIRO_API_KEY}`,
      "Content-Type": "application/json",
    },
  });

  const notes = await response.json();
  ```

  ```python Python theme={"system"}
  import os
  import requests

  headers = {
      'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}',
      'Content-Type': 'application/json'
  }

  response = requests.get('https://api.tiro.ooo/v1/external/notes', headers=headers)
  notes = response.json()
  ```

  ```go Go theme={"system"}
  import (
      "fmt"
      "net/http"
      "os"
  )

  func makeAuthenticatedRequest() (*http.Response, error) {
      client := &http.Client{}
      req, err := http.NewRequest("GET", "https://api.tiro.ooo/v1/external/notes", nil)
      if err != nil {
          return nil, err
      }
      
      apiKey := os.Getenv("TIRO_API_KEY")
      req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
      req.Header.Set("Content-Type", "application/json")
      
      return client.Do(req)
  }
  ```

  ```kotlin Kotlin + Spring theme={"system"}
  @Service
  class TiroApiService {
      
      @Value("\${tiro.api.key}")
      private lateinit var apiKey: String
      
      private val restTemplate = RestTemplate()
      
      fun getNotes(): ResponseEntity<String> {
          val headers = HttpHeaders()
          headers.set("Authorization", "Bearer $apiKey")
          headers.contentType = MediaType.APPLICATION_JSON
          
          val entity = HttpEntity<String>(headers)
          
          return restTemplate.exchange(
              "https://api.tiro.ooo/v1/external/notes",
              HttpMethod.GET,
              entity,
              String::class.java
          )
      }
  }
  ```
</CodeGroup>

## 認証エラー

認証に失敗すると、`401 Unauthorized`レスポンスが返されます。主な原因は次のとおりです。

* Authorizationヘッダーがない
* keyの形式が不正（`{id}.{secret}`である必要があります）
* 不明なkey id
* 無効、期限切れ、または削除されたkey

```json theme={"system"}
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key provided is invalid",
    "type": "authentication_error"
  }
}
```

## レガシー個人キー(非推奨)

ワークスペース導入より前は、**個人**API キーはワークスペースではなくアカウントに紐づいていました。これらの個人キーは非推奨です。**チーム**キーは現在ワークスペースキーとして渡されます — 各チームが 1 つのワークスペースに対応します。ワークスペースキーが個人キーとチームキーの両方を置き換えます。

<Warning>
  レガシー個人キーは**2026年6月30日**に動作を停止します。それ以降、レガシー個人キーで送ったリクエストは`401 Unauthorized`を返します。ダウンタイムを避けるため、その前に移行してください。
</Warning>

|           | レガシー個人キー            | ワークスペースキー                                     |
| --------- | ------------------- | --------------------------------------------- |
| **範囲**    | アカウント全体             | ワークスペース 1 つ                                   |
| **新規発行**  | 無効                  | ダッシュボード → ワークスペースを選択 → **Create New API Key** |
| **既存のキー** | 2026年6月30日まで表示と失効のみ | フルライフサイクル                                     |
| **形式**    | `{id}.{secret}`     | `{id}.{secret}` — 変更なし                        |

形式は同一なので、移行は 1 行の差し替えで済みます — コードの書き換えは不要です。

### 3 ステップで移行する

<Steps>
  <Step title="Create a workspace key">
    [ダッシュボード](https://platform.tiro.ooo/dashboard/api-keys)で、連携が使用するノートを含むワークスペースを選択し、キーを作成します。
  </Step>

  <Step title="Swap the secret">
    `TIRO_API_KEY`環境変数の値を新しいキーに置き換えます。ほかのコード変更は必要ありません。
  </Step>

  <Step title="Revoke the legacy key">
    新しいキーでトラフィックが流れることを確認したら、ダッシュボードの**Legacy personal keys**セクションからレガシーキーを削除します。
  </Step>
</Steps>

<Note>
  レガシーキーはアカウント上のすべてのノートに到達しましたが、ワークスペースキーは 1 つのワークスペースにのみ到達します。データが複数のワークスペースにまたがる場合は、ワークスペースごとにキーを 1 つずつ作成してください。
</Note>

## セキュリティのベストプラクティス

### 環境変数

環境変数を使用して、API keyを安全に保管します。

<CodeGroup>
  ```bash .env theme={"system"}
  # .env file (never commit this!)
  TIRO_API_KEY=abc123.XYZ...
  ```

  ```javascript Node.js theme={"system"}
  // Load from environment
  const apiKey = process.env.TIRO_API_KEY;
  if (!apiKey) {
    throw new Error('TIRO_API_KEY environment variable is required');
  }
  ```

  ```python Python theme={"system"}
  import os

  # Load from environment with validation
  api_key = os.getenv('TIRO_API_KEY')
  if not api_key:
      raise ValueError('TIRO_API_KEY environment variable is required')
  ```

  ```go Go theme={"system"}
  import (
      "fmt"
      "os"
  )

  func getAPIKey() (string, error) {
      apiKey := os.Getenv("TIRO_API_KEY")
      if apiKey == "" {
          return "", fmt.Errorf("TIRO_API_KEY environment variable is required")
      }
      return apiKey, nil
  }
  ```

  ```kotlin Kotlin + Spring theme={"system"}
  # application.properties
  tiro.api.key=${TIRO_API_KEY}

  # Or application.yml
  tiro:
    api:
      key: ${TIRO_API_KEY}
  ```
</CodeGroup>

### その他のセキュリティガイドライン

* **keyを定期的にローテーションする**: 使用していないkeyを削除し、新しいkeyを生成します
* **環境ごとにkeyを分ける**: 開発環境と本番環境で異なるkeyを使用します
* **利用状況を監視する**: API keyの利用状況を追跡し、異常があればローテーションします
* **API keyを絶対にログに記録しない**: keyがアプリケーションのログに表示されないようにします
* **HTTPSのみを使用する**: 常に安全な接続でリクエストを行います
