# クライアント

> createClient で Nodaro SDK のクライアントを作成し、ベース URL、認証、タイムアウト、ワークスペースを設定して、クライアントが公開するすべてのリソースを確認します。

Source: https://nodaro.ai/ja/docs/developers/sdk/client

**クライアント**は、`createClient()` が返すオブジェクトです。`NodaroClient` は、あなたのベース URL、認証プロバイダー、設定を保持し、`client.workflows` や `client.nodes` のように、Nodaro API のあらゆる部分をリソースとして公開します。クライアントは 1 回作成し、すべての呼び出しで再利用します。

## クライアントを作成する
```ts

const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
timeoutMs: 120_000,
})
```

```ts
createClient(options: ClientOptions): NodaroClient
```

<TypeTable
type={{
baseUrl: {
type: 'string',
required: true,
description: "Nodaro のサーバーです。たとえば https://app.nodaro.ai や、セルフホスティング環境のアドレスです。ブラウザーアプリで同一オリジンのリクエストを送る場合は、空の文字列を使います。末尾のスラッシュは取り除かれます。",
},
auth: {
type: 'Auth',
required: true,
description: "認証プロバイダーです。new StaticTokenAuth(token)、supabaseAuth(supabase)、new CallbackAuth(fn)、または getToken() メソッドを持つ任意のオブジェクトです。",
typeDescriptionLink: '/docs/developers/sdk/auth',
},
fetch: {
type: 'typeof fetch',
default: 'globalThis.fetch',
description: "テスト、リトライ、トレーシング用の、独自の fetch 関数です。",
},
timeoutMs: {
type: 'number',
default: '60000',
description: "各リクエストのタイムアウトで、ミリ秒単位です。時間を超えると、リクエストは中断されます。",
},
workspaceId: {
type: 'string',
description: "すべてのリクエストが動作するワークスペースで、X-Nodaro-Workspace ヘッダーとして送信されます。省略すると、個人スペースで動作します。Nodaro Cloud の組織限定です。",
},
clientLabel: {
type: 'string',
default: "'sdk/<version>'",
description: "X-Nodaro-Client ヘッダーの値です。Nodaro は、これを各ジョブの発信元として記録します。SDK の上にほかのツールを構築する場合にだけ設定してください。",
},
}}
/>

`NodaroClient` はクラスとしてもエクスポートされているため、クライアントを受け取る関数の型を書けます。

```ts

async function countWorkflows(client: NodaroClient, projectId: string) {
const { data } = await client.workflows.list({ projectId })
return data.length
}
```

## クライアントのリソース
各リソースは `createClient` によって作成され、`client.<resource>` としてアクセスします。

| リソース | 内容 | リファレンス |
| --- | --- | --- |
| `client.workflows` | ワークフロー：作成、更新、共有、エクスポート、インポート、実行 | [ワークフローとプロジェクト](https://nodaro.ai/docs/developers/sdk/workflows) |
| `client.projects` | ワークフローを入れるプロジェクト | [ワークフローとプロジェクト](https://nodaro.ai/docs/developers/sdk/workflows) |
| `client.executions` | ワークフロー全体の実行 | [ジョブと実行](https://nodaro.ai/docs/developers/sdk/jobs-and-executions) |
| `client.jobs` | 単体の生成ジョブ | [ジョブと実行](https://nodaro.ai/docs/developers/sdk/jobs-and-executions) |
| `client.videoPro` | **動画生成 Pro**（Generate Video Pro）の実行を停止または継続 | [ジョブと実行](https://nodaro.ai/docs/developers/sdk/jobs-and-executions) |
| `client.nodes` | ノードカタログと単体のノード実行 | [ノードを実行する](https://nodaro.ai/docs/developers/sdk/nodes) |
| `client.apps` | 公開されたアプリとその実行 | [アプリとテンプレート](https://nodaro.ai/docs/developers/sdk/apps-and-templates) |
| `client.templates` | テンプレートマーケットプレイス | [アプリとテンプレート](https://nodaro.ai/docs/developers/sdk/apps-and-templates) |
| `client.tutorials` | チュートリアル動画とチュートリアルワークフロー | [アプリとテンプレート](https://nodaro.ai/docs/developers/sdk/apps-and-templates) |
| `client.llm` | 言語モデルからの構造化出力 | [LLM と Reduce](https://nodaro.ai/docs/developers/sdk/llm-and-reduce) |
| `client.reduce` | 複数の結果から最良のものを選ぶか、組み合わせる | [LLM と Reduce](https://nodaro.ai/docs/developers/sdk/llm-and-reduce) |
| `client.uploads` | ファイルのアップロード | [メディアとアップロード](https://nodaro.ai/docs/developers/sdk/media-and-uploads) |
| `client.library` | 保存済みのメディア | [メディアとアップロード](https://nodaro.ai/docs/developers/sdk/media-and-uploads) |
| `client.media` | メディアのダウンロード、トリミング、キャプション、オーバーレイ、コラージュ | [メディアとアップロード](https://nodaro.ai/docs/developers/sdk/media-and-uploads) |
| `client.voices` | ボイス、ボイスチェンジャー、ボイスデザイン、吹き替え | [ボイスと音声](https://nodaro.ai/docs/developers/sdk/voices-and-audio) |
| `client.audio` | 音声の分離、抽出、ミックス、トリミング、文字起こし | [ボイスと音声](https://nodaro.ai/docs/developers/sdk/voices-and-audio) |
| `client.edit` | 無音検出、オーディオ同期、編集プラン、EDL レンダリング | [編集](https://nodaro.ai/docs/developers/sdk/editing) |
| `client.scene3d` | 編集可能な 3D シーンと **3D レンダリング Pro**（3D Render Pro） | [3D シーン](https://nodaro.ai/docs/developers/sdk/scenes-3d) |
| `client.characters` | キャラクター | [キャラクター](https://nodaro.ai/docs/developers/sdk/characters) |
| `client.locations` | ロケーション | [ロケーション](https://nodaro.ai/docs/developers/sdk/locations) |
| `client.objects` | オブジェクトと小道具 | [オブジェクトとクリーチャー](https://nodaro.ai/docs/developers/sdk/objects-and-creatures) |
| `client.creatures` | 動物とクリーチャー | [オブジェクトとクリーチャー](https://nodaro.ai/docs/developers/sdk/objects-and-creatures) |
| `client.community` | 共有されたコミュニティのアセットライブラリ | [コミュニティライブラリ](https://nodaro.ai/docs/developers/sdk/community) |
| `client.studio` | スタジオプロダクション | [スタジオプロダクション](https://nodaro.ai/docs/developers/sdk/studio) |
| `client.shots` | 共有リンクの背後にある共有ショットレコード | [スタジオプロダクション](https://nodaro.ai/docs/developers/sdk/studio) |
| `client.recast` | Recast の実行と、自分で書いたスクリプト | [Recast](https://nodaro.ai/docs/developers/sdk/recast) |
| `client.pipelines` | ストーリーから動画へのパイプライン | [パイプライン](https://nodaro.ai/docs/developers/sdk/pipelines) |
| `client.copilot` | Nodaro アプリ内限定の Copilot スレッド | [Copilot](https://nodaro.ai/docs/developers/sdk/copilot) |
| `client.models` | モデルカタログ | [モデルとクレジット](https://nodaro.ai/docs/developers/sdk/models-and-credits) |
| `client.credits` | 残高とモデルの価格 | [モデルとクレジット](https://nodaro.ai/docs/developers/sdk/models-and-credits) |
| `client.pickerCatalogs` | 各ピッカーの有効なオプション | [ピッカー、プリセット、プロンプト](https://nodaro.ai/docs/developers/sdk/pickers-and-prompts) |
| `client.catalogs` | すべてのピッカーカタログを 1 回の呼び出しで | [ピッカー、プリセット、プロンプト](https://nodaro.ai/docs/developers/sdk/pickers-and-prompts) |
| `client.presets` | 保存済みのノードプリセットと標準プリセット | [ピッカー、プリセット、プロンプト](https://nodaro.ai/docs/developers/sdk/pickers-and-prompts) |
| `client.promptHelper` | プロンプトウィザード | [ピッカー、プリセット、プロンプト](https://nodaro.ai/docs/developers/sdk/pickers-and-prompts) |
| `client.organizations` | 組織、メンバー、招待 | [組織とワークスペース](https://nodaro.ai/docs/developers/sdk/organizations) |
| `client.workspaces` | ワークスペース、メンバー、参加コード | [組織とワークスペース](https://nodaro.ai/docs/developers/sdk/organizations) |
| `client.developerApps` | あなたが所有する OAuth アプリ | [OAuth と開発者アプリ](https://nodaro.ai/docs/developers/sdk/developer-apps) |
| `client.oauth` | コード交換、トークンの取り消し、同意画面用のデータ | [OAuth と開発者アプリ](https://nodaro.ai/docs/developers/sdk/developer-apps) |

クライアント自体には、さらに 3 つのメソッドがあります。[`me()`](#me)、[`withWorkspace()`](#withworkspaceworkspaceid)、[`request()`](#requestmethod-path-options) です。

## メソッドが返すもの
- **エンベロープはそのまま保たれます。**エンドポイントが `{ "data": ... }` で応答する場合、メソッドはそのエンベロープをそのまま返すため、`const { data } = await client.workflows.get(id)` のように書きます。ページ分割された一覧では、`data` の横に `nextCursor` のようなカーソルが加わります。
- **一部のリソースは、内容をそのまま返します。**いくつかのメソッドは、レスポンスを展開して返します。たとえば `client.characters.list()` は `{ characters, nextCursor }` を返し、`client.credits.balance()` は残高そのものを返します。正確な戻り値の型は、各リファレンスページに示されています。
- **削除とキャンセル**は、通常 `{ success: true }` を返します。
- **フィールド名は、通信時の形式に従います。**`Job` は、API がその形で送るため、`output_data` や `created_at` のような snake_case のフィールドを使います。`Workflow` と `WorkflowExecution` は camelCase を使います。

すべてのレスポンスと入力の型はエクスポートされているため、`import type` でインポートできます。[型](https://nodaro.ai/docs/developers/sdk/types)を参照してください。

## me()
```ts
me(): Promise<UserIdentity & MeOrganizations>
```

現在のトークンの持ち主の情報を返します（`GET /v1/me`）。有効なトークンであれば、API トークン、OAuth アクセストークン、ブラウザーセッションのいずれであっても、その所有者を返します。トークンがない場合や無効な場合は、`UnauthorizedError` をスローします。

```ts
const me = await client.me()
console.log(me.email, me.tier)
```

| フィールド | 型 | 説明 |
| --- | --- | --- |
| `id` | `string` | Nodaro のユーザー ID です。 |
| `email` | `string` | ユーザーのメールアドレスです。 |
| `displayName` | `string \| null` | 表示名です。設定されていない場合は `null` です。 |
| `avatarUrl` | `string \| null` | アバターの URL です。設定されていない場合は `null` です。 |
| `tier` | `string` | 保存されているサブスクリプションのティアで、`"free"` や `"pro"` などです。実際に適用されるティア（従量課金を含む）については、[`client.credits.balance()`](https://nodaro.ai/docs/developers/sdk/models-and-credits) の `effectiveTier` を読み取ってください。 |
| `isAdmin` | `boolean` | ユーザーが管理者かどうかです。表示内容を決めるためだけに使ってください。権限のチェックは、サーバー自身がすべて行います。 |

組織機能のある Nodaro Cloud インスタンスでは、結果に `organizations`、`workspaces`、`lastWorkspaceId`、`organizationsUnavailable` も含まれます。この 3 つの状態は、それぞれ異なる扱いが必要です。

| 見えるもの | 意味 | 対応 |
| --- | --- | --- |
| フィールドがない | インスタンスに組織機能がありません | ワークスペース切り替えを表示しないでください。 |
| フィールドはあるが空 | アカウントがどの組織にも属していません | 組織の作成または参加を提案してください。 |
| `organizationsUnavailable: true` | 参照に失敗しました | それまでの選択をそのまま保ってください。アクセスを失ったとユーザーに伝えないでください。 |

## withWorkspace(workspaceId)
```ts
withWorkspace(workspaceId: string | null): NodaroClient
```

`workspaceId` で動作する**新しい**クライアントを返します。新しいクライアントは、元のクライアントの認証、ベース URL、タイムアウト、fetch を共有します。個人スペースを使うには `null` を渡します。

```ts
const classroom = client.withWorkspace(workspaceId)

await classroom.workflows.run(workflowId) // runs in the workspace
await client.workflows.run(workflowId)    // runs in the personal space
```

このメソッドは、現在のクライアントを変更するのではなく、新しいクライアントを返します。そのため、1 つのクライアントで同時に実行される 2 つの操作が、ワークスペースを取り違えることはありません。

ワークスペースが決めるのは**スコープ**であって、**アクセス権**ではありません。一覧がどのワークスペースから読み取るか、新しいアイテムがどこに作られるかを決めます。ID で指定したアイテムの読み取り、変更、削除、実行は、そのアイテム自身のワークスペースによって決まります。ワークスペースの指定を忘れても自分の作業が見えなくなることはなく、間違ったワークスペースを指定しても、ほかの人の作業にアクセスすることはできません。

ワークスペースは、Nodaro Cloud の組織に属します。[組織とワークスペース](https://nodaro.ai/docs/developers/sdk/organizations)と[ワークスペース](https://nodaro.ai/docs/concepts/workspaces)を参照してください。

## request(method, path, options)
```ts
request<T>(method: string, path: string, options?: {
body?: unknown
query?: Record<string, string | number | boolean | undefined>
headers?: Record<string, string>
signal?: AbortSignal
}): Promise<T>
```

任意のエンドポイントにリクエストを送ります。まだリソースメソッドがない、ごく一部のエンドポイント向けです。あなたの認証ヘッダーとワークスペースを追加し、`body` を JSON として送信し、`timeoutMs` を適用して、リソースメソッドと同じ[型付きエラー](https://nodaro.ai/docs/developers/sdk/errors)をスローします。

```ts
// The same request that client.jobs.list() sends
const page = await client.request<{ data: unknown[]; next: string | null }>("GET", "/v1/jobs", {
query: { type: "llm-structured", limit: 20 },
})
```

`FormData` のボディは、マルチパートアップロードとして送信されます。`undefined` のクエリ値は除外されます。すべてのエンドポイントとそのフィールドは、[REST API リファレンス](https://nodaro.ai/docs/developers/api)に一覧があります。

## タイムアウトと独自の fetch
`timeoutMs` は、上限（デフォルトは 60 秒）を超えたリクエストを中断します。ほとんどの生成は、妥当な HTTP タイムアウトよりも長くかかるため、生成を開始してジョブをポーリングしてください。[`client.nodes.runAndWait()`](https://nodaro.ai/docs/developers/sdk/nodes) は、その両方を代わりに行います。ストリーミングを行うメソッドである `client.copilot.stream()` と `client.media.downloadVideoProgress()` は、数分間開いたままになることを想定しているため、タイムアウトを適用しません。

独自の `fetch` を渡すと、リクエストの送り方を変えられます。

- **テスト。**モックから、用意した `Response` オブジェクトを返します。
- **リトライ。**グローバルな `fetch` を、5xx の応答でリトライするヘルパーでラップします。
- **トレーシング。**トレーシングやモニタリングのライブラリでラップします。

```ts
const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
fetch: (input, init) => tracedFetch(input, init),
})
```

## ランタイムとブラウザー
SDK が使うのは `fetch` と `URL` だけです。これらは、Node.js 20 以降、最新のブラウザー、React Native、Cloudflare Workers、Deno、Bun でグローバルに使えます。ポリフィルは必要ありません。

- **OAuth トークンでの CORS。**OAuth アクセストークンで Nodaro を呼び出すブラウザーアプリは、開発者アプリの `allowedOrigins` に登録されたオリジンで動作する必要があります。[OAuth と開発者アプリ](https://nodaro.ai/docs/developers/sdk/developer-apps)を参照してください。
- **セッションでの CORS。**`supabaseAuth` を使うブラウザーアプリは、そのリストと照合されません。
- **クライアントラベル。**サーバーでは、SDK は `X-Nodaro-Client: sdk/<version>` を送信し、Nodaro はこれを各ジョブの発信元として記録します。ブラウザーでは、ブラウザーの `Origin` ヘッダーが既にあなたのアプリを示しているため、デフォルトのラベルは送信されません。自分で設定した `clientLabel` は、常に送信されます。

## Frequently asked questions

### Nodaro SDK では、どのベース URL を使えばよいですか？

Nodaro Cloud では https://app.nodaro.ai を使い、セルフホスティング環境では、そのインスタンスのアドレスを使います。Nodaro と同一オリジンで配信されるブラウザーアプリでは、空の文字列を使います。

### デフォルトのリクエストタイムアウトはどれくらいですか？

60 秒です。timeoutMs オプションで変更できます。生成はどのリクエストよりも長くかかるため、タイムアウトを延ばすのではなく、生成を開始してジョブをポーリングしてください。

### ワークスペースでリクエストを送るにはどうすればよいですか？

client.withWorkspace(workspaceId) を呼び出します。そのワークスペースであらゆるリクエストを送る新しいクライアントが返され、元のクライアントは、引き続き個人スペースで動作します。

### SDK にメソッドがないエンドポイントを呼び出すには、どうすればよいですか？

client.request(method, path, options) を使います。ほかのリソースメソッドと同じ認証ヘッダーを送り、同じタイムアウトを適用し、同じ型付きエラーをスローします。
