mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-19 02:34:37 -05:00
610 lines
20 KiB
Plaintext
610 lines
20 KiB
Plaintext
---
|
||
title: 外部API
|
||
---
|
||
|
||
import { Callout } from 'fumadocs-ui/components/callout'
|
||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||
import { Video } from '@/components/ui/video'
|
||
|
||
Simは、ワークフローの実行ログを照会したり、ワークフローが完了したときにリアルタイム通知を設定するためのWebhookを設定したりするための包括的な外部APIを提供しています。
|
||
|
||
## 認証
|
||
|
||
すべてのAPIリクエストには、`x-api-key`ヘッダーで渡されるAPIキーが必要です:
|
||
|
||
```bash
|
||
curl -H "x-api-key: YOUR_API_KEY" \
|
||
https://sim.ai/api/v1/logs?workspaceId=YOUR_WORKSPACE_ID
|
||
```
|
||
|
||
SimダッシュボードのユーザーセッティングからAPIキーを生成できます。
|
||
|
||
## ログAPI
|
||
|
||
すべてのAPIレスポンスには、ワークフロー実行の制限と使用状況に関する情報が含まれています:
|
||
|
||
```json
|
||
"limits": {
|
||
"workflowExecutionRateLimit": {
|
||
"sync": {
|
||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||
"maxBurst": 120, // Maximum burst capacity
|
||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||
"resetAt": "..." // When tokens next refill
|
||
},
|
||
"async": {
|
||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||
"maxBurst": 400, // Maximum burst capacity
|
||
"remaining": 398, // Current tokens available
|
||
"resetAt": "..." // When tokens next refill
|
||
}
|
||
},
|
||
"usage": {
|
||
"currentPeriodCost": 1.234, // Current billing period usage in USD
|
||
"limit": 10, // Usage limit in USD
|
||
"plan": "pro", // Current subscription plan
|
||
"isExceeded": false // Whether limit is exceeded
|
||
}
|
||
}
|
||
```
|
||
|
||
**注意:** レート制限はトークンバケットアルゴリズムを使用しています。最近の割り当てを完全に使用していない場合、`remaining`は`requestsPerMinute`を超えて`maxBurst`まで達することができ、バーストトラフィックを許可します。レスポンスボディのレート制限はワークフロー実行のためのものです。このAPIエンドポイントを呼び出すためのレート制限はレスポンスヘッダー(`X-RateLimit-*`)にあります。
|
||
|
||
### ログの照会
|
||
|
||
広範なフィルタリングオプションでワークフロー実行ログを照会します。
|
||
|
||
<Tabs items={['Request', 'Response']}>
|
||
<Tab value="Request">
|
||
|
||
```http
|
||
GET /api/v1/logs
|
||
```
|
||
|
||
**必須パラメータ:**
|
||
- `workspaceId` - ワークスペースID
|
||
|
||
**オプションフィルター:**
|
||
- `workflowIds` - カンマ区切りのワークフローID
|
||
- `folderIds` - カンマ区切りのフォルダID
|
||
- `triggers` - カンマ区切りのトリガータイプ: `api`, `webhook`, `schedule`, `manual`, `chat`
|
||
- `level` - レベルでフィルタリング: `info`, `error`
|
||
- `startDate` - 日付範囲開始のISOタイムスタンプ
|
||
- `endDate` - 日付範囲終了のISOタイムスタンプ
|
||
- `executionId` - 正確な実行ID一致
|
||
- `minDurationMs` - 最小実行時間(ミリ秒)
|
||
- `maxDurationMs` - 最大実行時間(ミリ秒)
|
||
- `minCost` - 最小実行コスト
|
||
- `maxCost` - 最大実行コスト
|
||
- `model` - 使用されたAIモデルでフィルタリング
|
||
|
||
**ページネーション:**
|
||
- `limit` - ページあたりの結果数(デフォルト:100)
|
||
- `cursor` - 次ページのカーソル
|
||
- `order` - ソート順:`desc`, `asc` (デフォルト:desc)
|
||
|
||
**詳細レベル:**
|
||
- `details` - レスポンス詳細レベル: `basic`, `full` (デフォルト: basic)
|
||
- `includeTraceSpans` - トレーススパンを含める (デフォルト: false)
|
||
- `includeFinalOutput` - 最終出力を含める (デフォルト: false)
|
||
</Tab>
|
||
<Tab value="Response">
|
||
|
||
```json
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "log_abc123",
|
||
"workflowId": "wf_xyz789",
|
||
"executionId": "exec_def456",
|
||
"level": "info",
|
||
"trigger": "api",
|
||
"startedAt": "2025-01-01T12:34:56.789Z",
|
||
"endedAt": "2025-01-01T12:34:57.123Z",
|
||
"totalDurationMs": 334,
|
||
"cost": {
|
||
"total": 0.00234
|
||
},
|
||
"files": null
|
||
}
|
||
],
|
||
"nextCursor": "eyJzIjoiMjAyNS0wMS0wMVQxMjozNDo1Ni43ODlaIiwiaWQiOiJsb2dfYWJjMTIzIn0",
|
||
"limits": {
|
||
"workflowExecutionRateLimit": {
|
||
"sync": {
|
||
"requestsPerMinute": 60,
|
||
"maxBurst": 120,
|
||
"remaining": 118,
|
||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||
},
|
||
"async": {
|
||
"requestsPerMinute": 200,
|
||
"maxBurst": 400,
|
||
"remaining": 398,
|
||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||
}
|
||
},
|
||
"usage": {
|
||
"currentPeriodCost": 1.234,
|
||
"limit": 10,
|
||
"plan": "pro",
|
||
"isExceeded": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
### ログ詳細の取得
|
||
|
||
特定のログエントリに関する詳細情報を取得します。
|
||
|
||
<Tabs items={['Request', 'Response']}>
|
||
<Tab value="Request">
|
||
|
||
```http
|
||
GET /api/v1/logs/{id}
|
||
```
|
||
|
||
</Tab>
|
||
<Tab value="Response">
|
||
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": "log_abc123",
|
||
"workflowId": "wf_xyz789",
|
||
"executionId": "exec_def456",
|
||
"level": "info",
|
||
"trigger": "api",
|
||
"startedAt": "2025-01-01T12:34:56.789Z",
|
||
"endedAt": "2025-01-01T12:34:57.123Z",
|
||
"totalDurationMs": 334,
|
||
"workflow": {
|
||
"id": "wf_xyz789",
|
||
"name": "My Workflow",
|
||
"description": "Process customer data"
|
||
},
|
||
"executionData": {
|
||
"traceSpans": [...],
|
||
"finalOutput": {...}
|
||
},
|
||
"cost": {
|
||
"total": 0.00234,
|
||
"tokens": {
|
||
"prompt": 123,
|
||
"completion": 456,
|
||
"total": 579
|
||
},
|
||
"models": {
|
||
"gpt-4o": {
|
||
"input": 0.001,
|
||
"output": 0.00134,
|
||
"total": 0.00234,
|
||
"tokens": {
|
||
"prompt": 123,
|
||
"completion": 456,
|
||
"total": 579
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"limits": {
|
||
"workflowExecutionRateLimit": {
|
||
"sync": {
|
||
"requestsPerMinute": 60,
|
||
"maxBurst": 120,
|
||
"remaining": 118,
|
||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||
},
|
||
"async": {
|
||
"requestsPerMinute": 200,
|
||
"maxBurst": 400,
|
||
"remaining": 398,
|
||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||
}
|
||
},
|
||
"usage": {
|
||
"currentPeriodCost": 1.234,
|
||
"limit": 10,
|
||
"plan": "pro",
|
||
"isExceeded": false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
### 実行詳細の取得
|
||
|
||
ワークフロー状態のスナップショットを含む実行詳細を取得します。
|
||
|
||
<Tabs items={['Request', 'Response']}>
|
||
<Tab value="Request">
|
||
|
||
```http
|
||
GET /api/v1/logs/executions/{executionId}
|
||
```
|
||
|
||
</Tab>
|
||
<Tab value="Response">
|
||
|
||
```json
|
||
{
|
||
"executionId": "exec_def456",
|
||
"workflowId": "wf_xyz789",
|
||
"workflowState": {
|
||
"blocks": {...},
|
||
"edges": [...],
|
||
"loops": {...},
|
||
"parallels": {...}
|
||
},
|
||
"executionMetadata": {
|
||
"trigger": "api",
|
||
"startedAt": "2025-01-01T12:34:56.789Z",
|
||
"endedAt": "2025-01-01T12:34:57.123Z",
|
||
"totalDurationMs": 334,
|
||
"cost": {...}
|
||
}
|
||
}
|
||
```
|
||
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
## 通知
|
||
|
||
ワークフローの実行が完了したときに、Webhook、メール、またはSlackを通じてリアルタイム通知を受け取ることができます。通知はログページからワークスペースレベルで設定されます。
|
||
|
||
### 設定
|
||
|
||
ログページからメニューボタンをクリックし、「通知を設定する」を選択して通知を設定します。
|
||
|
||
**通知チャネル:**
|
||
- **Webhook**: エンドポイントにHTTP POSTリクエストを送信
|
||
- **メール**: 実行詳細を含むメール通知を受信
|
||
- **Slack**: Slackチャンネルにメッセージを投稿
|
||
|
||
**ワークフロー選択:**
|
||
- 監視する特定のワークフローを選択
|
||
- または「すべてのワークフロー」を選択して現在および将来のワークフローを含める
|
||
|
||
**フィルタリングオプション:**
|
||
- `levelFilter`: 受信するログレベル (`info`, `error`)
|
||
- `triggerFilter`: 受信するトリガータイプ (`api`, `webhook`, `schedule`, `manual`, `chat`)
|
||
|
||
**オプションデータ:**
|
||
- `includeFinalOutput`: ワークフローの最終出力を含める
|
||
- `includeTraceSpans`: 詳細な実行トレーススパンを含める
|
||
- `includeRateLimits`: レート制限情報(同期/非同期の制限と残り)を含める
|
||
- `includeUsageData`: 請求期間の使用状況と制限を含める
|
||
|
||
### アラートルール
|
||
|
||
すべての実行について通知を受け取る代わりに、問題が検出された場合にのみ通知されるようにアラートルールを設定できます:
|
||
|
||
**連続失敗**
|
||
- X回連続して実行が失敗した後にアラート(例:3回連続の失敗)
|
||
- 実行が成功すると、リセットされます
|
||
|
||
**失敗率**
|
||
- 過去Y時間の失敗率がX%を超えた場合にアラート
|
||
- ウィンドウ内で最低5回の実行が必要
|
||
- 完全な時間ウィンドウが経過した後にのみトリガーされます
|
||
|
||
**レイテンシーしきい値**
|
||
- 実行がX秒以上かかった場合にアラート
|
||
- 遅いまたは停止しているワークフローを検出するのに役立ちます
|
||
|
||
**レイテンシースパイク**
|
||
- 実行が平均よりX%遅い場合にアラート
|
||
- 設定された時間ウィンドウでの平均所要時間と比較
|
||
- ベースラインを確立するために最低5回の実行が必要
|
||
|
||
**コスト閾値**
|
||
- 単一の実行コストが$Xを超えた場合にアラート
|
||
- 高価なLLM呼び出しを検出するのに役立つ
|
||
|
||
**アクティビティなし**
|
||
- X時間以内に実行がない場合にアラート
|
||
- 定期的に実行されるべきスケジュールされたワークフローの監視に役立つ
|
||
|
||
**エラー数**
|
||
- 時間枠内でエラー数がXを超えた場合にアラート
|
||
- 連続ではなく、総エラー数を追跡
|
||
|
||
すべてのアラートタイプには、通知スパムを防ぐための1時間のクールダウンが含まれています。
|
||
|
||
### Webhook設定
|
||
|
||
Webhookの場合、追加オプションが利用可能です:
|
||
- `url`:WebhookエンドポイントURL
|
||
- `secret`:HMAC署名検証用のオプションシークレット
|
||
|
||
### ペイロード構造
|
||
|
||
ワークフロー実行が完了すると、Simは以下のペイロードを送信します(webhook POST、メール、またはSlackを介して):
|
||
|
||
```json
|
||
{
|
||
"id": "evt_123",
|
||
"type": "workflow.execution.completed",
|
||
"timestamp": 1735925767890,
|
||
"data": {
|
||
"workflowId": "wf_xyz789",
|
||
"executionId": "exec_def456",
|
||
"status": "success",
|
||
"level": "info",
|
||
"trigger": "api",
|
||
"startedAt": "2025-01-01T12:34:56.789Z",
|
||
"endedAt": "2025-01-01T12:34:57.123Z",
|
||
"totalDurationMs": 334,
|
||
"cost": {
|
||
"total": 0.00234,
|
||
"tokens": {
|
||
"prompt": 123,
|
||
"completion": 456,
|
||
"total": 579
|
||
},
|
||
"models": {
|
||
"gpt-4o": {
|
||
"input": 0.001,
|
||
"output": 0.00134,
|
||
"total": 0.00234,
|
||
"tokens": {
|
||
"prompt": 123,
|
||
"completion": 456,
|
||
"total": 579
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"files": null,
|
||
"finalOutput": {...}, // Only if includeFinalOutput=true
|
||
"traceSpans": [...], // Only if includeTraceSpans=true
|
||
"rateLimits": {...}, // Only if includeRateLimits=true
|
||
"usage": {...} // Only if includeUsageData=true
|
||
},
|
||
"links": {
|
||
"log": "/v1/logs/log_abc123",
|
||
"execution": "/v1/logs/executions/exec_def456"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Webhookヘッダー
|
||
|
||
各Webhookリクエストには以下のヘッダーが含まれます(Webhookチャンネルのみ):
|
||
|
||
- `sim-event`:イベントタイプ(常に`workflow.execution.completed`)
|
||
- `sim-timestamp`:ミリ秒単位のUnixタイムスタンプ
|
||
- `sim-delivery-id`:べき等性のための一意の配信ID
|
||
- `sim-signature`:検証用のHMAC-SHA256署名(シークレットが設定されている場合)
|
||
- `Idempotency-Key`:重複検出のための配信IDと同じ
|
||
|
||
### 署名検証
|
||
|
||
Webhookシークレットを設定した場合、署名を検証してWebhookがSimからのものであることを確認します:
|
||
|
||
<Tabs items={['Node.js', 'Python']}>
|
||
<Tab value="Node.js">
|
||
|
||
```javascript
|
||
import crypto from 'crypto';
|
||
|
||
function verifyWebhookSignature(body, signature, secret) {
|
||
const [timestampPart, signaturePart] = signature.split(',');
|
||
const timestamp = timestampPart.replace('t=', '');
|
||
const expectedSignature = signaturePart.replace('v1=', '');
|
||
|
||
const signatureBase = `${timestamp}.${body}`;
|
||
const hmac = crypto.createHmac('sha256', secret);
|
||
hmac.update(signatureBase);
|
||
const computedSignature = hmac.digest('hex');
|
||
|
||
return computedSignature === expectedSignature;
|
||
}
|
||
|
||
// In your webhook handler
|
||
app.post('/webhook', (req, res) => {
|
||
const signature = req.headers['sim-signature'];
|
||
const body = JSON.stringify(req.body);
|
||
|
||
if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET)) {
|
||
return res.status(401).send('Invalid signature');
|
||
}
|
||
|
||
// Process the webhook...
|
||
});
|
||
```
|
||
|
||
</Tab>
|
||
<Tab value="Python">
|
||
|
||
```python
|
||
import hmac
|
||
import hashlib
|
||
import json
|
||
|
||
def verify_webhook_signature(body: str, signature: str, secret: str) -> bool:
|
||
timestamp_part, signature_part = signature.split(',')
|
||
timestamp = timestamp_part.replace('t=', '')
|
||
expected_signature = signature_part.replace('v1=', '')
|
||
|
||
signature_base = f"{timestamp}.{body}"
|
||
computed_signature = hmac.new(
|
||
secret.encode(),
|
||
signature_base.encode(),
|
||
hashlib.sha256
|
||
).hexdigest()
|
||
|
||
return hmac.compare_digest(computed_signature, expected_signature)
|
||
|
||
# In your webhook handler
|
||
@app.route('/webhook', methods=['POST'])
|
||
def webhook():
|
||
signature = request.headers.get('sim-signature')
|
||
body = json.dumps(request.json)
|
||
|
||
if not verify_webhook_signature(body, signature, os.environ['WEBHOOK_SECRET']):
|
||
return 'Invalid signature', 401
|
||
|
||
# Process the webhook...
|
||
```
|
||
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
### リトライポリシー
|
||
|
||
失敗したWebhook配信は指数バックオフとジッターを使用して再試行されます:
|
||
|
||
- 最大試行回数:5回
|
||
- リトライ遅延:5秒、15秒、1分、3分、10分
|
||
- ジッター:サンダリングハード問題を防ぐために最大10%の追加遅延
|
||
- HTTP 5xxと429レスポンスのみがリトライをトリガー
|
||
- 配信は30秒後にタイムアウト
|
||
|
||
<Callout type="info">
|
||
Webhook配信は非同期で処理され、ワークフロー実行のパフォーマンスに影響しません。
|
||
</Callout>
|
||
|
||
## ベストプラクティス
|
||
|
||
1. **ポーリング戦略**: ログをポーリングする場合、`order=asc`と`startDate`を使用したカーソルベースのページネーションを利用して、新しいログを効率的に取得してください。
|
||
|
||
2. **Webhookセキュリティ**: 常にWebhookシークレットを設定し、署名を検証して、リクエストがSimからのものであることを確認してください。
|
||
|
||
3. **べき等性**: `Idempotency-Key`ヘッダーを使用して、重複するWebhook配信を検出し処理してください。
|
||
|
||
4. **プライバシー**: デフォルトでは、`finalOutput`と`traceSpans`はレスポンスから除外されます。データが必要で、プライバシーへの影響を理解している場合にのみ有効にしてください。
|
||
|
||
5. **レート制限**: 429レスポンスを受け取った場合は指数バックオフを実装してください。推奨待機時間については`Retry-After`ヘッダーを確認してください。
|
||
|
||
## レート制限
|
||
|
||
APIは**トークンバケットアルゴリズム**をレート制限に使用し、バーストトラフィックを許可しながら公平な使用を提供します:
|
||
|
||
| プラン | リクエスト/分 | バースト容量 |
|
||
|------|-----------------|----------------|
|
||
| 無料 | 10 | 20 |
|
||
| プロ | 30 | 60 |
|
||
| チーム | 60 | 120 |
|
||
| エンタープライズ | 120 | 240 |
|
||
|
||
**仕組み:**
|
||
- トークンは`requestsPerMinute`のレートで補充されます
|
||
- アイドル状態のとき、最大`maxBurst`トークンまで蓄積できます
|
||
- 各リクエストは1トークンを消費します
|
||
- バースト容量によりトラフィックスパイクの処理が可能になります
|
||
|
||
レート制限情報はレスポンスヘッダーに含まれています:
|
||
- `X-RateLimit-Limit`:1分あたりのリクエスト数(補充レート)
|
||
- `X-RateLimit-Remaining`:現在利用可能なトークン
|
||
- `X-RateLimit-Reset`:トークンが次に補充されるISOタイムスタンプ
|
||
|
||
## 例:新しいログのポーリング
|
||
|
||
```javascript
|
||
let cursor = null;
|
||
const workspaceId = 'YOUR_WORKSPACE_ID';
|
||
const startDate = new Date().toISOString();
|
||
|
||
async function pollLogs() {
|
||
const params = new URLSearchParams({
|
||
workspaceId,
|
||
startDate,
|
||
order: 'asc',
|
||
limit: '100'
|
||
});
|
||
|
||
if (cursor) {
|
||
params.append('cursor', cursor);
|
||
}
|
||
|
||
const response = await fetch(
|
||
`https://sim.ai/api/v1/logs?${params}`,
|
||
{
|
||
headers: {
|
||
'x-api-key': 'YOUR_API_KEY'
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
|
||
// Process new logs
|
||
for (const log of data.data) {
|
||
console.log(`New execution: ${log.executionId}`);
|
||
}
|
||
|
||
// Update cursor for next poll
|
||
if (data.nextCursor) {
|
||
cursor = data.nextCursor;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Poll every 30 seconds
|
||
setInterval(pollLogs, 30000);
|
||
```
|
||
|
||
## 例:ウェブフックの処理
|
||
|
||
```javascript
|
||
import express from 'express';
|
||
import crypto from 'crypto';
|
||
|
||
const app = express();
|
||
app.use(express.json());
|
||
|
||
app.post('/sim-webhook', (req, res) => {
|
||
// Verify signature
|
||
const signature = req.headers['sim-signature'];
|
||
const body = JSON.stringify(req.body);
|
||
|
||
if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET)) {
|
||
return res.status(401).send('Invalid signature');
|
||
}
|
||
|
||
// Check timestamp to prevent replay attacks
|
||
const timestamp = parseInt(req.headers['sim-timestamp']);
|
||
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
|
||
|
||
if (timestamp < fiveMinutesAgo) {
|
||
return res.status(401).send('Timestamp too old');
|
||
}
|
||
|
||
// Process the webhook
|
||
const event = req.body;
|
||
|
||
switch (event.type) {
|
||
case 'workflow.execution.completed':
|
||
const { workflowId, executionId, status, cost } = event.data;
|
||
|
||
if (status === 'error') {
|
||
console.error(`Workflow ${workflowId} failed: ${executionId}`);
|
||
// Handle error...
|
||
} else {
|
||
console.log(`Workflow ${workflowId} completed: ${executionId}`);
|
||
console.log(`Cost: $${cost.total}`);
|
||
// Process successful execution...
|
||
}
|
||
break;
|
||
}
|
||
|
||
// Return 200 to acknowledge receipt
|
||
res.status(200).send('OK');
|
||
});
|
||
|
||
app.listen(3000, () => {
|
||
console.log('Webhook server listening on port 3000');
|
||
});
|
||
```
|