Sim

外部 API

Sim 提供了一个全面的外部 API,用于查询工作流执行日志,并在工作流完成时设置实时通知的 webhook。

身份验证

所有 API 请求都需要在 x-api-key 标头中传递 API 密钥:

curl -H "x-api-key: YOUR_API_KEY" \
  https://sim.ai/api/v1/logs?workspaceId=YOUR_WORKSPACE_ID

您可以在 Sim 仪表板的用户设置中生成 API 密钥。

日志 API

所有 API 响应都包含有关您的工作流执行限制和使用情况的信息:

"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-*)。

查询日志

使用广泛的过滤选项查询工作流执行日志。

GET /api/v1/logs

必需参数:

  • workspaceId - 您的工作区 ID

可选过滤器:

  • workflowIds - 逗号分隔的工作流 ID
  • folderIds - 逗号分隔的文件夹 ID
  • triggers - 逗号分隔的触发类型:apiwebhookschedulemanualchat
  • level - 按级别过滤:infoerror
  • startDate - 日期范围起始的 ISO 时间戳
  • endDate - 日期范围结束的 ISO 时间戳
  • executionId - 精确执行 ID 匹配
  • minDurationMs - 最小执行持续时间(毫秒)
  • maxDurationMs - 最大执行持续时间(毫秒)
  • minCost - 最小执行成本
  • maxCost - 最大执行成本
  • model - 按使用的 AI 模型过滤

分页:

  • limit - 每页结果数(默认:100)
  • cursor - 下一页的游标
  • order - 排序顺序:descasc(默认:降序)

详细级别:

  • details - 响应详细级别:basic, full(默认:basic)
  • includeTraceSpans - 包含跟踪跨度(默认:false)
  • includeFinalOutput - 包含最终输出(默认:false)
{
  "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
    }
  }
}

获取日志详情

检索特定日志条目的详细信息。

GET /api/v1/logs/{id}
{
  "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
      }
    }
  }
}

获取执行详情

检索执行详情,包括工作流状态快照。

GET /api/v1/logs/executions/{executionId}
{
  "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": {...}
  }
}

通知

通过 webhook、电子邮件或 Slack 获取工作流执行完成的实时通知。通知在工作区级别从日志页面进行配置。

配置

通过点击菜单按钮并选择“配置通知”从日志页面配置通知。

通知渠道:

  • Webhook:向您的端点发送 HTTP POST 请求
  • 电子邮件:接收包含执行详情的电子邮件通知
  • Slack:向 Slack 频道发送消息

工作流选择:

  • 选择特定的工作流进行监控
  • 或选择“所有工作流”以包含当前和未来的工作流

过滤选项:

  • levelFilter:接收的日志级别(infoerror
  • triggerFilter:接收的触发类型(apiwebhookschedulemanualchat

可选数据:

  • includeFinalOutput:包含工作流的最终输出
  • includeTraceSpans:包含详细的执行跟踪跨度
  • includeRateLimits:包含速率限制信息(同步/异步限制和剩余)
  • includeUsageData:包含计费周期的使用情况和限制

警报规则

与其为每次执行接收通知,不如配置警报规则,仅在检测到问题时收到通知:

连续失败

  • 在 X 次连续失败执行后发出警报(例如,连续 3 次失败)
  • 当执行成功时重置

失败率

  • 当失败率在过去 Y 小时内超过 X% 时发出警报
  • 需要窗口内至少 5 次执行
  • 仅在整个时间窗口结束后触发

延迟阈值

  • 当任何执行时间超过 X 秒时发出警报
  • 用于捕捉缓慢或挂起的工作流

延迟峰值

  • 当执行时间比平均值慢 X% 时发出警报
  • 与配置时间窗口内的平均持续时间进行比较
  • 需要至少 5 次执行以建立基线

成本阈值

  • 当单次执行成本超过 $X 时发出警报
  • 用于捕捉高成本的 LLM 调用

无活动

  • 当 X 小时内没有执行发生时发出警报
  • 用于监控应定期运行的计划工作流

错误计数

  • 当错误计数在某个时间窗口内超过 X 时发出警报
  • 跟踪总错误数,而非连续错误

所有警报类型都包括 1 小时的冷却时间,以防止通知过多。

Webhook 配置

对于 webhooks,可用以下附加选项:

  • url:您的 webhook 端点 URL
  • secret:用于 HMAC 签名验证的可选密钥

负载结构

当工作流执行完成时,Sim 会发送以下负载(通过 webhook POST、电子邮件或 Slack):

{
  "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:

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...
});
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...

重试策略

失败的 webhook 交付将使用指数退避和抖动进行重试:

  • 最大尝试次数:5
  • 重试延迟:5 秒、15 秒、1 分钟、3 分钟、10 分钟
  • 抖动:最多额外延迟 10% 以防止蜂拥效应
  • 仅 HTTP 5xx 和 429 响应会触发重试
  • 交付在 30 秒后超时

Webhook 交付是异步处理的,不会影响工作流执行性能。

最佳实践

  1. 轮询策略:在轮询日志时,使用基于游标的分页与 order=ascstartDate 来高效获取新日志。

  2. Webhook 安全性:始终配置一个 webhook 密钥并验证签名,以确保请求来自 Sim。

  3. 幂等性:使用 Idempotency-Key 标头检测并处理重复的 webhook 交付。

  4. 隐私:默认情况下,finalOutputtraceSpans 会从响应中排除。仅在需要这些数据并了解隐私影响时启用它们。

  5. 速率限制:当收到 429 响应时,实施指数退避。检查 Retry-After 标头以获取推荐的等待时间。

速率限制

该 API 使用 令牌桶算法 进行速率限制,在提供公平使用的同时允许突发流量:

计划请求/分钟突发容量
免费1020
专业版3060
团队版60120
企业版120240

工作原理:

  • 令牌以 requestsPerMinute 的速率补充
  • 空闲时最多可累积 maxBurst 个令牌
  • 每个请求消耗 1 个令牌
  • 突发容量允许处理流量高峰

速率限制信息包含在响应头中:

  • X-RateLimit-Limit:每分钟请求数(补充速率)
  • X-RateLimit-Remaining:当前可用令牌数
  • X-RateLimit-Reset:令牌下次补充的 ISO 时间戳

示例:轮询新日志

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);

示例:处理 Webhooks

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');
});
On this page

On this page

Start building today
Trusted by over 60,000 builders.
Build Agentic workflows visually on a drag-and-drop canvas or with natural language.
Get started