A unified interface for performing actions on SaaS tools through AI-friendly APIs.
The StackOne AI SDK provides the StackOneToolSet class, which fetches tools dynamically from StackOne's MCP (Model Context Protocol) endpoint. This ensures you always have access to the latest tool definitions.
# Using npm
npm install @stackone/ai zod
# Using yarn
yarn add @stackone/ai zod
# Using pnpm
pnpm add @stackone/ai zod
# Using bun
bun add @stackone/ai zodNote:
zodis a peer dependency required for AI SDK integrations and internal schema validation. Version>=3.25.0 <5is supported.
import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();constemployeeTool=tools.getTool('workday_list_workers');constemployees=awaitemployeeTool.execute();Set the STACKONE_API_KEY environment variable:
export STACKONE_API_KEY=<your-api-key>or load from a .env file using your preferred environment variable library.
StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels:
import{StackOneToolSet}from'@stackone/ai';// Simplest: set STACKONE_ACCOUNT_ID environment variableconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();// Explicit single accountconstexplicitToolset=newStackOneToolSet({accountId: 'your-workday-account'});constexplicitTools=awaitexplicitToolset.fetchTools();// Multiple accounts - returns tools from both integrationsconstmultiAccountToolset=newStackOneToolSet();constallTools=awaitmultiAccountToolset.fetchTools({accountIds: ['workday-account-123','hibob-account-456'],});// Filter to specific integration when using multiple accountsconstworkdayOnly=awaitmultiAccountToolset.fetchTools({accountIds: ['workday-account-123','hibob-account-456'],actions: ['workday_*'],// Only Workday tools});// Set directly on a tool instancetools.setAccountId('direct-account-id');constcurrentAccountId=tools.getAccountId();// Get the current account IDThe StackOneToolSet makes it super easy to use StackOne APIs as tools in your AI applications.
With OpenAI Chat Completions API
npm install @stackone/ai openai # or: yarn/pnpm/bun addimport{OpenAI}from'openai';import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();awaitopenai.chat.completions.create({model: 'gpt-5.1',messages: [{role: 'system',content: 'You are a helpful HR assistant using Workday.',},{role: 'user',content: 'Create a time-off request for employee id cxIQ5764hj2',},],tools: tools.toOpenAI(),});With OpenAI Responses API
npm install @stackone/ai openai # or: yarn/pnpm/bun addimportOpenAIfrom'openai';import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();constopenai=newOpenAI();awaitopenai.responses.create({model: 'gpt-5.1',instructions: 'You are a helpful HR assistant.',input: 'What is the phone number for employee c28xIQ?',tools: tools.toOpenAIResponses(),});With Anthropic Claude
npm install @stackone/ai @anthropic-ai/sdk # or: yarn/pnpm/bun addimportAnthropicfrom'@anthropic-ai/sdk';import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();constanthropic=newAnthropic();awaitanthropic.messages.create({model: 'claude-haiku-4-5-20241022',max_tokens: 1024,system: 'You are a helpful HR assistant.',messages: [{role: 'user',content: 'What is the phone number for employee c28xIQ?',},],tools: tools.toAnthropic(),});With AI SDK by Vercel
npm install @stackone/ai ai @ai-sdk/openai # or: yarn/pnpm/bun addSupports AI SDK v5–v7 (
ai>=5.0.108 <8.0.0). AI SDK v7 requires Node.js 22+.
import{openai}from'@ai-sdk/openai';import{generateText,stepCountIs}from'ai';import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();awaitgenerateText({model: openai('gpt-5.1'),tools: awaittools.toAISDK(),stopWhen: stepCountIs(3),});With Claude Agent SDK
npm install @stackone/ai @anthropic-ai/claude-agent-sdk zod # or: yarn/pnpm/bun addimport{query}from'@anthropic-ai/claude-agent-sdk';import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();// Fetch tools and convert to Claude Agent SDK formatconsttools=awaittoolset.fetchTools();constmcpServer=awaittools.toClaudeAgentSdk();// Use with Claude Agent SDK queryconstresult=query({prompt: 'Get the employee with id: abc123',options: {model: 'claude-sonnet-4-5-20250929',mcpServers: {'stackone-tools': mcpServer},tools: [],// Disable built-in toolsmaxTurns: 3,},});forawait(constmessageofresult){// Process streaming messages}You can filter tools by account IDs, providers, and action patterns:
// Filter by account IDstoolset.setAccounts(['account-123','account-456']);consttools=awaittoolset.fetchTools();// ORconsttools=awaittoolset.fetchTools({accountIds: ['account-123','account-456'],});// Filter by providersconsttools=awaittoolset.fetchTools({providers: ['hibob','workday']});// Filter by actions with exact matchconsttools=awaittoolset.fetchTools({actions: ['hibob_list_employees','hibob_create_employees'],});// Filter by actions with glob patternsconsttools=awaittoolset.fetchTools({actions: ['*_list_employees']});// Combine multiple filtersconsttools=awaittoolset.fetchTools({accountIds: ['account-123'],providers: ['hibob'],actions: ['*_list_*'],});This is especially useful when you want to:
- Limit tools to specific linked accounts
- Focus on specific HR/CRM/ATS providers
- Get only certain types of operations (e.g., all "list" operations)
Search for tools using natural language queries. Works with both semantic (cloud) and local BM25+TF-IDF search.
import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();constsearchTool=toolset.getSearchTool();// Search for relevant tools — returns a Tools collectionconsttools=awaitsearchTool.search('manage employees',{topK: 5});// Execute a discovered tool directlyconstlistTool=tools.getTool('workday_list_workers');constresult=awaitlistTool.execute({query: {limit: 10}});Discover tools using natural language instead of exact names. Queries like "onboard new hire" resolve to the right actions even when the tool is called workday_create_employee.
import{StackOneToolSet}from'@stackone/ai';// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environmentconsttoolset=newStackOneToolSet();// Search by intent — returns Tools collection ready for any frameworkconsttools=awaittoolset.searchTools('manage employee records',{topK: 5});constopenAITools=tools.toOpenAI();// Lightweight: inspect results without fetching full tool definitionsconstresults=awaittoolset.searchActionNames('time off requests',{topK: 5});Control which search backend searchTools() uses via the search option:
// 'auto' (default) — tries semantic search first, falls back to localconsttools=awaittoolset.searchTools('manage employees',{search: 'auto'});// 'semantic' — semantic API only, throws if unavailableconsttools=awaittoolset.searchTools('manage employees',{search: 'semantic'});// 'local' — local BM25+TF-IDF only, no semantic API callconsttools=awaittoolset.searchTools('manage employees',{search: 'local'});Results are automatically scoped to connectors in your linked accounts. See Search Tools Example for SearchTool (getSearchTool) integration, AI SDK, and agent loop patterns.
import{StackOneToolSet}from'@stackone/ai';consttoolset=newStackOneToolSet({baseUrl: 'https://api.example-dev.com'});The SDK includes built-in prompt injection protection via StackOne Defender. It runs on every tool call result before the content reaches your LLM, detecting and sanitizing injection attacks hidden in external data (emails, documents, CRM notes, etc.).
By default, the SDK defers to your project's dashboard defender setting. Pass an explicit defender config to override the project setting per toolset.
defender option | Effective behavior |
|---|---|
| omitted (default) | Project dashboard setting controls — SDK adds nothing |
{ useProjectSettings: true } | Same as omitting; explicit, self-documenting form |
{ enabled, blockHighRisk, ... } | SDK-level config wins, overrides the project setting |
null | Defender forcibly disabled, overrides the project setting |
When passing an explicit object, missing fields fall back to DEFAULT_DEFENDER_CONFIG (exported from @stackone/ai): enabled: true, blockHighRisk: false, both tiers on.
import{StackOneToolSet,DEFAULT_DEFENDER_CONFIG}from'@stackone/ai';// Default — defer to project dashboard settingconsttoolset=newStackOneToolSet({apiKey: '...'});// Same as default, explicit formconsttoolset=newStackOneToolSet({apiKey: '...',defender: {useProjectSettings: true},});// Explicitly disabled — overrides any project settingconsttoolset=newStackOneToolSet({apiKey: '...',defender: null,});// Opt in with safe defaults, but block on HIGH/CRITICAL — overrides project settingconsttoolset=newStackOneToolSet({apiKey: '...',defender: { ...DEFAULT_DEFENDER_CONFIG,blockHighRisk: true},});// Fully explicit SDK-level configconsttoolset=newStackOneToolSet({apiKey: '...',defender: {enabled: true,blockHighRisk: true,// throw on HIGH or CRITICAL riskuseTier1Classification: true,// pattern-based (regex, role markers)useTier2Classification: true,// ML-based (ONNX model)},});Use the defenderMode getter to check how a toolset will behave at runtime:
consttoolset=newStackOneToolSet({apiKey: '...',defender: null});toolset.defenderMode;// 'disabled' | 'explicit' | 'project'When the SDK overrides the project dashboard (mode disabled or explicit), it emits a yellow console.warn line once per process per distinct override shape so the override is visible at runtime without spamming logs. Pass NO_COLOR=1 to suppress color, or FORCE_COLOR=1 to force it when piping output. The project mode is silent.
Defender assigns a risk level to each scanned result:
| Level | Meaning |
|---|---|
low | No threats detected |
medium | Suspicious patterns detected, role markers stripped |
high | Injection patterns found, content redacted |
critical | Severe injection attempt with multiple indicators |
When blockHighRisk: false (default), high and critical results are annotated and returned — the LLM sees the sanitized content. When blockHighRisk: true, those results are blocked entirely.
For more detail on how the detection pipeline works, see the @stackone/defender package.
You can use the dryRun option to return the api arguments from a tool call without making the actual api call:
import{StackOneToolSet}from'@stackone/ai';// Initialize the toolsetconsttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();constemployeeTool=tools.getTool('workday_list_workers');// Use dryRun to see the request detailsconstdryRunResult=awaitemployeeTool.execute({query: {limit: 5}},{dryRun: true});console.log(dryRunResult);// {// url: "https://api.stackone.com/actions/rpc",// method: "POST",// headers: { ... },// body: "...",// mappedParams: { ... }// }The dryRun option returns an object containing:
url: The full URL with query parametersmethod: The HTTP methodheaders: The request headersbody: The request bodymappedParams: The parameters after mapping and derivation
Actions that download a file (for example googledrive_unified_download_file, documents_download_file, or any *_unified_download_file) return raw bytes plus metadata instead of parsed JSON. The SDK decides from the response Content-Type: JSON media types (application/json and +json suffixes) are parsed as usual, and anything else is treated as a file download.
Use the exported isBinaryDownloadResult type guard to narrow the result — no casts needed:
import{writeFileSync}from'node:fs';import{isBinaryDownloadResult,StackOneToolSet}from'@stackone/ai';consttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools({actions: ['googledrive_*']});constdownload=tools.getTool('googledrive_unified_download_file');constresult=awaitdownload.execute({id: 'file-id'});if(isBinaryDownloadResult(result)){// result.content is a Buffer; result.fileName is string | nullwriteFileSync(result.fileName??'download.bin',result.content);}The download result (typed BinaryDownloadResult) contains:
content:Buffer— the raw file bytes (not aJsonValue; see note)contentType:string— the file's MIME type (e.g.application/pdf), orapplication/octet-streamstatusCode:number— HTTP status of the download responseheaders:Record<string, string>— the response headersfileName:string | null— filename fromContent-Disposition(RFC 5987filename*aware), ornull
Note:
contentis a rawBuffer, not aJsonValue.JSON.stringifyturns it into a{ type: 'Buffer', data: [...] }byte array (not the file, and potentially huge), so if you forward tool results to an LLM — or anything that re-serializes to JSON — strip or transform thecontentkey first (for example, base64-encode it on the LLM-facing path).
The StackOne AI SDK includes a built-in feedback collection tool (tool_feedback) that allows users to provide feedback on their experience with StackOne tools. This tool is automatically included when using fetchTools() and helps improve the SDK based on user input.
The feedback tool:
- Requires explicit user consent before submitting feedback
- Collects user feedback about their experience with StackOne tools
- Tracks tool usage by recording which tools were used
- Submits to StackOne via the
/ai/tool-feedbackendpoint - Uses the same API key as other SDK operations for authentication
The feedback tool is automatically available when using StackOneToolSet:
import{StackOneToolSet}from'@stackone/ai';consttoolset=newStackOneToolSet();consttools=awaittoolset.fetchTools();// The feedback tool is automatically includedconstfeedbackTool=tools.getTool('tool_feedback');// Use with AI agents - they will ask for user consent firstconstopenAITools=tools.toOpenAI();// orconstaiSdkTools=awaittools.toAISDK();You can also use the feedback tool directly:
// Get the feedback toolconstfeedbackTool=tools.getTool('tool_feedback');// Submit feedback (after getting user consent)constresult=awaitfeedbackTool.execute({feedback: 'The tools worked great! Very easy to use.',account_id: 'acc_123456',tool_names: ['workday_list_workers','workday_create_time_off_request'],});The feedback tool supports both single and multiple account IDs. When you provide an array of account IDs, the feedback will be sent to each account individually:
// Single account ID (string)awaitfeedbackTool.execute({feedback: 'The tools worked great! Very easy to use.',account_id: 'acc_123456',tool_names: ['workday_list_workers','workday_create_time_off_request'],});// Multiple account IDs (array)awaitfeedbackTool.execute({feedback: 'The tools worked great! Very easy to use.',account_id: ['acc_123456','acc_789012'],tool_names: ['workday_list_workers','workday_create_time_off_request'],});Response Format: When using multiple account IDs, the tool returns a summary of all submissions:
{message: "Feedback sent to 2 account(s)",total_accounts: 2,successful: 2,failed: 0,results: [{account_id: "acc_123456",status: "success",result: {message: "Feedback successfully stored", ... }},{account_id: "acc_789012",status: "success",result: {message: "Feedback successfully stored", ... }}]}When AI agents use this tool, they will:
- Ask for user consent: "Are you ok with sending feedback to StackOne?"
- Collect feedback: Get the user's verbatim feedback
- Track tool usage: Record which tools were used in the session
- Submit to all accounts: Send the same feedback to each account ID provided
- Report results: Show which accounts received the feedback successfully
The tool description includes clear instructions for AI agents to always ask for explicit user consent before submitting feedback.
# 1. Set up credentials
cp .env.example .env
# Edit .env with your API keys# 2. Install dependencies
pnpm install
# 3. Run any example
pnpm run:example examples/openai-integration.tsSee the examples/ directory for the full list.
This project includes a Nix flake for reproducible development environments. All development tools are defined in flake.nix and provided via Nix.
# Install Nix with flakes enabled (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf -L https://artifacts.nixos.org/experimental-installer | \
sh -s -- install
# If flakes are not enabled, enable them with:
mkdir -p ~/.config/nix &&echo"experimental-features = nix-command flakes">>~/.config/nix/nix.conf# Automatic activation with direnv (recommended)
direnv allow
# Or manual activation
nix developThe flake provides all necessary development dependencies including Node.js, pnpm, and other build tools.