Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docs-embeddings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Setup Node
uses: actions/setup-node@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/i18n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Run Lingo.dev translations
env:
Expand Down Expand Up @@ -116,7 +116,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Install dependencies
run: bun install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-ts-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Setup Node
uses: actions/setup-node@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/trigger-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.22

- name: Install dependencies
run: bun install
Expand Down
32 changes: 32 additions & 0 deletions apps/docs/content/docs/en/execution/costs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,38 @@ Different subscription plans have different usage limits:
| **Team** | $500 (pooled) | 50 sync, 100 async |
| **Enterprise** | Custom | Custom |

## Billing Model

Sim uses a **base subscription + overage** billing model:

### How It Works

**Pro Plan ($20/month):**
- Monthly subscription includes $20 of usage
- Usage under $20 → No additional charges
- Usage over $20 → Pay the overage at month end
- Example: $35 usage = $20 (subscription) + $15 (overage)

**Team Plan ($40/seat/month):**
- Pooled usage across all team members
- Overage calculated from total team usage
- Organization owner receives one bill

**Enterprise Plans:**
- Fixed monthly price, no overages
- Custom usage limits per agreement

### Threshold Billing

When unbilled overage reaches $50, Sim automatically bills the full unbilled amount.

**Example:**
- Day 10: $70 overage → Bill $70 immediately
- Day 15: Additional $35 usage ($105 total) → Already billed, no action
- Day 20: Another $50 usage ($155 total, $85 unbilled) → Bill $85 immediately

This spreads large overage charges throughout the month instead of one large bill at period end.

## Cost Management Best Practices

1. **Monitor Regularly**: Check your usage dashboard frequently to avoid surprises
Expand Down
81 changes: 79 additions & 2 deletions apps/docs/content/docs/en/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -593,14 +593,91 @@ async function executeClientSideWorkflow() {
});

console.log('Workflow result:', result);

// Update UI with result
document.getElementById('result')!.textContent =
document.getElementById('result')!.textContent =
JSON.stringify(result.output, null, 2);
} catch (error) {
console.error('Error:', error);
}
}
```

### File Upload

File objects are automatically detected and converted to base64 format. Include them in your input under the field name matching your workflow's API trigger input format.

The SDK converts File objects to this format:
```typescript
{
type: 'file',
data: 'data:mime/type;base64,base64data',
name: 'filename',
mime: 'mime/type'
}
```

Alternatively, you can manually provide files using the URL format:
```typescript
{
type: 'url',
data: 'https://example.com/file.pdf',
name: 'file.pdf',
mime: 'application/pdf'
}
```

<Tabs items={['Browser', 'Node.js']}>
<Tab value="Browser">
```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
apiKey: process.env.NEXT_PUBLIC_SIM_API_KEY!
});

// From file input
async function handleFileUpload(event: Event) {
const input = event.target as HTMLInputElement;
const files = Array.from(input.files || []);

// Include files under the field name from your API trigger's input format
const result = await client.executeWorkflow('workflow-id', {
input: {
documents: files, // Must match your workflow's "files" field name
instructions: 'Analyze these documents'
}
});

console.log('Result:', result);
}
```
</Tab>
<Tab value="Node.js">
```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';
import fs from 'fs';

const client = new SimStudioClient({
apiKey: process.env.SIM_API_KEY!
});

// Read file and create File object
const fileBuffer = fs.readFileSync('./document.pdf');
const file = new File([fileBuffer], 'document.pdf', {
type: 'application/pdf'
});

// Include files under the field name from your API trigger's input format
const result = await client.executeWorkflow('workflow-id', {
input: {
documents: [file], // Must match your workflow's "files" field name
query: 'Summarize this document'
}
});
```
</Tab>
</Tabs>

// Attach to button click
document.getElementById('executeBtn')?.addEventListener('click', executeClientSideWorkflow);
Expand Down
59 changes: 57 additions & 2 deletions apps/docs/content/docs/en/triggers/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ The API trigger exposes your workflow as a secure HTTP endpoint. Send JSON data
/>
</div>

Add an **Input Format** field for each parameter. Runtime output keys mirror the schema and are also available under `<api.input>`.
Add an **Input Format** field for each parameter. Supported types:

Manual runs in the editor use the `value` column so you can test without sending a request. During execution the resolver populates both `<api.userId>` and `<api.input.userId>`.
- **string** - Text values
- **number** - Numeric values
- **boolean** - True/false values
- **json** - JSON objects
- **files** - File uploads (access via `<api.fieldName[0].url>`, `<api.fieldName[0].name>`, etc.)

Runtime output keys mirror the schema and are available under `<api.input>`.

Manual runs in the editor use the `value` column so you can test without sending a request. During execution the resolver populates both `<api.fieldName>` and `<api.input.fieldName>`.

## Request Example

Expand Down Expand Up @@ -123,6 +131,53 @@ data: {"blockId":"agent1-uuid","chunk":" complete"}
| `<api.field>` | Field defined in the Input Format |
| `<api.input>` | Entire structured request body |

### File Upload Format

The API accepts files in two formats:

**1. Base64-encoded files** (recommended for SDKs):
```json
{
"documents": [{
"type": "file",
"data": "data:application/pdf;base64,JVBERi0xLjQK...",
"name": "document.pdf",
"mime": "application/pdf"
}]
}
```
- Maximum file size: 20MB per file
- Files are uploaded to cloud storage and converted to UserFile objects with all properties

**2. Direct URL references**:
```json
{
"documents": [{
"type": "url",
"data": "https://example.com/document.pdf",
"name": "document.pdf",
"mime": "application/pdf"
}]
}
```
- File is not uploaded, URL is passed through directly
- Useful for referencing existing files

### File Properties

For files, access all properties:

| Property | Description | Type |
|----------|-------------|------|
| `<api.fieldName[0].url>` | Signed download URL | string |
| `<api.fieldName[0].name>` | Original filename | string |
| `<api.fieldName[0].size>` | File size in bytes | number |
| `<api.fieldName[0].type>` | MIME type | string |
| `<api.fieldName[0].uploadedAt>` | Upload timestamp (ISO 8601) | string |
| `<api.fieldName[0].expiresAt>` | URL expiry timestamp (ISO 8601) | string |

For URL-referenced files, the same properties are available except `uploadedAt` and `expiresAt` since the file is not uploaded to our storage.

If no Input Format is defined, the executor exposes the raw JSON at `<api.input>` only.

<Callout type="warning">
Expand Down
25 changes: 18 additions & 7 deletions apps/docs/content/docs/en/triggers/chat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,24 @@ The Chat trigger creates a conversational interface for your workflow. Deploy yo

The trigger writes three fields that downstream blocks can reference:

| Reference | Description |
|-----------|-------------|
| `<chat.input>` | Latest user message |
| `<chat.conversationId>` | Conversation thread ID |
| `<chat.files>` | Optional uploaded files |

Files include `name`, `mimeType`, and a signed download `url`.
| Reference | Description | Type |
|-----------|-------------|------|
| `<chat.input>` | Latest user message | string |
| `<chat.conversationId>` | Conversation thread ID | string |
| `<chat.files>` | Optional uploaded files | files array |

### File Properties

Access individual file properties using array indexing:

| Property | Description | Type |
|----------|-------------|------|
| `<chat.files[0].url>` | Signed download URL | string |
| `<chat.files[0].name>` | Original filename | string |
| `<chat.files[0].size>` | File size in bytes | number |
| `<chat.files[0].type>` | MIME type | string |
| `<chat.files[0].uploadedAt>` | Upload timestamp (ISO 8601) | string |
| `<chat.files[0].expiresAt>` | URL expiry timestamp (ISO 8601) | string |

## Usage Notes

Expand Down
20 changes: 14 additions & 6 deletions apps/sim/app/api/__test-utils__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1116,12 +1116,20 @@ export function createMockDatabase(options: MockDatabaseOptions = {}) {

const createUpdateChain = () => ({
set: vi.fn().mockImplementation(() => ({
where: vi.fn().mockImplementation(() => {
if (updateOptions.throwError) {
return Promise.reject(createDbError('update', updateOptions.errorMessage))
}
return Promise.resolve(updateOptions.results)
}),
where: vi.fn().mockImplementation(() => ({
returning: vi.fn().mockImplementation(() => {
if (updateOptions.throwError) {
return Promise.reject(createDbError('update', updateOptions.errorMessage))
}
return Promise.resolve(updateOptions.results)
}),
then: vi.fn().mockImplementation((resolve) => {
if (updateOptions.throwError) {
return Promise.reject(createDbError('update', updateOptions.errorMessage))
}
return Promise.resolve(updateOptions.results).then(resolve)
}),
})),
})),
})

Expand Down
4 changes: 4 additions & 0 deletions apps/sim/app/api/billing/update-cost/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { userStats } from '@sim/db/schema'
import { eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { isBillingEnabled } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
Expand Down Expand Up @@ -148,6 +149,9 @@ export async function POST(req: NextRequest) {
addedTokens: totalTokens,
})

// Check if user has hit overage threshold and bill incrementally
await checkAndBillOverageThreshold(userId)

const duration = Date.now() - startTime

logger.info(`[${requestId}] Cost update completed successfully`, {
Expand Down
Loading