# Documentation (/) # Sim Documentation [#sim-documentation] Welcome to Sim, the open-source AI workspace where teams build, deploy, and manage AI agents. Create agents visually with the workflow builder, conversationally through Chat, or programmatically with the API — connected to 1,000+ integrations and every major LLM. ## Quick Start [#quick-start] Learn what you can build with Sim Build your first agent in 10 minutes Learn about the building blocks Explore 1,000+ integrations ## Core Concepts [#core-concepts] Understand how data flows between blocks Work with workflow and environment variables Monitor agent runs and manage costs Start agents via API, webhooks, or schedules ## Advanced Features [#advanced-features] Set up workspace roles and permissions Connect external services with Model Context Protocol Integrate Sim into your applications --- # What is Sim? (/academy) Sim is a unified workspace for **building and operating AI systems**. Everything you make lives in one place, and everything connects. ## The workspace [#the-workspace] A **workspace** is a collection of shared resources and the workflows that use them. * **Resources** are your data: [tables](/academy/tables/intro) (structured records), [knowledge bases](/academy/knowledge-bases/intro) (searchable memory), and [files](/academy/files/intro) (documents and media). * **Workflows** are your processes: the agents and automations that read those resources, act, and write results back. Everything in a workspace can see everything else in it. A workflow reads a table, searches a knowledge base, produces a file, and the next workflow picks up where it left off. ## Integrations connect you to the outside world [#integrations-connect-you-to-the-outside-world] **Integrations** are plugins. They let your resources and workflows reach services beyond Sim, send a Slack message, read a Gmail inbox, write a row to a CRM, call any API. You connect an account once, and any workflow can use it. So the whole picture is small: a workspace is **data + processes**, integrations plug it into **everything else**, and the rest of this course is just learning each piece and watching them compose. ## Related documentation [#related-documentation] * [Introduction](/introduction) * [Getting started](/getting-started) --- # Choosing what to use (/agents/choosing) When you build an agent, several features overlap: a deterministic block and an agent tool can run the same integration, and a custom tool, an MCP tool, and a workflow-as-tool can all give an agent the same action. This page lays out the differences so you pick the right one. They vary along three lines: whether the action is **deterministic** (always runs) or **model-decided** (an agent chooses), whether it lives in one workflow or is **reusable** across your workspace, and whether it comes from Sim or an **external** provider. The running example is a workflow that scores inbound sales leads. It reads a new lead, enriches it, decides on a score, logs the result, and notifies the team. Each option below builds part of it: {/* VISUAL: decision tree. Always happens? → deterministic block. Else agent chooses? → agent tool. Reuse across workspace? → custom tool. External toolset? → MCP. Whole workflow? → workflow-as-tool. Reusable instructions? → skill. */} ## Deterministic block [#deterministic-block] A **block** is a single step that runs at a fixed point on the path, with no model deciding whether to. It always runs when the workflow reaches it. Use one when the action must happen every time: an API call, a data transform, a branch. In the lead scorer, a [Google Sheets](/integrations) block always appends the scored lead to a tracking sheet, and a [Function](/workflows/blocks/function) block always reshapes the enrichment response into the fields the next step expects. There is no judgment call. The work is guaranteed. Most steps in a workflow are blocks. Reach for the kinds below only when you want a model to decide, or you want to reuse something. ## Agent tool [#agent-tool] An **agent tool** is an action you hand to an [Agent](/workflows/blocks/agent) block. The agent reads the task and decides whether and when to call it. The same catalog of [integrations](/integrations) that exist as standalone blocks can also be attached to an agent as tools. In the lead scorer, the Agent has a Search tool and a Send Email tool. For a lead with a thin profile it runs Search to gather context; for a strong lead it calls Send Email. A thin, obvious lead might trigger neither. The agent chooses per run. Each tool carries a `usageControl` setting. **Auto** lets the model decide (the default). **Force** makes the agent call the tool every run, for actions that should never be skipped, like always logging the decision. **None** removes the tool from that agent. A block and an agent tool can be the same underlying integration. The difference is who decides. A block runs because the path reached it. An agent tool runs because the agent chose it. ## Custom tool [#custom-tool] A **custom tool** is a tool you define once with an object schema and a snippet of JavaScript or Python, then reuse across your workspace. It needs no external account. Use one when you have logic that several agents or workflows would otherwise duplicate. In the lead scorer, a `normalizeCompanyDomain` custom tool cleans a raw website into a canonical domain. The same tool serves the lead scorer, a deduplication workflow, and a reporting agent. Define it in the workspace, then pick it from any Agent block's tool list. ## MCP server [#mcp-server] **MCP** (Model Context Protocol) is a standard for connecting an external tool provider. Connect an [MCP server](/agents/mcp) and its tools appear in the agent's tool list as a set. Use it to bring in a complete toolset that Sim does not provide natively, rather than wiring each action by hand. In the lead scorer, your CRM vendor ships an MCP server. After you connect it once, the agent can read accounts and update records through the vendor's own tools. The difference from a custom tool is who maintains it: a custom tool is code you wrote, while an MCP server is a toolbox someone else maintains. ## Workflow-as-tool [#workflow-as-tool] A **workflow-as-tool** is a whole workflow handed to an agent as one callable tool. You pick the workflow in the [Agent](/workflows/blocks/agent) block's tool list; the agent decides when to call it and supplies the inputs, which arrive at the child's [Start](/workflows/triggers/start) trigger, and the child's result comes back as the tool's output. Use it when a multi-step procedure should be at the agent's disposal, not on the path. In the lead scorer, the agent has a `Deep Enrich` workflow as a tool — its own five-step procedure. For a thin lead, the agent calls it to fill out the profile before scoring; for a complete lead, it never runs. The agent weighs a whole procedure the same way it weighs a single action. A workflow can also run as a fixed step: the [Workflow](/workflows/blocks/workflow) block calls a child workflow because the path reached it. Same child workflow, same Start trigger — the difference, as with blocks and agent tools, is who decides. The Enrich step in the diagram is that deterministic case. ## Skill [#skill] A **skill** is reusable instructions, a written playbook an agent can follow. Each skill has a short name and description that are always visible to the agent, plus a longer body the agent loads only when it decides the skill applies. Use one to capture how something should be done, separate from the tools that do it. In the lead scorer, a `lead-scoring-rubric` skill spells out the bands and disqualifiers. The agent sees the skill's name and description on every run, and when a lead is ambiguous it loads the full rubric and applies it. The distinction is simple: a tool is an action the agent takes, and a skill is guidance the agent reads. Manage skills in your [workspace](/agents/skills). | | Who decides it runs | Where it lives | How you author it | | -------------------- | ------------------- | ------------------ | --------------------------------------- | | **Block** | The path | The workflow | Drag in and configure | | **Agent tool** | The agent | On the Agent block | Pick from the integrations | | **Custom tool** | The agent | The workspace | Write the code once | | **MCP server** | The agent | An external server | Connect it | | **Workflow-as-tool** | The agent | Its own workflow | Build it, then pick it in the tool list | | **Skill** | The agent | The workspace | Write the instructions | ## Putting it together [#putting-it-together] The lead scorer uses six kinds at once: blocks for the steps that must always run, agent tools for the calls the model should weigh, a custom tool for shared logic, an MCP server for the CRM, a deep-enrichment workflow the agent calls when a lead needs it, and a skill for the scoring rules. In general, start with a block, then move to an agent tool when a model should decide. Reach for the rest only when you need reuse, an external toolset, a sub-workflow, or written guidance. ## Next [#next] --- # Custom Tools (/agents/custom-tools) Custom tools let you write your own JavaScript functions and make them available as callable tools in Agent blocks. This is useful when you need functionality that isn't covered by Sim's built-in integrations — for example, calling an internal API, performing a custom calculation, or transforming data in a specific way. ## How Custom Tools Work [#how-custom-tools-work] A custom tool has two parts: 1. **Schema** — A JSON definition describing the tool's name, description, and parameters (using the OpenAI function-calling format). This tells the AI agent what the tool does and what inputs it expects. 2. **Code** — A JavaScript function body that runs when the agent calls the tool. Parameters defined in the schema are available as variables in your code. When an Agent block has access to a custom tool, the AI model decides when to call it based on the schema description and the conversation context — just like built-in tools. ## Creating a Custom Tool [#creating-a-custom-tool] ### Open Custom Tools settings [#open-custom-tools-settings] Navigate to **Settings → Custom Tools** in your workspace and click **Add**. ### Define the schema [#define-the-schema] In the **Schema** tab, define your tool using JSON in the OpenAI function-calling format: ```json { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a city", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city name" }, "units": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature units" } }, "required": ["city"] } } } ``` You can use the AI wand button to generate a schema from a natural language description of what the tool should do. ### Write the code [#write-the-code] Switch to the **Code** tab and write the JavaScript function body. Parameters from your schema are available directly as variables: ```javascript const response = await fetch( `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=${units === 'celsius' ? 'metric' : 'imperial'}&appid={{OPENWEATHER_API_KEY}}` ); const data = await response.json(); return { temperature: data.main.temp, description: data.weather[0].description, humidity: data.main.humidity }; ``` For a secret used as a complete JavaScript expression, prefer the unquoted form, such as `const apiKey = {{OPENWEATHER_API_KEY}};`. Quoted and embedded forms remain supported, including the placeholder embedded in the URL above, `"Bearer {{KEY}}"`, template literals, and JavaScript regex literals. The value is bound separately when the tool executes rather than pasted into its source, so its exact string contents are preserved. You can also use the AI wand to generate code from a description. Environment variables are referenced with `{{KEY}}` syntax. ### Save [#save] Click **Save** to create the tool. It's now available to use in any Agent block across your workspace. ## Using Custom Tools in Workflows [#using-custom-tools-in-workflows] Once created, custom tools appear alongside built-in tools when configuring an Agent block: 1. Open an Agent block 2. Click **Add Tools** 3. Find your custom tool in the tool list 4. The agent will call the tool when it determines it's relevant to the task ## Code Environment [#code-environment] ### Available Features [#available-features] * **Async/await** — Your code runs in an async context, so you can use `await` directly * **fetch()** — Make HTTP requests to external APIs * **Node.js built-ins** — Access to `crypto`, `Buffer`, and other standard modules * **Environment variables** — Use `{{KEY}}` syntax to bind secrets at execution time without placing plaintext in source code ### Limitations [#limitations] * **No npm packages** — External libraries like `axios` or `lodash` are not available. Use built-in APIs instead * **Parameters by name** — Schema parameters are available directly as variables (e.g., `city`), not via a `params` object ### Returning Results [#returning-results] Return a value from your code to send it back to the agent: ```javascript const result = await fetch(`https://api.example.com/data?q=${query}`); const data = await result.json(); return data; ``` The returned value becomes the tool output that the agent sees and can use in its response. ## Managing Custom Tools [#managing-custom-tools] From **Settings → Custom Tools** you can: * **Search** tools by name, function name, or description * **Edit** any tool's schema or code * **Delete** tools that are no longer needed Deleting a custom tool removes it from all Agent blocks that reference it. Make sure no active workflows depend on the tool before deleting. ## Permissions [#permissions] | Action | Required Permission | | -------------------- | --------------------------------- | | View custom tools | **Read**, **Write**, or **Admin** | | Create or edit tools | **Write** or **Admin** | | Delete tools | **Admin** | --- # Overview (/agents) An **agent** is a [workflow](/workflows) that reasons and acts on its own. It reads an input, decides what to do, and carries it out, calling tools and using your data along the way. You build a custom agent in Sim by composing a workflow whose thinking runs through one or more [Agent blocks](/workflows/blocks/agent). An **Agent block** is the reasoning step inside that workflow: a model reads the values available to it, decides, and returns a result that later blocks read by reference. A simple agent is a single Agent block; a larger one wires several together with other blocks. The Agent block is where the model thinks; the rest of the workflow is what it acts on and through. The example throughout is an agent that scores inbound sales leads. ## The Agent block [#the-agent-block] You set up the reasoning step by giving the Agent block a **model** and a **prompt**. The model is the LLM that powers it; you pick one from the available providers, and the default is `claude-sonnet-4-6`. The prompt is a system message that defines who the agent is and how it should behave, plus a user message that carries the input, usually a reference like ``. When it runs, the Agent block reasons, calls any tools it needs, and stores its result under its own name. By default that result is free text in `content`, read by a later block as ``, alongside run details like the model used, token counts, tool calls, and cost. Every setting and output field is in the [Agent block reference](/workflows/blocks/agent). ## What you give an agent [#what-you-give-an-agent] On its own, an Agent block can only reason and write text. You extend it so it can act, follow your rules, use your data, remember, and return results other blocks can rely on. Each feature maps to something you'd want an agent to do. ### Take an action: tools [#take-an-action-tools] To let an agent do something in the world, give it **tools**. A tool is an action the agent can call, like sending an email, searching the web, updating a CRM record, or running another workflow. You attach tools to the Agent block, and the agent decides which to call for the task in front of it. In the lead scorer, the agent has a search tool to gather context on a thin profile and an email tool to reach out to a strong lead. Tools come from a few places: * **[Integrations](/integrations)** are the catalog of external services: Gmail, Slack, Airtable, Linear, and hundreds more. * **[Custom tools](/agents/custom-tools)** are tools you define once with a schema and a snippet of code, then reuse. * **[MCP tools](/agents/mcp)** come from an external provider you connect through the Model Context Protocol. * **[Workflow-as-tool](/workflows)** makes another workflow callable, so the agent runs a whole procedure as one step. The same integration can run two ways. As a [block](/workflows#blocks) it runs because the path reached it. As an agent tool it runs because the agent chose it. Per-tool usage controls (force a tool, or disable one) are in the [Agent block reference](/workflows/blocks/agent). ### Follow a procedure: skills [#follow-a-procedure-skills] To give an agent instructions it can follow, write a [skill](/agents/skills). A skill is a reusable playbook with a short name and description the agent always sees, plus a longer body it loads only when the skill applies. In the lead scorer, a `lead-scoring-rubric` skill spells out the bands and disqualifiers, and the agent reads the full rubric only when a lead is ambiguous. A tool is an action the agent takes; a skill is guidance it reads. ### Use your documents: knowledge [#use-your-documents-knowledge] To let an agent answer from your own content, connect a [knowledge base](/knowledgebase). The agent searches it and grounds its answers in what it finds, instead of relying only on the model's general training. Give the lead scorer a knowledge base of past deals and it can compare a new lead against ones you've closed before. ### Remember across runs: memory [#remember-across-runs-memory] To let an agent reuse information from one run to the next, give it [memory](/integrations/memory), which stores and recalls values keyed to a conversation. Without it, each run starts fresh; with it, an agent in a chat carries what was said earlier into later messages. ### Return a usable result: structured output [#return-a-usable-result-structured-output] To make an agent's result something later blocks can act on, give it a **structured output**: a typed object you define instead of free text. In the lead scorer, the agent returns `{ score, tier, reason }`, and a later [Condition](/workflows/blocks/condition) block reads `` to branch. See [how blocks pass data](/workflows/data-flow) for reading fields. ## Choosing what to use [#choosing-what-to-use] Start with one Agent block and a prompt, then add only what the task needs: a tool when the agent should act, a skill when it needs written guidance, a knowledge base when it should answer from your documents, memory when it should remember, and a structured output when a later block has to read its result. ## Next [#next] --- # Using MCP tools (/agents/mcp) The Model Context Protocol ([MCP](https://modelcontextprotocol.com/)) is an open standard for connecting AI to external tools and data. Add an MCP server to your workspace and its tools become available to your agents — a way to integrate services Sim doesn't have a built-in integration for. ## Adding an MCP Server as a Tool [#adding-an-mcp-server-as-a-tool] MCP servers provide collections of tools that your agents can use.
To add one: 1. Navigate to **Settings → MCP Tools**
MCP Tools settings page
2. Click **Add** to open the configuration modal 3. Enter a **Server Name** and **Server URL** 4. Add any required **Headers** (e.g. API keys) 5. Click **Add MCP** to save
Add New MCP Server modal
You can also configure MCP servers directly from the toolbar in an Agent block for quick setup. ### Server Configuration Options [#server-configuration-options] | Field | Description | | ------------- | ---------------------------------------------------- | | **Name** | Display name for the server | | **URL** | The MCP server endpoint | | **Transport** | Currently supports `streamable-http` | | **Headers** | Key-value pairs for authentication or custom headers | | **Timeout** | Connection timeout in milliseconds (default: 30,000) | ### Environment Variables in Configuration [#environment-variables-in-configuration] Server URLs and headers support environment variable substitution using `{{VAR_NAME}}` syntax. This keeps sensitive values like API keys out of the server configuration. ``` URL: https://api.example.com/mcp Authorization: Bearer {{MCP_API_TOKEN}} ``` When you type `{{` in the URL or header fields, a dropdown appears showing available workspace environment variables. When a saved secret is successfully substituted this way, exact occurrences of its value are masked in stored MCP tool-call traces. The real URL or header value still reaches the MCP server unchanged. See [Execution log protection](/platform/credentials#execution-log-protection) for the exact scope and limitations. ### Testing and Validation [#testing-and-validation] Click **Test Connection** before saving to verify the server is reachable and discover available tools. The test response shows the number of tools found and the protocol version. After saving, each server displays its available tools with parameter names, types, and required flags. If a server's tools change (e.g., after a server update), click **Refresh** to fetch the latest schemas. This automatically updates any agent blocks using those tools. Tool validation badges appear on servers with issues — for example, if a tool was removed from the server but is still referenced in a workflow. Click the badge to see which workflows are affected. ### Domain Allowlisting [#domain-allowlisting] Self-hosted deployments can restrict which MCP server domains are allowed by setting the `ALLOWED_MCP_DOMAINS` environment variable (comma-separated list). When set, only servers on approved domains can be added. When unset, all domains are allowed. ## Using MCP Tools in Agents [#using-mcp-tools-in-agents] Once MCP servers are configured, their tools become available within your agent blocks:
Using MCP Tool in Agent Block
1. Open an **Agent** block 2. In the **Tools** section, click **Add tool…** 3. Under **MCP Servers**, click a server to see its tools
MCP tools list for a selected server
4. Select individual tools, or choose **Use all N tools** to add every tool from that server 5. The agent can now access these tools during execution If you haven't configured a server yet, click **Add MCP Server** at the top of the dropdown to open the setup modal without leaving the block. ## Standalone MCP Tool Block [#standalone-mcp-tool-block] For more granular control, you can use the dedicated MCP Tool block to execute specific MCP tools:
Standalone MCP Tool Block
The MCP Tool block runs one configured tool with parameters you set explicitly, and its output is readable by later blocks like any other. ## When to Use MCP Tool vs Agent [#when-to-use-mcp-tool-vs-agent] | | **Agent with MCP tools** | **MCP Tool block** | | -------------- | -------------------------------- | -------------------------------------- | | **Execution** | AI decides which tools to call | Deterministic — runs the tool you pick | | **Parameters** | AI chooses at runtime | You set them explicitly | | **Best for** | Dynamic, conversational flows | Structured, repeatable steps | | **Reasoning** | Handles complex multi-step logic | One tool, one call | ## Permission Requirements [#permission-requirements] MCP functionality requires specific workspace permissions: | Action | Required Permission | | ---------------------------- | --------------------------------- | | Create or update MCP servers | **Write** or **Admin** | | Delete MCP servers | **Admin** | | Use MCP tools in agents | **Write** or **Admin** | | View available MCP tools | **Read**, **Write**, or **Admin** | | Execute MCP Tool blocks | **Read**, **Write**, or **Admin** | MCP servers run with the permissions of the user who configured them — only connect servers you trust, and review a server's tools before handing them to agents. --- # Agent skills (/agents/skills) Agent Skills are reusable packages of instructions that give your AI agents specialized capabilities. Based on the open [Agent Skills](https://agentskills.io) format, skills let you capture domain expertise, workflows, and best practices that agents can load on demand. ## How Skills Work [#how-skills-work] Skills use **progressive disclosure** to keep agent context lean: 1. **Discovery** — Only skill names and descriptions are included in the agent's system prompt (\~50-100 tokens each) 2. **Activation** — When the agent decides a skill is relevant, it calls the `load_skill` tool to load the full instructions into context 3. **Execution** — The agent follows the loaded instructions to complete the task ## Creating Skills [#creating-skills] Skills live on the **Integrations** page: click **Integrations** in the workspace sidebar, then switch to the **Skills** tab. It lists every skill in the workspace, searchable by name. Click a skill to open its detail page, where you edit, share, and delete it. The Skills tab on the Integrations page Click **+ Add to Sim** to open the skill create page, which takes three fields: | Field | Description | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Name** | A kebab-case identifier (e.g. `sql-expert`, `code-reviewer`). Max 64 characters. | | **Description** | A short explanation of what the skill does and when to use it. This is what the agent reads to decide whether to activate the skill. Max 1024 characters. | | **Content** | The full skill instructions in markdown. This is loaded when the agent activates the skill. | The description is critical — it's the only thing the agent sees before deciding to load a skill. Be specific about when and why the skill should be used. ### Importing skills [#importing-skills] Bring in an existing skill in the open [SKILL.md](https://agentskills.io/specification) format two ways: * **Import** — the **Import** action on the create page takes a `.md` file with YAML frontmatter, or a `.zip` containing a `SKILL.md`. * **Paste content** — paste the `SKILL.md` straight into **Content**. The frontmatter carries the `name` and `description`; the markdown body is the content. Integration pages suggest **curated skills** for their service — open one (HubSpot, for example) and add a suggested skill with one click. ### Writing Good Skill Content [#writing-good-skill-content] Skill content follows the same conventions as [SKILL.md files](https://agentskills.io/specification): ```markdown # SQL Expert ## When to use this skill Use when the user asks you to write, optimize, or debug SQL queries. ## Instructions 1. Always ask which database engine (PostgreSQL, MySQL, SQLite) 2. Use CTEs over subqueries for readability 3. Add index recommendations when relevant 4. Explain query plans for optimization requests ## Common Patterns ... ``` **Recommended structure:** * **When to use** — Specific triggers and scenarios * **Instructions** — Step-by-step guidance with numbered lists * **Examples** — Input/output samples showing expected behavior * **Common Patterns** — Reusable approaches for frequent tasks * **Edge Cases** — Gotchas and special considerations Keep skills focused and under 500 lines. If a skill grows too large, split it into multiple specialized skills. ## Skill Editors [#skill-editors] Everyone in the workspace sees and uses every skill — including members who join later. Nobody needs to be added to a skill to use it. Each skill has an explicit **editors** list. Editors can edit the skill, delete it, and manage the editors list. Workspace admins can always do this too — they are editors of every skill automatically and cannot be removed from the list. Whoever creates a skill becomes an editor. Open a skill from the Skills tab to manage it. The detail page has the editable fields, a **Share** action for adding editors from your workspace members, and the **Skill Editors** list at the bottom. The editors list controls who can edit a skill — it never affects who can see, use, or run it. A workflow that references a skill always executes it, no matter who runs the workflow. Treat skill content as shared team instructions, not as a secret. ## Using Skills in Chat [#using-skills-in-chat] Skills work in Chat too. Type `/` in the message box to open the skills menu, then pick a skill — or keep typing to filter by name. The skill appears in your message as a tag, e.g. `/format-markdown`. Tagging a skill loads its full instructions into the conversation, so Sim follows them for that request — no waiting for Sim to decide the skill is relevant on its own. ## Adding Skills to an Agent [#adding-skills-to-an-agent] Open any **Agent** block and find the **Skills** dropdown below the tools section. Select the skills you want the agent to have access to. Add Skill Selected skills appear as cards that you can click to edit or remove. ### What Happens at Runtime [#what-happens-at-runtime] When the workflow runs: 1. The agent's system prompt includes an `` section listing each skill's name and description 2. A `load_skill` tool is automatically added to the agent's available tools 3. When the agent determines a skill is relevant to the current task, it calls `load_skill` with the skill name 4. The full skill content is returned as a tool response, giving the agent detailed instructions This works across all supported LLM providers — the `load_skill` tool uses standard tool-calling, so no provider-specific configuration is needed. ## Common Use Cases [#common-use-cases] Skills are most valuable when agents need specialized knowledge or multi-step workflows: **Domain Expertise** * `api-integration-expert` — Best practices for calling specific APIs (authentication, rate limiting, error handling) * `data-transformation` — ETL patterns, data cleaning, and validation rules * `code-reviewer` — Code review guidelines specific to your team's standards **Workflow Templates** * `bug-investigation` — Step-by-step debugging methodology (reproduce → isolate → test → fix) * `feature-implementation` — Development workflow from requirements to deployment * `document-generator` — Templates and formatting rules for technical documentation **Company-Specific Knowledge** * `our-architecture` — System architecture diagrams, service dependencies, and deployment processes * `style-guide` — Brand guidelines, writing tone, UI/UX patterns * `customer-onboarding` — Standard procedures and common customer questions **When to use skills vs. agent instructions:** * Use **skills** for knowledge that applies across multiple workflows or changes frequently * Use **agent instructions** for task-specific context that's unique to a single agent ## Best Practices [#best-practices] **Writing Effective Descriptions** * **Be specific and keyword-rich** — Instead of "Helps with SQL", write "Write optimized SQL queries for PostgreSQL, MySQL, and SQLite, including index recommendations and query plan analysis" * **Include activation triggers** — Mention specific words or phrases that should prompt the skill (e.g., "Use when the user mentions PDFs, forms, or document extraction") * **Keep it under 200 words** — Agents scan descriptions quickly; make every word count **Skill Scope and Organization** * **One skill per domain** — A focused `sql-expert` skill works better than a broad `database-everything` skill * **Limit to 5-10 skills per agent** — More skills = more decision overhead; start small and add as needed * **Split large skills** — If a skill exceeds 500 lines, break it into focused sub-skills **Content Structure** * **Use markdown formatting** — Headers, lists, and code blocks help agents parse and follow instructions * **Provide examples** — Show input/output pairs so agents understand expected behavior * **Be explicit about edge cases** — Don't assume agents will infer special handling **Testing and Iteration** * **Test activation** — Run your workflow and verify the agent loads the skill when expected * **Check for false positives** — Make sure skills aren't activating when they shouldn't * **Refine descriptions** — If a skill isn't loading when needed, add more keywords to the description ## Learn More [#learn-more] * [Agent Skills specification](https://agentskills.io) — The open format for portable agent skills * [Example skills](https://github.com/anthropics/skills) — Browse community skill examples * [Best practices](https://agentskills.io/what-are-skills) — Writing effective skills --- # Authentication (/api-reference/authentication) To access the Sim API, you need an API key. Sim supports two types of API keys — **personal keys** and **workspace keys** — each with different billing and access behaviors. ## Key Types [#key-types] | | **Personal Keys** | **Workspace Keys** | | --------------- | ------------------------------------------ | --------------------------- | | **Billing** | Workspace payer for workspace-hosted usage | Workspace payer | | **Scope** | Across workspaces you have access to | Shared across the workspace | | **Managed by** | Each user individually | Workspace admins | | **Permissions** | Must be enabled at workspace level | Require admin permissions | Personal keys identify the user making a request; they do not select who pays. Hosted usage is billed to the workspace's organization or personal billing account and, for organizations, is attributed to the actor's member cap. Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used. ## Generating API Keys [#generating-api-keys] To generate a personal key, open **Account settings** → **Sim API keys**. Workspace administrators can create shared keys from **Workspace settings** → **Sim API keys**. API keys are only shown once when generated. Store your key securely — you will not be able to view it again. ## Using API Keys [#using-api-keys] Pass your API key in the `X-API-Key` header with every request: ```bash curl -X POST https://www.sim.ai/api/v2/workflows/{workflowId}/execute \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{"input": {}}' ``` ```typescript const response = await fetch( 'https://www.sim.ai/api/v2/workflows/{workflowId}/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.SIM_API_KEY!, }, body: JSON.stringify({ input: {} }), } ) ``` ```python import requests response = requests.post( "https://www.sim.ai/api/v2/workflows/{workflowId}/execute", headers={ "Content-Type": "application/json", "X-API-Key": os.environ["SIM_API_KEY"], }, json={"input": {}}, ) ``` ## Where Keys Are Used [#where-keys-are-used] API keys authenticate access to: * **Workflow execution** — run deployed workflows via the API * **Logs API** — query workflow execution logs and metrics * **MCP servers** — authenticate connections to deployed MCP servers * **SDKs** — the [Python](/api-reference/python) and [TypeScript](/api-reference/typescript) SDKs use API keys for all operations ## Security [#security] * Keys use the `sk-sim-` prefix and are encrypted at rest * Keys can be revoked at any time from the dashboard * Use environment variables to store keys — never hardcode them in source code * For browser-based applications, use a backend proxy to avoid exposing keys to the client Never expose your API key in client-side code. Use a server-side proxy to make authenticated requests on behalf of your frontend. --- # Getting Started (/api-reference/getting-started) ## Base URL [#base-url] All API requests are made to: ``` https://www.sim.ai ``` ## Quick Start [#quick-start] ### Get your API key [#get-your-api-key] Go to the Sim platform and navigate to **Settings**, then go to **Sim Keys** and click **Create**. See [Authentication](/api-reference/authentication) for details on key types. ### Find your workflow ID [#find-your-workflow-id] Open a workflow in the Sim editor. The workflow ID is in the URL: ``` https://www.sim.ai/workspace/{workspaceId}/w/{workflowId} ``` You can also use the [List Workflows](/api-reference/workflows/listWorkflows) endpoint to get all workflow IDs in a workspace. ### Deploy your workflow [#deploy-your-workflow] A workflow must be deployed before it can be executed via the API. Click the **Deploy** button in the editor toolbar, or use the dashboard to manage deployments. ### Make your first request [#make-your-first-request] ```bash curl -X POST https://www.sim.ai/api/v2/workflows/{workflowId}/execute \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{"input": {}}' ``` ```typescript const response = await fetch( `https://www.sim.ai/api/v2/workflows/${workflowId}/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.SIM_API_KEY!, }, body: JSON.stringify({ input: {} }), } ) const data = await response.json() console.log(data.data.output) ``` ```python import requests import os response = requests.post( f"https://www.sim.ai/api/v2/workflows/{workflow_id}/execute", headers={ "Content-Type": "application/json", "X-API-Key": os.environ["SIM_API_KEY"], }, json={"input": {}}, ) data = response.json() print(data["data"]["output"]) ``` ## Sync vs Async Execution [#sync-vs-async-execution] By default, workflow executions are **synchronous** — the API blocks until the workflow completes and returns the result directly. For long-running workflows, use **asynchronous execution** by passing `async: true`: ```bash curl -X POST https://www.sim.ai/api/v2/workflows/{workflowId}/execute \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{"input": {}, "async": true}' ``` Keyed callers can optionally provide `X-Run-Id: my-run-123` to choose the run ID. Run IDs cannot be reused; a duplicate returns `409`. This returns immediately with a `runId` and `statusUrl`: ```json { "data": { "runId": "c7a92e15-3f4b-4d8c-a1e6-9b0d5f2c8e74", "statusUrl": "https://www.sim.ai/api/v2/workflows/{workflowId}/runs/c7a92e15-3f4b-4d8c-a1e6-9b0d5f2c8e74" } } ``` Poll the run status endpoint until the status is terminal: ```bash curl https://www.sim.ai/api/v2/workflows/{workflowId}/runs/{runId}?includeOutput=true \ -H "X-API-Key: YOUR_API_KEY" ``` Execution status transitions follow: `queued` → `running` → `completed`, `failed`, `cancelled`, or `paused`. The `data.output` field is populated for completed executions when `includeOutput=true`. ## Response Format [#response-format] Successful v2 responses wrap the run resource in `data`: ```json { "data": { "runId": "c7a92e15-3f4b-4d8c-a1e6-9b0d5f2c8e74", "workflowId": "{workflowId}", "status": "completed", "output": { "result": "Hello, world!" }, "error": null, "durationMs": 842 } } ``` ## Error Handling [#error-handling] The API uses standard HTTP status codes. v2 errors include a stable code and human-readable message: ```json { "error": { "code": "NOT_FOUND", "message": "Workflow not found" } } ``` | Status | Meaning | What to do | | ------ | -------------------------- | --------------------------------------------------- | | `400` | Invalid request parameters | Check the `details` array for specific field errors | | `401` | Missing or invalid API key | Verify your `X-API-Key` header | | `403` | Access denied | Check you have permission for this resource | | `404` | Resource not found | Verify the ID exists and belongs to your workspace | | `429` | Rate limit exceeded | Wait for the duration in the `Retry-After` header | ### Unrecognized fields are rejected [#unrecognized-fields-are-rejected] Every v2 endpoint validates the request against its published schema — path parameters, query string, and body — and answers `400` for any field it does not declare. A misspelled parameter is an error rather than a silent no-op, so `?limt=20` fails instead of quietly returning an unbounded list. This holds for endpoints that declare no query parameters at all. Do not append tracking tags, cache busters, or other extra parameters to a v2 URL; send only what the endpoint documents. ```json { "error": { "code": "BAD_REQUEST", "message": "Invalid request", "details": [ { "code": "unrecognized_keys", "keys": ["limt"], "path": [], "message": "Unrecognized key: \"limt\"" } ] } } ``` Use [Get Billing Status](/api-reference/billing/getBillingStatus) to inspect current credit and storage usage. ## Rate Limits [#rate-limits] Rate limits depend on your subscription plan and apply separately to synchronous and asynchronous executions. When rate limited, the API returns a `429` response with a `Retry-After` header indicating how many seconds to wait before retrying. ## Pagination [#pagination] List endpoints (workflows, logs, audit logs) use **cursor-based pagination**: ```bash # First page curl "https://www.sim.ai/api/v2/logs?limit=20" \ -H "X-API-Key: YOUR_API_KEY" # Next page — use the nextCursor from the previous response curl "https://www.sim.ai/api/v2/logs?limit=20&cursor=abc123" \ -H "X-API-Key: YOUR_API_KEY" ``` The response includes a `nextCursor` field. When `nextCursor` is absent or `null`, you have reached the last page. --- # Python (/api-reference/python) The official Python SDK for Sim allows you to execute workflows programmatically from your Python applications using the official Python SDK. The Python SDK supports Python 3.8+ with async execution support, automatic rate limiting with exponential backoff, and usage tracking. ## Installation [#installation] Install the SDK using pip: ```bash pip install simstudio-sdk ``` ## Quick Start [#quick-start] Here's a simple example to get you started: ```python from simstudio import SimStudioClient # Initialize the client client = SimStudioClient( api_key="your-api-key-here", base_url="https://sim.ai" # optional, defaults to https://sim.ai ) # Execute a workflow try: result = client.execute_workflow("workflow-id") print("Workflow executed successfully:", result) except Exception as error: print("Workflow execution failed:", error) ``` ## API Reference [#api-reference] ### SimStudioClient [#simstudioclient] #### Constructor [#constructor] ```python SimStudioClient(api_key: str, base_url: str = "https://sim.ai") ``` **Parameters:** * `api_key` (str): Your Sim API key * `base_url` (str, optional): Base URL for the Sim API #### Methods [#methods] ##### execute\_workflow() [#execute_workflow] Execute a workflow with optional input data. ```python result = client.execute_workflow( "workflow-id", input={"message": "Hello, world!"}, timeout=30.0 # 30 seconds ) ``` **Parameters:** * `workflow_id` (str): The ID of the workflow to execute * `input` (dict, optional): Input data to pass to the workflow * `timeout` (float, optional): Timeout in seconds (default: 30.0) * `stream` (bool, optional): Enable streaming responses (default: False) * `selected_outputs` (list\[str], optional): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`) * `async_execution` (bool, optional): Execute asynchronously (default: False) * `execution_timeout_seconds` (int, optional): Optional server-side async execution cap from 1 to 604800 seconds. Requires `async_execution=True` and cannot extend the account policy. **Returns:** `WorkflowExecutionResult | AsyncExecutionResult` When `async_execution=True`, returns immediately with a `run_id` and `status_url` for polling. Otherwise, waits for completion. ##### get\_workflow\_status() [#get_workflow_status] Get the status of a workflow (deployment status, etc.). ```python status = client.get_workflow_status("workflow-id") print("Is deployed:", status.is_deployed) ``` **Parameters:** * `workflow_id` (str): The ID of the workflow **Returns:** `WorkflowStatus` ##### validate\_workflow() [#validate_workflow] Validate that a workflow is ready for execution. ```python is_ready = client.validate_workflow("workflow-id") if is_ready: # Workflow is deployed and ready pass ``` **Parameters:** * `workflow_id` (str): The ID of the workflow **Returns:** `bool` ##### get\_workflow\_run() [#get_workflow_run] Get the status and optional outputs of a workflow execution. ```python status = client.get_workflow_run("workflow-id", "run-id", include_output=True) print("Status:", status["status"]) # 'queued', 'running', 'completed', 'failed' if status["status"] == "completed": print("Output:", status["output"]) ``` **Parameters:** * `workflow_id` (str): The workflow ID * `run_id` (str): The run ID returned from async execution * `include_output` (bool, optional): Include the final output for completed executions * `selected_outputs` (list\[str], optional): Block output selectors to include **Returns:** `Dict[str, Any]` **Response fields:** * `runId` (str): The run ID * `workflowId` (str): The workflow ID * `status` (str): One of `'queued'`, `'pending'`, `'running'`, `'paused'`, `'completed'`, `'failed'`, `'cancelled'` * `startedAt` / `endedAt` (str): Execution timestamps * `durationMs` (int, optional): Duration in milliseconds * `output` (any, optional): The workflow output when requested for a completed execution * `blockOutputs` (dict, optional): Requested block outputs * `error` (dict, optional): Structured failure details with `code`, `message`, and optional `details` ##### get\_job\_status() [#get_job_status] Get the status of a job created through the legacy async execution endpoint. New integrations should use `get_workflow_run()` with the run ID instead. ```python status = client.get_job_status("legacy-job-id") ``` ##### execute\_with\_retry() [#execute_with_retry] Execute a workflow with automatic retry on rate limit errors using exponential backoff. ```python result = client.execute_with_retry( "workflow-id", input={"message": "Hello"}, timeout=30.0, max_retries=3, # Maximum number of retries initial_delay=1.0, # Initial delay in seconds max_delay=30.0, # Maximum delay in seconds backoff_multiplier=2.0 # Exponential backoff multiplier ) ``` **Parameters:** * `workflow_id` (str): The ID of the workflow to execute * `input` (dict, optional): Input data to pass to the workflow * `timeout` (float, optional): Timeout in seconds * `stream` (bool, optional): Enable streaming responses * `selected_outputs` (list, optional): Block outputs to stream * `async_execution` (bool, optional): Execute asynchronously * `max_retries` (int, optional): Maximum number of retries (default: 3) * `initial_delay` (float, optional): Initial delay in seconds (default: 1.0) * `max_delay` (float, optional): Maximum delay in seconds (default: 30.0) * `backoff_multiplier` (float, optional): Backoff multiplier (default: 2.0) **Returns:** `WorkflowExecutionResult | AsyncExecutionResult` The retry logic uses exponential backoff (1s → 2s → 4s → 8s...) with ±25% jitter to prevent thundering herd. If the API provides a `retry-after` header, it will be used instead. ##### get\_rate\_limit\_info() [#get_rate_limit_info] Get the current rate limit information from the last API response. ```python rate_limit_info = client.get_rate_limit_info() if rate_limit_info: print("Limit:", rate_limit_info.limit) print("Remaining:", rate_limit_info.remaining) print("Reset:", datetime.fromtimestamp(rate_limit_info.reset)) ``` **Returns:** `RateLimitInfo | None` ##### get\_usage\_limits() [#get_usage_limits] Get current usage limits and quota information for your account. ```python limits = client.get_usage_limits() print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"]) print("Async requests remaining:", limits.rate_limit["async"]["remaining"]) print("Current period cost:", limits.usage["currentPeriodCost"]) print("Plan:", limits.usage["plan"]) ``` **Returns:** `UsageLimits` **Response structure:** ```python { "success": bool, "rateLimit": { "sync": { "isLimited": bool, "limit": int, "remaining": int, "resetAt": str }, "async": { "isLimited": bool, "limit": int, "remaining": int, "resetAt": str }, "authType": str # 'api' or 'manual' }, "usage": { "currentPeriodCost": float, "limit": float, "plan": str # e.g., 'free', 'pro' } } ``` ##### set\_api\_key() [#set_api_key] Update the API key. ```python client.set_api_key("new-api-key") ``` ##### set\_base\_url() [#set_base_url] Update the base URL. ```python client.set_base_url("https://my-custom-domain.com") ``` ##### close() [#close] Close the underlying HTTP session. ```python client.close() ``` ## Data Classes [#data-classes] ### WorkflowExecutionResult [#workflowexecutionresult] ```python @dataclass class WorkflowExecutionResult: success: bool output: Optional[Any] = None error: Optional[str] = None logs: Optional[List[Any]] = None metadata: Optional[Dict[str, Any]] = None trace_spans: Optional[List[Any]] = None total_duration: Optional[float] = None status: Optional[str] = None ``` `success` is `True` only for the `completed` and `paused` statuses. `status` carries the server's terminal status verbatim, so a cancelled run (`success=False`, `error=None`) is distinguishable from a failed one. ### AsyncExecutionResult [#asyncexecutionresult] ```python @dataclass class AsyncExecutionResult: success: bool run_id: str status_url: str message: str = "" async_execution: bool = True ``` ### WorkflowStatus [#workflowstatus] ```python @dataclass class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None needs_redeployment: bool = False ``` ### RateLimitInfo [#ratelimitinfo] ```python @dataclass class RateLimitInfo: limit: int remaining: int reset: int retry_after: Optional[int] = None ``` ### UsageLimits [#usagelimits] ```python @dataclass class UsageLimits: success: bool rate_limit: Dict[str, Any] usage: Dict[str, Any] ``` ### SimStudioError [#simstudioerror] ```python class SimStudioError(Exception): def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None): super().__init__(message) self.code = code self.status = status ``` **Common error codes:** * `UNAUTHORIZED`: Invalid API key * `TIMEOUT`: Request timed out * `RATE_LIMIT_EXCEEDED`: Rate limit exceeded * `USAGE_LIMIT_EXCEEDED`: Usage limit exceeded * `EXECUTION_ERROR`: Workflow execution failed ## Examples [#examples] ### Basic Workflow Execution [#basic-workflow-execution] Set up the SimStudioClient with your API key. Check if the workflow is deployed and ready for execution. Run the workflow with your input data. Process the execution result and handle any errors. ```python import os from simstudio import SimStudioClient client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def run_workflow(): try: # Check if workflow is ready is_ready = client.validate_workflow("my-workflow-id") if not is_ready: raise Exception("Workflow is not deployed or ready") # Execute the workflow result = client.execute_workflow( "my-workflow-id", input={ "message": "Process this data", "user_id": "12345" } ) if result.success: print("Output:", result.output) print("Duration:", result.metadata.get("duration") if result.metadata else None) else: print("Workflow failed:", result.error) except Exception as error: print("Error:", error) run_workflow() ``` ### Error Handling [#error-handling] Handle different types of errors that may occur during workflow execution: ```python from simstudio import SimStudioClient, SimStudioError import os client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def execute_with_error_handling(): try: result = client.execute_workflow("workflow-id") return result except SimStudioError as error: if error.code == "UNAUTHORIZED": print("Invalid API key") elif error.code == "TIMEOUT": print("Workflow execution timed out") elif error.code == "USAGE_LIMIT_EXCEEDED": print("Usage limit exceeded") elif error.code == "INVALID_JSON": print("Invalid JSON in request body") else: print(f"Workflow error: {error}") raise except Exception as error: print(f"Unexpected error: {error}") raise ``` ### Context Manager Usage [#context-manager-usage] Use the client as a context manager to automatically handle resource cleanup: ```python from simstudio import SimStudioClient import os # Using context manager to automatically close the session with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client: result = client.execute_workflow("workflow-id") print("Result:", result) # Session is automatically closed here ``` ### Batch Workflow Execution [#batch-workflow-execution] Execute multiple workflows efficiently: ```python from simstudio import SimStudioClient import os client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def execute_workflows_batch(workflow_data_pairs): """Execute multiple workflows with different input data.""" results = [] for workflow_id, input_data in workflow_data_pairs: try: # Validate workflow before execution if not client.validate_workflow(workflow_id): print(f"Skipping {workflow_id}: not deployed") continue result = client.execute_workflow(workflow_id, input_data) results.append({ "workflow_id": workflow_id, "success": result.success, "output": result.output, "error": result.error }) except Exception as error: results.append({ "workflow_id": workflow_id, "success": False, "error": str(error) }) return results # Example usage workflows = [ ("workflow-1", {"type": "analysis", "data": "sample1"}), ("workflow-2", {"type": "processing", "data": "sample2"}), ] results = execute_workflows_batch(workflows) for result in results: print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}") ``` ### Async Workflow Execution [#async-workflow-execution] Execute workflows asynchronously for long-running tasks: ```python import os import time from simstudio import SimStudioClient client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def execute_async(): try: # Start async execution result = client.execute_workflow( "workflow-id", input={"data": "large dataset"}, async_execution=True # Execute asynchronously ) # Check if result is an async execution if hasattr(result, 'async_execution') and result.async_execution: print(f"Run ID: {result.run_id}") print(f"Status endpoint: {result.status_url}") # Poll for completion status = client.get_workflow_run( "workflow-id", result.run_id, include_output=True ) while status["status"] in ["queued", "pending", "running"]: print(f"Current status: {status['status']}") time.sleep(2) # Wait 2 seconds status = client.get_workflow_run( "workflow-id", result.run_id, include_output=True ) if status["status"] == "completed": print("Workflow completed!") print(f"Output: {status['output']}") print(f"Duration: {status['durationMs']}") else: print(f"Workflow failed: {status['error']}") except Exception as error: print(f"Error: {error}") execute_async() ``` ### Rate Limiting and Retry [#rate-limiting-and-retry] Handle rate limits automatically with exponential backoff: ```python import os from simstudio import SimStudioClient, SimStudioError client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def execute_with_retry_handling(): try: # Automatically retries on rate limit result = client.execute_with_retry( "workflow-id", input={"message": "Process this"}, max_retries=5, initial_delay=1.0, max_delay=60.0, backoff_multiplier=2.0 ) print(f"Success: {result}") except SimStudioError as error: if error.code == "RATE_LIMIT_EXCEEDED": print("Rate limit exceeded after all retries") # Check rate limit info rate_limit_info = client.get_rate_limit_info() if rate_limit_info: from datetime import datetime reset_time = datetime.fromtimestamp(rate_limit_info.reset) print(f"Rate limit resets at: {reset_time}") execute_with_retry_handling() ``` ### Usage Monitoring [#usage-monitoring] Monitor your account usage and limits: ```python import os from simstudio import SimStudioClient client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def check_usage(): try: limits = client.get_usage_limits() print("=== Rate Limits ===") print("Sync requests:") print(f" Limit: {limits.rate_limit['sync']['limit']}") print(f" Remaining: {limits.rate_limit['sync']['remaining']}") print(f" Resets at: {limits.rate_limit['sync']['resetAt']}") print(f" Is limited: {limits.rate_limit['sync']['isLimited']}") print("\nAsync requests:") print(f" Limit: {limits.rate_limit['async']['limit']}") print(f" Remaining: {limits.rate_limit['async']['remaining']}") print(f" Resets at: {limits.rate_limit['async']['resetAt']}") print(f" Is limited: {limits.rate_limit['async']['isLimited']}") print("\n=== Usage ===") print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}") print(f"Limit: ${limits.usage['limit']:.2f}") print(f"Plan: {limits.usage['plan']}") percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100 print(f"Usage: {percent_used:.1f}%") if percent_used > 80: print("⚠️ Warning: You are approaching your usage limit!") except Exception as error: print(f"Error checking usage: {error}") check_usage() ``` ### Streaming Workflow Execution [#streaming-workflow-execution] Execute workflows with real-time streaming responses: ```python from simstudio import SimStudioClient import os client = SimStudioClient(api_key=os.getenv("SIM_API_KEY")) def execute_with_streaming(): """Execute workflow with streaming enabled.""" try: # Enable streaming for specific block outputs result = client.execute_workflow( "workflow-id", input={"message": "Count to five"}, stream=True, selected_outputs=["agent1.content"] # Use blockName.attribute format ) print("Workflow result:", result) except Exception as error: print("Error:", error) execute_with_streaming() ``` The streaming response follows the Server-Sent Events (SSE) format: ``` data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"} data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"} data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}} data: [DONE] ``` **Flask Streaming Example:** ```python from flask import Flask, Response, stream_with_context import requests import json import os app = Flask(__name__) @app.route('/stream-workflow') def stream_workflow(): """Stream workflow execution to the client.""" def generate(): response = requests.post( 'https://sim.ai/api/v2/workflows/WORKFLOW_ID/execute', headers={ 'Content-Type': 'application/json', 'X-API-Key': os.getenv('SIM_API_KEY') }, json={ 'input': {'message': 'Generate a story'}, 'stream': True, 'selectedOutputs': ['agent1.content'] }, stream=True ) for line in response.iter_lines(): if line: decoded_line = line.decode('utf-8') if decoded_line.startswith('data: '): data = decoded_line[6:] # Remove 'data: ' prefix if data == '[DONE]': break try: parsed = json.loads(data) if 'chunk' in parsed: yield f"data: {json.dumps(parsed)}\n\n" elif parsed.get('event') == 'done': yield f"data: {json.dumps(parsed)}\n\n" print("Execution complete:", parsed.get('metadata')) except json.JSONDecodeError: pass return Response( stream_with_context(generate()), mimetype='text/event-stream' ) if __name__ == '__main__': app.run(debug=True) ``` ### Environment Configuration [#environment-configuration] Configure the client using environment variables: ```python import os from simstudio import SimStudioClient # Development configuration client = SimStudioClient( api_key=os.getenv("SIM_API_KEY") base_url=os.getenv("SIM_BASE_URL", "https://sim.ai") ) ``` ```python import os from simstudio import SimStudioClient # Production configuration with error handling api_key = os.getenv("SIM_API_KEY") if not api_key: raise ValueError("SIM_API_KEY environment variable is required") client = SimStudioClient( api_key=api_key, base_url=os.getenv("SIM_BASE_URL", "https://sim.ai") ) ``` ## Getting Your API Key [#getting-your-api-key] Navigate to [Sim](https://sim.ai) and log in to your account. Navigate to the workflow you want to execute programmatically. Click on "Deploy" to deploy your workflow if it hasn't been deployed yet. During the deployment process, select or create an API key. Copy the API key to use in your Python application. ## Requirements [#requirements] * Python 3.8+ * requests >= 2.25.0 ## License [#license] Apache-2.0 --- # TypeScript (/api-reference/typescript) The official TypeScript/JavaScript SDK for Sim provides full type safety and supports both Node.js and browser environments, allowing you to execute workflows programmatically from your Node.js applications, web applications, and other JavaScript environments. The TypeScript SDK provides full type safety, async execution support, automatic rate limiting with exponential backoff, and usage tracking. ## Installation [#installation] Install the SDK using your preferred package manager: ```bash npm install simstudio-ts-sdk ``` ```bash yarn add simstudio-ts-sdk ``` ```bash bun add simstudio-ts-sdk ``` ## Quick Start [#quick-start] Here's a simple example to get you started: ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; // Initialize the client const client = new SimStudioClient({ apiKey: 'your-api-key-here', baseUrl: 'https://sim.ai' // optional, defaults to https://sim.ai }); // Execute a workflow try { const result = await client.executeWorkflow('workflow-id'); console.log('Workflow executed successfully:', result); } catch (error) { console.error('Workflow execution failed:', error); } ``` ## API Reference [#api-reference] ### SimStudioClient [#simstudioclient] #### Constructor [#constructor] ```typescript new SimStudioClient(config: SimStudioConfig) ``` **Configuration:** * `config.apiKey` (string): Your Sim API key * `config.baseUrl` (string, optional): Base URL for the Sim API (defaults to `https://sim.ai`) #### Methods [#methods] ##### executeWorkflow() [#executeworkflow] Execute a workflow with optional input data. ```typescript const result = await client.executeWorkflow('workflow-id', { message: 'Hello, world!' }, { timeout: 30000 // 30 seconds }); ``` **Parameters:** * `workflowId` (string): The ID of the workflow to execute * `input` (any, optional): Input data to pass to the workflow * `options` (ExecutionOptions, optional): * `timeout` (number): Timeout in milliseconds (default: 30000) * `stream` (boolean): Enable streaming responses (default: false) * `selectedOutputs` (string\[]): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`) * `async` (boolean): Execute asynchronously (default: false) * `executionTimeoutSeconds` (number): Optional server-side async execution cap from 1 to 604800 seconds. Requires `async: true` and cannot extend the account policy. **Returns:** `Promise` When `async: true`, returns immediately with a `runId` and `statusUrl` for polling. Otherwise, waits for completion. ##### getWorkflowStatus() [#getworkflowstatus] Get the status of a workflow (deployment status, etc.). ```typescript const status = await client.getWorkflowStatus('workflow-id'); console.log('Is deployed:', status.isDeployed); ``` **Parameters:** * `workflowId` (string): The ID of the workflow **Returns:** `Promise` ##### validateWorkflow() [#validateworkflow] Validate that a workflow is ready for execution. ```typescript const isReady = await client.validateWorkflow('workflow-id'); if (isReady) { // Workflow is deployed and ready } ``` **Parameters:** * `workflowId` (string): The ID of the workflow **Returns:** `Promise` ##### getWorkflowRun() [#getworkflowrun] Get the status and optional outputs of a workflow run. ```typescript const status = await client.getWorkflowRun('workflow-id', 'run-id', { includeOutput: true }); console.log('Status:', status.status); // 'queued', 'running', 'completed', 'failed' if (status.status === 'completed') { console.log('Output:', status.output); } ``` **Parameters:** * `workflowId` (string): The workflow ID * `runId` (string): The run ID returned from async execution * `options.includeOutput` (boolean, optional): Include the final output for completed executions * `options.selectedOutputs` (string\[], optional): Block output selectors to include **Returns:** `Promise` **Response fields:** * `runId` (string): The run ID * `workflowId` (string): The workflow ID * `status` (string): One of `'queued'`, `'pending'`, `'running'`, `'paused'`, `'completed'`, `'failed'`, `'cancelled'` * `startedAt` / `endedAt` (string): Execution timestamps * `durationMs` (number, nullable): Duration in milliseconds * `output` (any, nullable): The workflow output when requested for a completed execution * `blockOutputs` (object, nullable): Requested block outputs * `error` (object, nullable): Structured failure details with `code`, `message`, and optional `details` ##### getJobStatus() [#getjobstatus] Get the status of a job created through the legacy async execution endpoint. New integrations should use `getWorkflowRun()` with the run ID instead. ```typescript const status = await client.getJobStatus('legacy-job-id'); ``` ##### executeWithRetry() [#executewithretry] Execute a workflow with automatic retry on rate limit errors using exponential backoff. ```typescript const result = await client.executeWithRetry('workflow-id', { message: 'Hello' }, { timeout: 30000 }, { maxRetries: 3, // Maximum number of retries initialDelay: 1000, // Initial delay in ms (1 second) maxDelay: 30000, // Maximum delay in ms (30 seconds) backoffMultiplier: 2 // Exponential backoff multiplier }); ``` **Parameters:** * `workflowId` (string): The ID of the workflow to execute * `input` (any, optional): Input data to pass to the workflow * `options` (ExecutionOptions, optional): Same as `executeWorkflow()` * `retryOptions` (RetryOptions, optional): * `maxRetries` (number): Maximum number of retries (default: 3) * `initialDelay` (number): Initial delay in ms (default: 1000) * `maxDelay` (number): Maximum delay in ms (default: 30000) * `backoffMultiplier` (number): Backoff multiplier (default: 2) **Returns:** `Promise` The retry logic uses exponential backoff (1s → 2s → 4s → 8s...) with ±25% jitter to prevent thundering herd. If the API provides a `retry-after` header, it will be used instead. ##### getRateLimitInfo() [#getratelimitinfo] Get the current rate limit information from the last API response. ```typescript const rateLimitInfo = client.getRateLimitInfo(); if (rateLimitInfo) { console.log('Limit:', rateLimitInfo.limit); console.log('Remaining:', rateLimitInfo.remaining); console.log('Reset:', new Date(rateLimitInfo.reset * 1000)); } ``` **Returns:** `RateLimitInfo | null` ##### getUsageLimits() [#getusagelimits] Get current usage limits and quota information for your account. ```typescript const limits = await client.getUsageLimits(); console.log('Sync requests remaining:', limits.rateLimit.sync.remaining); console.log('Async requests remaining:', limits.rateLimit.async.remaining); console.log('Current period cost:', limits.usage.currentPeriodCost); console.log('Plan:', limits.usage.plan); ``` **Returns:** `Promise` **Response structure:** ```typescript { success: boolean rateLimit: { sync: { isLimited: boolean limit: number remaining: number resetAt: string } async: { isLimited: boolean limit: number remaining: number resetAt: string } authType: string // 'api' or 'manual' } usage: { currentPeriodCost: number limit: number plan: string // e.g., 'free', 'pro' } } ``` ##### setApiKey() [#setapikey] Update the API key. ```typescript client.setApiKey('new-api-key'); ``` ##### setBaseUrl() [#setbaseurl] Update the base URL. ```typescript client.setBaseUrl('https://my-custom-domain.com'); ``` ## Types [#types] ### WorkflowExecutionResult [#workflowexecutionresult] ```typescript interface WorkflowExecutionResult { success: boolean; output?: any; error?: string; logs?: any[]; metadata?: { duration?: number; runId?: string; [key: string]: any; }; traceSpans?: any[]; totalDuration?: number; } ``` ### AsyncExecutionResult [#asyncexecutionresult] ```typescript interface AsyncExecutionResult { success: boolean; runId: string; statusUrl: string; message: string; async: true; } ``` ### WorkflowStatus [#workflowstatus] ```typescript interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; needsRedeployment: boolean; } ``` ### RateLimitInfo [#ratelimitinfo] ```typescript interface RateLimitInfo { limit: number; remaining: number; reset: number; retryAfter?: number; } ``` ### UsageLimits [#usagelimits] ```typescript interface UsageLimits { success: boolean; rateLimit: { sync: { isLimited: boolean; limit: number; remaining: number; resetAt: string; }; async: { isLimited: boolean; limit: number; remaining: number; resetAt: string; }; authType: string; }; usage: { currentPeriodCost: number; limit: number; plan: string; }; } ``` ### SimStudioError [#simstudioerror] ```typescript class SimStudioError extends Error { code?: string; status?: number; } ``` **Common error codes:** * `UNAUTHORIZED`: Invalid API key * `TIMEOUT`: Request timed out * `RATE_LIMIT_EXCEEDED`: Rate limit exceeded * `USAGE_LIMIT_EXCEEDED`: Usage limit exceeded * `EXECUTION_ERROR`: Workflow execution failed ## Examples [#examples] ### Basic Workflow Execution [#basic-workflow-execution] Set up the SimStudioClient with your API key. Check if the workflow is deployed and ready for execution. Run the workflow with your input data. Process the execution result and handle any errors. ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); async function runWorkflow() { try { // Check if workflow is ready const isReady = await client.validateWorkflow('my-workflow-id'); if (!isReady) { throw new Error('Workflow is not deployed or ready'); } // Execute the workflow const result = await client.executeWorkflow('my-workflow-id', { message: 'Process this data', userId: '12345' }); if (result.success) { console.log('Output:', result.output); console.log('Duration:', result.metadata?.duration); } else { console.error('Workflow failed:', result.error); } } catch (error) { console.error('Error:', error); } } runWorkflow(); ``` ### Error Handling [#error-handling] Handle different types of errors that may occur during workflow execution: ```typescript import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); async function executeWithErrorHandling() { try { const result = await client.executeWorkflow('workflow-id'); return result; } catch (error) { if (error instanceof SimStudioError) { switch (error.code) { case 'UNAUTHORIZED': console.error('Invalid API key'); break; case 'TIMEOUT': console.error('Workflow execution timed out'); break; case 'USAGE_LIMIT_EXCEEDED': console.error('Usage limit exceeded'); break; case 'INVALID_JSON': console.error('Invalid JSON in request body'); break; default: console.error('Workflow error:', error.message); } } else { console.error('Unexpected error:', error); } throw error; } } ``` ### Environment Configuration [#environment-configuration] Configure the client using environment variables: ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; // Development configuration const apiKey = process.env.SIM_API_KEY; if (!apiKey) { throw new Error('SIM_API_KEY environment variable is required'); } const client = new SimStudioClient({ apiKey, baseUrl: process.env.SIM_BASE_URL // optional }); ``` ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; // Production configuration with validation const apiKey = process.env.SIM_API_KEY; if (!apiKey) { throw new Error('SIM_API_KEY environment variable is required'); } const client = new SimStudioClient({ apiKey, baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai' }); ``` ### Node.js Express Integration [#nodejs-express-integration] Integrate with an Express.js server: ```typescript import express from 'express'; import { SimStudioClient } from 'simstudio-ts-sdk'; const app = express(); const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); app.use(express.json()); app.post('/execute-workflow', async (req, res) => { try { const { workflowId, input } = req.body; const result = await client.executeWorkflow(workflowId, input, { timeout: 60000 }); res.json({ success: true, data: result }); } catch (error) { console.error('Workflow execution error:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }); } }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` ### Next.js API Route [#nextjs-api-route] Use with Next.js API routes: ```typescript // pages/api/workflow.ts or app/api/workflow/route.ts import { NextApiRequest, NextApiResponse } from 'next'; import { SimStudioClient } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } try { const { workflowId, input } = req.body; const result = await client.executeWorkflow(workflowId, input, { timeout: 30000 }); res.status(200).json(result); } catch (error) { console.error('Error executing workflow:', error); res.status(500).json({ error: 'Failed to execute workflow' }); } } ``` ### Browser Usage [#browser-usage] Use in the browser (with proper CORS configuration): ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; // Note: In production, use a proxy server to avoid exposing API keys const client = new SimStudioClient({ apiKey: 'your-public-api-key', // Use with caution in browser baseUrl: 'https://sim.ai' }); async function executeClientSideWorkflow() { try { const result = await client.executeWorkflow('workflow-id', { userInput: 'Hello from browser' }); console.log('Workflow result:', result); // Update UI with result document.getElementById('result')!.textContent = JSON.stringify(result.output, null, 2); } catch (error) { console.error('Error:', error); } } ``` ### File Upload [#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' } ``` ```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', { documents: files, // Must match your workflow's "files" field name instructions: 'Analyze these documents' }); console.log('Result:', result); } ``` ```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', { documents: [file], // Must match your workflow's "files" field name query: 'Summarize this document' }); ``` When using the SDK in the browser, be careful not to expose sensitive API keys. Consider using a backend proxy or public API keys with limited permissions. ### React Hook Example [#react-hook-example] Create a custom React hook for workflow execution: ```typescript import { useState, useCallback } from 'react'; import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); interface UseWorkflowResult { result: WorkflowExecutionResult | null; loading: boolean; error: Error | null; executeWorkflow: (workflowId: string, input?: any) => Promise; } export function useWorkflow(): UseWorkflowResult { const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const executeWorkflow = useCallback(async (workflowId: string, input?: any) => { setLoading(true); setError(null); setResult(null); try { const workflowResult = await client.executeWorkflow(workflowId, input, { timeout: 30000 }); setResult(workflowResult); } catch (err) { setError(err instanceof Error ? err : new Error('Unknown error')); } finally { setLoading(false); } }, []); return { result, loading, error, executeWorkflow }; } // Usage in component function WorkflowComponent() { const { result, loading, error, executeWorkflow } = useWorkflow(); const handleExecute = () => { executeWorkflow('my-workflow-id', { message: 'Hello from React!' }); }; return (
{error &&
Error: {error.message}
} {result && (

Result:

{JSON.stringify(result, null, 2)}
)}
); } ``` ### Async Workflow Execution [#async-workflow-execution] Execute workflows asynchronously for long-running tasks: ```typescript import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); async function executeAsync() { try { // Start async execution const result = await client.executeWorkflow('workflow-id', { data: 'large dataset' }, { async: true // Execute asynchronously }); // Check if result is an async execution if ('async' in result && result.async) { console.log('Run ID:', result.runId); console.log('Status endpoint:', result.statusUrl); // Poll for completion let status = await client.getWorkflowRun('workflow-id', result.runId, { includeOutput: true }); while (status.status === 'queued' || status.status === 'pending' || status.status === 'running') { console.log('Current status:', status.status); await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds status = await client.getWorkflowRun('workflow-id', result.runId, { includeOutput: true }); } if (status.status === 'completed') { console.log('Workflow completed!'); console.log('Output:', status.output); console.log('Duration:', status.durationMs); } else { console.error('Workflow failed:', status.error); } } } catch (error) { console.error('Error:', error); } } executeAsync(); ``` ### Rate Limiting and Retry [#rate-limiting-and-retry] Handle rate limits automatically with exponential backoff: ```typescript import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); async function executeWithRetryHandling() { try { // Automatically retries on rate limit const result = await client.executeWithRetry('workflow-id', { message: 'Process this' }, {}, { maxRetries: 5, initialDelay: 1000, maxDelay: 60000, backoffMultiplier: 2 }); console.log('Success:', result); } catch (error) { if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') { console.error('Rate limit exceeded after all retries'); // Check rate limit info const rateLimitInfo = client.getRateLimitInfo(); if (rateLimitInfo) { console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000)); } } } } ``` ### Usage Monitoring [#usage-monitoring] Monitor your account usage and limits: ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); async function checkUsage() { try { const limits = await client.getUsageLimits(); console.log('=== Rate Limits ==='); console.log('Sync requests:'); console.log(' Limit:', limits.rateLimit.sync.limit); console.log(' Remaining:', limits.rateLimit.sync.remaining); console.log(' Resets at:', limits.rateLimit.sync.resetAt); console.log(' Is limited:', limits.rateLimit.sync.isLimited); console.log('\nAsync requests:'); console.log(' Limit:', limits.rateLimit.async.limit); console.log(' Remaining:', limits.rateLimit.async.remaining); console.log(' Resets at:', limits.rateLimit.async.resetAt); console.log(' Is limited:', limits.rateLimit.async.isLimited); console.log('\n=== Usage ==='); console.log('Current period cost: $' + limits.usage.currentPeriodCost.toFixed(2)); console.log('Limit: $' + limits.usage.limit.toFixed(2)); console.log('Plan:', limits.usage.plan); const percentUsed = (limits.usage.currentPeriodCost / limits.usage.limit) * 100; console.log('Usage: ' + percentUsed.toFixed(1) + '%'); if (percentUsed > 80) { console.warn('⚠️ Warning: You are approaching your usage limit!'); } } catch (error) { console.error('Error checking usage:', error); } } checkUsage(); ``` ### Streaming Workflow Execution [#streaming-workflow-execution] Execute workflows with real-time streaming responses: ```typescript import { SimStudioClient } from 'simstudio-ts-sdk'; const client = new SimStudioClient({ apiKey: process.env.SIM_API_KEY! }); async function executeWithStreaming() { try { // Enable streaming for specific block outputs const result = await client.executeWorkflow('workflow-id', { message: 'Count to five' }, { stream: true, selectedOutputs: ['agent1.content'] // Use blockName.attribute format }); console.log('Workflow result:', result); } catch (error) { console.error('Error:', error); } } ``` The streaming response follows the Server-Sent Events (SSE) format: ``` data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"} data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"} data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}} data: [DONE] ``` **React Streaming Example:** ```typescript import { useState, useEffect } from 'react'; function StreamingWorkflow() { const [output, setOutput] = useState(''); const [loading, setLoading] = useState(false); const executeStreaming = async () => { setLoading(true); setOutput(''); // IMPORTANT: Make this API call from your backend server, not the browser // Never expose your API key in client-side code const response = await fetch('https://sim.ai/api/v2/workflows/WORKFLOW_ID/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.SIM_API_KEY! // Server-side environment variable only }, body: JSON.stringify({ input: { message: 'Generate a story' }, stream: true, selectedOutputs: ['agent1.content'] }) }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); while (reader) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { setLoading(false); break; } try { const parsed = JSON.parse(data); if (parsed.chunk) { setOutput(prev => prev + parsed.chunk); } else if (parsed.event === 'done') { console.log('Execution complete:', parsed.metadata); } } catch (e) { // Skip invalid JSON } } } } }; return (
{output}
); } ``` ## Getting Your API Key [#getting-your-api-key] Navigate to [Sim](https://sim.ai) and log in to your account. Navigate to the workflow you want to execute programmatically. Click on "Deploy" to deploy your workflow if it hasn't been deployed yet. During the deployment process, select or create an API key. Copy the API key to use in your TypeScript/JavaScript application. ## Requirements [#requirements] * Node.js 16+ * TypeScript 5.0+ (for TypeScript projects) ## License [#license] Apache-2.0 --- # Files & documents (/chat/files)