---
title: External API
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Video } from '@/components/ui/video'

Sim provides a comprehensive external API for querying workflow run logs and setting up webhooks for real-time notifications when workflows complete.

## Authentication

All API requests require an API key passed in the `x-api-key` header:

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

You can generate API keys from the Sim platform and navigate to **Settings**, then go to **Sim Keys** and click **Create**.

## Logs API

All API responses include information about your workflow run limits and usage:

```json
"limits": {
  "workflowExecutionRateLimit": {
    "sync": {
      "requestsPerMinute": 150,  // Sustained rate limit per minute
      "maxBurst": 300,           // Maximum burst capacity
      "remaining": 298,          // Current tokens available (up to maxBurst)
      "resetAt": "..."           // When tokens next refill
    },
    "async": {
      "requestsPerMinute": 1000, // Sustained rate limit per minute
      "maxBurst": 2000,          // Maximum burst capacity
      "remaining": 1998,         // 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
  }
}
```

**Note:** Rate limits use a token bucket algorithm. `remaining` can exceed `requestsPerMinute` up to `maxBurst` when you haven't used your full allowance recently, allowing for burst traffic. The rate limits in the response body are for workflow runs. The rate limits for calling this API endpoint are in the response headers (`X-RateLimit-*`).

### Query Logs

Query workflow run logs with extensive filtering options.

<Tabs items={['Request', 'Response']}>
  <Tab value="Request">
    ```http
    GET /api/v1/logs
    ```

    **Required Parameters:**
    - `workspaceId` - Your workspace ID

    **Optional Filters:**
    - `workflowIds` - Comma-separated workflow IDs
    - `folderIds` - Comma-separated folder IDs
    - `triggers` - Comma-separated trigger types: `api`, `webhook`, `schedule`, `manual`, `chat`
    - `level` - Filter by level: `info`, `error`
    - `startDate` - ISO timestamp for date range start
    - `endDate` - ISO timestamp for date range end
    - `executionId` - Exact run ID match
    - `minDurationMs` - Minimum run duration in milliseconds
    - `maxDurationMs` - Maximum run duration in milliseconds
    - `minCost` - Minimum run cost
    - `maxCost` - Maximum run cost
    - `model` - Filter by AI model used

    **Pagination:**
    - `limit` - Results per page (default: 100)
    - `cursor` - Cursor for next page
    - `order` - Sort order: `desc`, `asc` (default: desc)

    **Detail Level:**
    - `details` - Response detail level: `basic`, `full` (default: basic)
    - `includeTraceSpans` - Include trace spans (default: false)
    - `includeFinalOutput` - Include final output (default: 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": 150,
              "maxBurst": 300,
              "remaining": 298,
              "resetAt": "2025-01-01T12:35:56.789Z"
            },
            "async": {
              "requestsPerMinute": 1000,
              "maxBurst": 2000,
              "remaining": 1998,
              "resetAt": "2025-01-01T12:35:56.789Z"
            }
          },
          "usage": {
            "currentPeriodCost": 1.234,
            "limit": 10,
            "plan": "pro",
            "isExceeded": false
          }
        }
    }
    ```
  </Tab>
</Tabs>

### Get Log Details

Retrieve detailed information about a specific log entry.

<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": 150,
              "maxBurst": 300,
              "remaining": 298,
              "resetAt": "2025-01-01T12:35:56.789Z"
            },
            "async": {
              "requestsPerMinute": 1000,
              "maxBurst": 2000,
              "remaining": 1998,
              "resetAt": "2025-01-01T12:35:56.789Z"
            }
          },
          "usage": {
            "currentPeriodCost": 1.234,
            "limit": 10,
            "plan": "pro",
            "isExceeded": false
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

### Get Run Details

Retrieve run details including the workflow state snapshot.

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

## Notifications

Get real-time notifications when workflow runs complete via webhook, email, or Slack. Notifications are configured at the workspace level from the Logs page.

### Configuration

Configure notifications from the Logs page by clicking the menu button and selecting "Configure Notifications".

**Notification Channels:**
- **Webhook**: Send HTTP POST requests to your endpoint
- **Email**: Receive email notifications with run details
- **Slack**: Post messages to a Slack channel

**Workflow Selection:**
- Select specific workflows to monitor
- Or choose "All Workflows" to include current and future workflows

**Filtering Options:**
- `levelFilter`: Log levels to receive (`info`, `error`)
- `triggerFilter`: Trigger types to receive (`api`, `webhook`, `schedule`, `manual`, `chat`)

**Optional Data:**
- `includeFinalOutput`: Include the workflow's final output
- `includeTraceSpans`: Include detailed trace spans
- `includeRateLimits`: Include rate limit information (sync/async limits and remaining)
- `includeUsageData`: Include billing period usage and limits

### Alert Rules

Instead of receiving notifications for every run, configure alert rules to be notified only when issues are detected:

**Consecutive Failures**
- Alert after X consecutive failed runs (e.g., 3 failures in a row)
- Resets when a run succeeds

**Failure Rate**
- Alert when failure rate exceeds X% over the last Y hours
- Requires minimum 5 runs in the window
- Only triggers after the full time window has elapsed

**Latency Threshold**
- Alert when any run takes longer than X seconds
- Useful for catching slow or hanging workflows

**Latency Spike**
- Alert when a run is X% slower than the average
- Compares against the average duration over the configured time window
- Requires minimum 5 runs to establish baseline

**Cost Threshold**
- Alert when a single run costs more than $X
- Useful for catching expensive LLM calls

**No Activity**
- Alert when no runs occur within X hours
- Useful for monitoring scheduled workflows that should run regularly

**Error Count**
- Alert when error count exceeds X within a time window
- Tracks total errors, not consecutive

All alert types include a 1-hour cooldown to prevent notification spam.

### Webhook Configuration

For webhooks, additional options are available:
- `url`: Your webhook endpoint URL
- `secret`: Optional secret for HMAC signature verification

### Payload Structure

When a workflow run completes, Sim sends the following payload (via webhook POST, email, or 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 Headers

Each webhook request includes these headers (webhook channel only):

- `sim-event`: Event type (always `workflow.execution.completed`)
- `sim-timestamp`: Unix timestamp in milliseconds
- `sim-delivery-id`: Unique delivery ID for idempotency
- `sim-signature`: HMAC-SHA256 signature for verification (if secret configured)
- `Idempotency-Key`: Same as delivery ID for duplicate detection

### Signature Verification

If you configure a webhook secret, verify the signature to ensure the webhook is from 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>

### Retry Policy

Failed webhook deliveries are retried with exponential backoff and jitter:

- Maximum attempts: 5
- Retry delays: 5 seconds, 15 seconds, 1 minute, 3 minutes, 10 minutes
- Jitter: Up to 10% additional delay to prevent thundering herd
- Only HTTP 5xx and 429 responses trigger retries
- Deliveries timeout after 30 seconds

<Callout type="info">
  Webhook deliveries are processed asynchronously and don't affect workflow run performance.
</Callout>

## Best Practices

1. **Polling Strategy**: When polling for logs, use cursor-based pagination with `order=asc` and `startDate` to fetch new logs efficiently.

2. **Webhook Security**: Always configure a webhook secret and verify signatures to ensure requests are from Sim.

3. **Idempotency**: Use the `Idempotency-Key` header to detect and handle duplicate webhook deliveries.

4. **Privacy**: By default, `finalOutput` and `traceSpans` are excluded from responses. Only enable these if you need the data and understand the privacy implications.

5. **Rate Limiting**: Implement exponential backoff when you receive 429 responses. Check the `Retry-After` header for the recommended wait time.

## Rate Limiting

The API uses a **token bucket algorithm** for rate limiting, providing fair usage while allowing burst traffic:

| Plan | Requests/Minute | Burst Capacity |
|------|-----------------|----------------|
| Free | 30 | 60 |
| Pro | 100 | 200 |
| Team | 200 | 400 |
| Enterprise | 500 | 1000 |

**How it works:**
- Tokens refill at `requestsPerMinute` rate
- You can accumulate up to `maxBurst` tokens when idle
- Each request consumes 1 token
- Burst capacity allows handling traffic spikes

Rate limit information is included in response headers:
- `X-RateLimit-Limit`: Requests per minute (refill rate)
- `X-RateLimit-Remaining`: Current tokens available
- `X-RateLimit-Reset`: ISO timestamp when tokens next refill

## Example: Polling for New Logs

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

## Example: Processing Webhooks

```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');
});
```

import { FAQ } from '@/components/ui/faq'

<FAQ items={[
  { question: "How do I trigger an async run via the API?", answer: "Set the X-Execution-Mode header to 'async' on your POST request to /api/workflows/{id}/execute. The API returns a 202 response with a jobId, executionId, and a statusUrl you can poll to check when the job completes. Async mode does not support draft state, workflow overrides, or selective output options." },
  { question: "What authentication methods does the API support?", answer: "The API supports two authentication methods: API keys passed in the x-api-key header, and session-based authentication for logged-in users. API keys can be generated from Settings > Sim Keys in the platform. Workflows with public API access enabled can also be called without authentication." },
  { question: "How does the webhook retry policy work?", answer: "Failed webhook deliveries are retried up to 5 times with exponential backoff: 5 seconds, 15 seconds, 1 minute, 3 minutes, and 10 minutes, plus up to 10% jitter. Only HTTP 5xx and 429 responses trigger retries. Each delivery times out after 30 seconds." },
  { question: "What rate limits apply to the Logs API?", answer: "Rate limits use a token bucket algorithm. Free plans get 30 requests/minute with 60 burst capacity, Pro gets 100/200, Team gets 200/400, and Enterprise gets 500/1000. These are separate from workflow run rate limits, which are shown in the response body." },
  { question: "How do I verify that a webhook is from Sim?", answer: "Configure a webhook secret when setting up notifications. Sim signs each delivery with HMAC-SHA256 using the format 't={timestamp},v1={signature}' in the sim-signature header. Compute the HMAC of '{timestamp}.{body}' with your secret and compare it to the signature value." },
  { question: "What alert rules are available for notifications?", answer: "You can configure alerts for consecutive failures, failure rate thresholds, latency thresholds, latency spikes (percentage above average), cost thresholds, no-activity periods, and error counts within a time window. All alert types include a 1-hour cooldown to prevent notification spam." },
  { question: "Can I filter which runs trigger notifications?", answer: "Yes. You can filter notifications by specific workflows (or select all), log level (info or error), and trigger type (api, webhook, schedule, manual, chat). You can also choose whether to include final output, trace spans, rate limits, and usage data in the notification payload." },
]} />
