Compare commits

..

14 Commits

Author SHA1 Message Date
Waleed
0d2e6ff31d v0.5.102: new integrations, new tools, ci speedups, memory leak instrumentation 2026-02-28 12:48:10 -08:00
Waleed
9be75e3633 improvement(loops): validate loops integration and update skill files (#3384)
* improvement(loops): validate loops integration and update skill files

* loops icon color

* update databricks icon
2026-02-28 12:37:01 -08:00
Waleed
40bab7731a fix(chat-deploy): fix launch chat popup and auth persistence, clean up React anti-patterns (#3380)
* fix(chat-deploy): fix launch chat popup and auth persistence, clean up React anti-patterns

* lint

* fix(greenhouse): fix email_address query param, add .trim() to ID paths, revert onValidationChange to useEffect

* fix(chat-deploy): fix stale AuthSelector state, stabilize refetch ref, clean up copy timeout

* fix(chat-deploy): reset chatSuccess on modal open to prevent stuck state
2026-02-28 12:01:42 -08:00
Waleed
4fd0989264 v0.5.101: circular dependency mitigation, confluence enhancements, google tasks and bigquery integrations, workflow lock 2026-02-26 15:04:53 -08:00
Waleed
67f8a687f6 v0.5.100: multiple credentials, 40% speedup, gong, attio, audit log improvements 2026-02-25 00:28:25 -08:00
Waleed
af592349d3 v0.5.99: local dev improvements, live workflow logs in terminal 2026-02-23 00:24:49 -08:00
Waleed
0d86ea01f0 v0.5.98: change detection improvements, rate limit and code execution fixes, removed retired models, hex integration 2026-02-21 18:07:40 -08:00
Waleed
115f04e989 v0.5.97: oidc discovery for copilot mcp 2026-02-21 02:06:25 -08:00
Waleed
34d92fae89 v0.5.96: sim oauth provider, slack ephemeral message tool and blockkit support 2026-02-20 18:22:20 -08:00
Waleed
67aa4bb332 v0.5.95: gemini 3.1 pro, cloudflare, dataverse, revenuecat, redis, upstash, algolia tools; isolated-vm robustness improvements, tables backend (#3271)
* feat(tools): advanced fields for youtube, vercel; added cloudflare and dataverse tools (#3257)

* refactor(vercel): mark optional fields as advanced mode

Move optional/power-user fields behind the advanced toggle:
- List Deployments: project filter, target, state
- Create Deployment: project ID override, redeploy from, target
- List Projects: search
- Create/Update Project: framework, build/output/install commands
- Env Vars: variable type
- Webhooks: project IDs filter
- Checks: path, details URL
- Team Members: role filter
- All operations: team ID scope

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(youtube): mark optional params as advanced mode

Hide pagination, sort order, and filter fields behind the advanced
toggle for a cleaner default UX across all YouTube operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* added advanced fields for vercel and youtube, added cloudflare and dataverse block

* addded desc for dataverse

* add more tools

* ack comment

* more

* ops

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(tables): added tables (#2867)

* updates

* required

* trashy table viewer

* updates

* updates

* filtering ui

* updates

* updates

* updates

* one input mode

* format

* fix lints

* improved errors

* updates

* updates

* chages

* doc strings

* breaking down file

* update comments with ai

* updates

* comments

* changes

* revert

* updates

* dedupe

* updates

* updates

* updates

* refactoring

* renames & refactors

* refactoring

* updates

* undo

* update db

* wand

* updates

* fix comments

* fixes

* simplify comments

* u[dates

* renames

* better comments

* validation

* updates

* updates

* updates

* fix sorting

* fix appearnce

* updating prompt to make it user sort

* rm

* updates

* rename

* comments

* clean comments

* simplicifcaiton

* updates

* updates

* refactor

* reduced type confusion

* undo

* rename

* undo changes

* undo

* simplify

* updates

* updates

* revert

* updates

* db updates

* type fix

* fix

* fix error handling

* updates

* docs

* docs

* updates

* rename

* dedupe

* revert

* uncook

* updates

* fix

* fix

* fix

* fix

* prepare merge

* readd migrations

* add back missed code

* migrate enrichment logic to general abstraction

* address bugbot concerns

* adhere to size limits for tables

* remove conflicting migration

* add back migrations

* fix tables auth

* fix permissive auth

* fix lint

* reran migrations

* migrate to use tanstack query for all server state

* update table-selector

* update names

* added tables to permission groups, updated subblock types

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: waleed <walif6@gmail.com>

* fix(snapshot): changed insert to upsert when concurrent identical child workflows are running (#3259)

* fix(snapshot): changed insert to upsert when concurrent identical child workflows are running

* fixed ci tests failing

* fix(workflows): disallow duplicate workflow names at the same folder level (#3260)

* feat(tools): added redis, upstash, algolia, and revenuecat (#3261)

* feat(tools): added redis, upstash, algolia, and revenuecat

* ack comment

* feat(models): add gemini-3.1-pro-preview and update gemini-3-pro thinking levels (#3263)

* fix(audit-log): lazily resolve actor name/email when missing (#3262)

* fix(blocks): move type coercions from tools.config.tool to tools.config.params (#3264)

* fix(blocks): move type coercions from tools.config.tool to tools.config.params

Number() coercions in tools.config.tool ran at serialization time before
variable resolution, destroying dynamic references like <block.result.count>
by converting them to NaN/null. Moved all coercions to tools.config.params
which runs at execution time after variables are resolved.

Fixed in 15 blocks: exa, arxiv, sentry, incidentio, wikipedia, ahrefs,
posthog, elasticsearch, dropbox, hunter, lemlist, spotify, youtube, grafana,
parallel. Also added mode: 'advanced' to optional exa fields.

Closes #3258

* fix(blocks): address PR review — move remaining param mutations from tool() to params()

- Moved field mappings from tool() to params() in grafana, posthog,
  lemlist, spotify, dropbox (same dynamic reference bug)
- Fixed parallel.ts excerpts/full_content boolean logic
- Fixed parallel.ts search_queries empty case (must set undefined)
- Fixed elasticsearch.ts timeout not included when already ends with 's'
- Restored dropbox.ts tool() switch for proper default fallback

* fix(blocks): restore field renames to tool() for serialization-time validation

Field renames (e.g. personalApiKey→apiKey) must be in tool() because
validateRequiredFieldsBeforeExecution calls selectToolId()→tool() then
checks renamed field names on params. Only type coercions (Number(),
boolean) stay in params() to avoid destroying dynamic variable references.

* improvement(resolver): resovled empty sentinel to not pass through unexecuted valid refs to text inputs (#3266)

* fix(blocks): add required constraint for serviceDeskId in JSM block (#3268)

* fix(blocks): add required constraint for serviceDeskId in JSM block

* fix(blocks): rename custom field values to request field values in JSM create request

* fix(trigger): add isolated-vm support to trigger.dev container builds (#3269)

Scheduled workflow executions running in trigger.dev containers were
failing to spawn isolated-vm workers because the native module wasn't
available in the container. This caused loop condition evaluation to
silently fail and exit after one iteration.

- Add isolated-vm to build.external and additionalPackages in trigger config
- Include isolated-vm-worker.cjs via additionalFiles for child process spawning
- Add fallback path resolution for worker file in trigger.dev environment

* fix(tables): hide tables from sidebar and block registry (#3270)

* fix(tables): hide tables from sidebar and block registry

* fix(trigger): add isolated-vm support to trigger.dev container builds (#3269)

Scheduled workflow executions running in trigger.dev containers were
failing to spawn isolated-vm workers because the native module wasn't
available in the container. This caused loop condition evaluation to
silently fail and exit after one iteration.

- Add isolated-vm to build.external and additionalPackages in trigger config
- Include isolated-vm-worker.cjs via additionalFiles for child process spawning
- Add fallback path resolution for worker file in trigger.dev environment

* lint

* fix(trigger): update node version to align with main app (#3272)

* fix(build): fix corrupted sticky disk cache on blacksmith (#3273)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
2026-02-20 13:43:07 -08:00
Waleed
15ace5e63f v0.5.94: vercel integration, folder insertion, migrated tracking redirects to rewrites 2026-02-18 16:53:34 -08:00
Waleed
fdca73679d v0.5.93: NextJS config changes, MCP and Blocks whitelisting, copilot keyboard shortcuts, audit logs 2026-02-18 12:10:05 -08:00
Waleed
da46a387c9 v0.5.92: shortlinks, copilot scrolling stickiness, pagination 2026-02-17 15:13:21 -08:00
Waleed
b7e377ec4b v0.5.91: docs i18n, turborepo upgrade 2026-02-16 00:36:05 -08:00
42 changed files with 450 additions and 1585 deletions

View File

@@ -76,6 +76,7 @@ export function ApiIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function ConditionalIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3997,7 +3998,7 @@ export function LoopsIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 256 256' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fill='currentColor'
fill='#FD4E00'
d='M192.352 88.042c0-7.012-5.685-12.697-12.697-12.697s-12.697 5.685-12.697 12.697c0 .634.052 1.255.142 1.866a25.248 25.248 0 0 0-4.9-.49c-14.006 0-25.36 11.354-25.36 25.36 0 1.63.16 3.222.456 4.765a37.8 37.8 0 0 0-9.296-1.173c-20.95 0-37.935 16.985-37.935 37.935S107.05 194.24 128 194.24s37.935-16.985 37.935-37.935a37.7 37.7 0 0 0-3.78-16.555 25.2 25.2 0 0 0 12.487-3.336 25.2 25.2 0 0 0 4.558 3.336v.02c14.006 0 25.36-11.354 25.36-25.36 0-12.48-9.018-22.855-20.888-24.996a12.6 12.6 0 0 0 8.68-11.972m-77.05 68.263c0-7.012 5.685-12.697 12.697-12.697s12.697 5.685 12.697 12.697c0 7.013-5.685 12.697-12.697 12.697s-12.697-5.685-12.697-12.697'
/>
</svg>
@@ -4541,7 +4542,7 @@ export function DatabricksIcon(props: SVGProps<SVGSVGElement>) {
<svg {...props} viewBox='0 0 241 266' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M228.085 109.654L120.615 171.674L5.53493 105.41L0 108.475V156.582L120.615 225.911L228.085 164.128V189.596L120.615 251.615L5.53493 185.351L0 188.417V196.67L120.615 266L241 196.67V148.564L235.465 145.498L120.615 211.527L12.9148 149.743V124.275L120.615 186.059L241 116.729V69.3298L235.004 65.7925L120.615 131.585L18.4498 73.1028L120.615 14.3848L204.562 62.7269L211.942 58.4823V52.5869L120.615 0L0 69.3298V76.8759L120.615 146.206L228.085 84.1862V109.654Z'
fill='#F9F7F4'
fill='#FF3621'
/>
</svg>
)
@@ -6011,20 +6012,3 @@ export function HexIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function ShortIoIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 64 65' fill='none' xmlns='http://www.w3.org/2000/svg'>
<rect width='64' height='65' fill='#FFFFFF' />
<path
d='M41.1 45.7c0 2-.8 3.5-2.5 4.6-1.6 1-3.8 1.6-6.5 1.6-3.4 0-6-.8-8-2.3-2-1.6-3-3.6-3.2-6.1l-16.3-.4c0 4.1 1.2 7.8 3.6 11.1A24 24 0 0 0 18 62c2.2 1 4.5 1.7 7 2.2l.4.1H0V.2h24.9A25.4 25.4 0 0 0 9.3 9.5C7.1 12.5 6 15.9 6 19.7c0 4.2.9 7.6 2.6 10.1 1.7 2.5 4 4.4 6.8 5.7 2.8 1.3 6.3 2.3 10.6 3.2 4.4.9 7.5 1.6 9.5 2.2 1.9.5 3.3 1.1 4.3 1.9.8.6 1.3 1.6 1.3 2.9Z'
fill='#0BB07D'
/>
<path d='M25.3 64.2h-.6l.1-.1.5.1Z' fill='#33333D' />
<path
d='M64 64.2H38.1a28 28 0 0 0 7.1-2.2 23 23 0 0 0 9.4-7.6c2.2-3.2 3.4-6.8 3.4-10.8a17 17 0 0 0-2.6-9.8c-1.7-2.4-4-4.3-6.9-5.5a54.4 54.4 0 0 0-10.8-3.1c-4.3-.8-7.3-1.5-9.2-2.1a12 12 0 0 1-4.2-1.8c-.9-.7-1.3-1.7-1.3-3 0-1.9.7-3.3 2.2-4.3 1.5-1 3.4-1.5 5.8-1.5 2.7 0 4.9.7 6.5 2.1a7.8 7.8 0 0 1 2.7 5.4h16.4c0-3.8-1.1-7.3-3.3-10.5a23 23 0 0 0-9.1-7.4c-2.1-1-4.4-1.7-6.8-2.1H64v64.2Z'
fill='#383738'
/>
</svg>
)
}

View File

@@ -126,7 +126,6 @@ import {
ServiceNowIcon,
SftpIcon,
ShopifyIcon,
ShortIoIcon,
SimilarwebIcon,
SlackIcon,
SmtpIcon,
@@ -284,7 +283,6 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
sftp: SftpIcon,
sharepoint: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
short_io: ShortIoIcon,
similarweb: SimilarwebIcon,
slack: SlackIcon,
smtp: SmtpIcon,

View File

@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="databricks"
color="#FF3621"
color="#F9F7F4"
/>
{/* MANUAL-CONTENT-START:intro */}

View File

@@ -35,541 +35,472 @@ Integrate Greenhouse into the workflow. List and retrieve candidates, jobs, appl
### `greenhouse_list_candidates`
Lists candidates from Greenhouse with optional filtering by date, job, or email
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
| `created_after` | string | No | Return only candidates created at or after this ISO 8601 timestamp |
| `created_before` | string | No | Return only candidates created before this ISO 8601 timestamp |
| `updated_after` | string | No | Return only candidates updated at or after this ISO 8601 timestamp |
| `updated_before` | string | No | Return only candidates updated before this ISO 8601 timestamp |
| `job_id` | string | No | Filter to candidates who applied to this job ID \(excludes prospects\) |
| `email` | string | No | Filter to candidates with this email address |
| `candidate_ids` | string | No | Comma-separated candidate IDs to retrieve \(max 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `candidates` | array | List of candidates |
| ↳ `id` | number | Candidate ID |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `company` | string | Current employer |
| ↳ `title` | string | Current job title |
| ↳ `is_private` | boolean | Whether candidate is private |
| ↳ `can_email` | boolean | Whether candidate can be emailed |
| ↳ `email_addresses` | array | Email addresses |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Email type \(personal, work, other\) |
| ↳ `tags` | array | Candidate tags |
| ↳ `application_ids` | array | Associated application IDs |
| ↳ `created_at` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `last_activity` | string | Last activity timestamp \(ISO 8601\) |
| `count` | number | Number of candidates returned |
### `greenhouse_get_candidate`
Retrieves a specific candidate by ID with full details including contact info, education, and employment history
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `candidateId` | string | Yes | The ID of the candidate to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `id` | number | Candidate ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `company` | string | Current employer |
| `title` | string | Current job title |
| `is_private` | boolean | Whether candidate is private |
| `can_email` | boolean | Whether candidate can be emailed |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `last_activity` | string | Last activity timestamp \(ISO 8601\) |
| `email_addresses` | array | Email addresses |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Type \(personal, work, other\) |
| `phone_numbers` | array | Phone numbers |
| ↳ `value` | string | Phone number |
| ↳ `type` | string | Type \(home, work, mobile, skype, other\) |
| `addresses` | array | Addresses |
| ↳ `value` | string | Address |
| ↳ `type` | string | Type \(home, work, other\) |
| `website_addresses` | array | Website addresses |
| ↳ `value` | string | URL |
| ↳ `type` | string | Type \(personal, company, portfolio, blog, other\) |
| `social_media_addresses` | array | Social media profiles |
| ↳ `value` | string | URL or handle |
| `tags` | array | Tags |
| `application_ids` | array | Associated application IDs |
| `recruiter` | object | Assigned recruiter |
| ↳ `id` | number | User ID |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `name` | string | Full name |
| ↳ `employee_id` | string | Employee ID |
| `coordinator` | object | Assigned coordinator |
| ↳ `id` | number | User ID |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `name` | string | Full name |
| ↳ `employee_id` | string | Employee ID |
| `attachments` | array | File attachments \(URLs expire after 7 days\) |
| ↳ `filename` | string | File name |
| ↳ `url` | string | Download URL \(expires after 7 days\) |
| ↳ `type` | string | Type \(resume, cover_letter, offer_packet, other\) |
| ↳ `created_at` | string | Upload timestamp |
| `educations` | array | Education history |
| ↳ `id` | number | Education record ID |
| ↳ `school_name` | string | School name |
| ↳ `degree` | string | Degree type |
| ↳ `discipline` | string | Field of study |
| ↳ `start_date` | string | Start date \(ISO 8601\) |
| ↳ `end_date` | string | End date \(ISO 8601\) |
| `employments` | array | Employment history |
| ↳ `id` | number | Employment record ID |
| ↳ `company_name` | string | Company name |
| ↳ `title` | string | Job title |
| ↳ `start_date` | string | Start date \(ISO 8601\) |
| ↳ `end_date` | string | End date \(ISO 8601\) |
| `custom_fields` | object | Custom field values |
### `greenhouse_list_jobs`
Lists jobs from Greenhouse with optional filtering by status, department, or office
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
| `status` | string | No | Filter by job status \(open, closed, draft\) |
| `created_after` | string | No | Return only jobs created at or after this ISO 8601 timestamp |
| `created_before` | string | No | Return only jobs created before this ISO 8601 timestamp |
| `updated_after` | string | No | Return only jobs updated at or after this ISO 8601 timestamp |
| `updated_before` | string | No | Return only jobs updated before this ISO 8601 timestamp |
| `department_id` | string | No | Filter to jobs in this department ID |
| `office_id` | string | No | Filter to jobs in this office ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `jobs` | array | List of jobs |
| ↳ `id` | number | Job ID |
| ↳ `name` | string | Job title |
| ↳ `status` | string | Job status \(open, closed, draft\) |
| ↳ `confidential` | boolean | Whether the job is confidential |
| ↳ `departments` | array | Associated departments |
| ↳ `id` | number | Department ID |
| ↳ `name` | string | Department name |
| ↳ `offices` | array | Associated offices |
| ↳ `id` | number | Office ID |
| ↳ `name` | string | Office name |
| ↳ `opened_at` | string | Date job was opened \(ISO 8601\) |
| ↳ `closed_at` | string | Date job was closed \(ISO 8601\) |
| ↳ `created_at` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `count` | number | Number of jobs returned |
### `greenhouse_get_job`
Retrieves a specific job by ID with full details including hiring team, openings, and custom fields
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `jobId` | string | Yes | The ID of the job to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `id` | number | Job ID |
| `name` | string | Job title |
| `requisition_id` | string | External requisition ID |
| `status` | string | Job status \(open, closed, draft\) |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `opened_at` | string | Date job was opened \(ISO 8601\) |
| `closed_at` | string | Date job was closed \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `is_template` | boolean | Whether this is a job template |
| `notes` | string | Hiring plan notes \(may contain HTML\) |
| `departments` | array | Associated departments |
| ↳ `id` | number | Department ID |
| ↳ `name` | string | Department name |
| ↳ `parent_id` | number | Parent department ID |
| `offices` | array | Associated offices |
| ↳ `id` | number | Office ID |
| ↳ `name` | string | Office name |
| ↳ `location` | object | Office location |
| ↳ `name` | string | Location name |
| `hiring_team` | object | Hiring team members |
| ↳ `hiring_managers` | array | Hiring managers |
| ↳ `recruiters` | array | Recruiters \(includes responsible flag\) |
| ↳ `coordinators` | array | Coordinators \(includes responsible flag\) |
| ↳ `sourcers` | array | Sourcers |
| `openings` | array | Job openings/slots |
| ↳ `id` | number | Opening internal ID |
| ↳ `opening_id` | string | Custom opening identifier |
| ↳ `status` | string | Opening status \(open, closed\) |
| ↳ `opened_at` | string | Date opened \(ISO 8601\) |
| ↳ `closed_at` | string | Date closed \(ISO 8601\) |
| ↳ `application_id` | number | Hired application ID |
| ↳ `close_reason` | object | Reason for closing |
| ↳ `id` | number | Close reason ID |
| ↳ `name` | string | Close reason name |
| `custom_fields` | object | Custom field values |
### `greenhouse_list_applications`
Lists applications from Greenhouse with optional filtering by job, status, or date
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
| `job_id` | string | No | Filter applications by job ID |
| `status` | string | No | Filter by status \(active, converted, hired, rejected\) |
| `created_after` | string | No | Return only applications created at or after this ISO 8601 timestamp |
| `created_before` | string | No | Return only applications created before this ISO 8601 timestamp |
| `last_activity_after` | string | No | Return only applications with activity at or after this ISO 8601 timestamp |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `applications` | array | List of applications |
| ↳ `id` | number | Application ID |
| ↳ `candidate_id` | number | Associated candidate ID |
| ↳ `prospect` | boolean | Whether this is a prospect application |
| ↳ `status` | string | Status \(active, converted, hired, rejected\) |
| ↳ `current_stage` | object | Current interview stage |
| ↳ `id` | number | Stage ID |
| ↳ `name` | string | Stage name |
| ↳ `jobs` | array | Associated jobs |
| ↳ `id` | number | Job ID |
| ↳ `name` | string | Job name |
| ↳ `applied_at` | string | Application date \(ISO 8601\) |
| ↳ `rejected_at` | string | Rejection date \(ISO 8601\) |
| ↳ `last_activity_at` | string | Last activity date \(ISO 8601\) |
| `count` | number | Number of applications returned |
### `greenhouse_get_application`
Retrieves a specific application by ID with full details including source, stage, answers, and attachments
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `applicationId` | string | Yes | The ID of the application to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `id` | number | Application ID |
| `candidate_id` | number | Associated candidate ID |
| `prospect` | boolean | Whether this is a prospect application |
| `status` | string | Status \(active, converted, hired, rejected\) |
| `applied_at` | string | Application date \(ISO 8601\) |
| `rejected_at` | string | Rejection date \(ISO 8601\) |
| `last_activity_at` | string | Last activity date \(ISO 8601\) |
| `location` | object | Candidate location |
| ↳ `address` | string | Location address |
| `source` | object | Application source |
| ↳ `id` | number | Source ID |
| ↳ `public_name` | string | Source name |
| `credited_to` | object | User credited for the application |
| ↳ `id` | number | User ID |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `name` | string | Full name |
| ↳ `employee_id` | string | Employee ID |
| `recruiter` | object | Assigned recruiter |
| ↳ `id` | number | User ID |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `name` | string | Full name |
| ↳ `employee_id` | string | Employee ID |
| `coordinator` | object | Assigned coordinator |
| ↳ `id` | number | User ID |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `name` | string | Full name |
| ↳ `employee_id` | string | Employee ID |
| `current_stage` | object | Current interview stage \(null when hired\) |
| ↳ `id` | number | Stage ID |
| ↳ `name` | string | Stage name |
| `rejection_reason` | object | Rejection reason |
| ↳ `id` | number | Rejection reason ID |
| ↳ `name` | string | Rejection reason name |
| ↳ `type` | object | Rejection reason type |
| ↳ `id` | number | Type ID |
| ↳ `name` | string | Type name |
| `jobs` | array | Associated jobs |
| ↳ `id` | number | Job ID |
| ↳ `name` | string | Job name |
| `job_post_id` | number | Job post ID |
| `answers` | array | Application question answers |
| ↳ `question` | string | Question text |
| ↳ `answer` | string | Answer text |
| `attachments` | array | File attachments \(URLs expire after 7 days\) |
| ↳ `filename` | string | File name |
| ↳ `url` | string | Download URL \(expires after 7 days\) |
| ↳ `type` | string | Type \(resume, cover_letter, offer_packet, other\) |
| ↳ `created_at` | string | Upload timestamp |
| `custom_fields` | object | Custom field values |
### `greenhouse_list_users`
Lists Greenhouse users (recruiters, hiring managers, admins) with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
| `created_after` | string | No | Return only users created at or after this ISO 8601 timestamp |
| `created_before` | string | No | Return only users created before this ISO 8601 timestamp |
| `updated_after` | string | No | Return only users updated at or after this ISO 8601 timestamp |
| `updated_before` | string | No | Return only users updated before this ISO 8601 timestamp |
| `email` | string | No | Filter by email address |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `users` | array | List of Greenhouse users |
| ↳ `id` | number | User ID |
| ↳ `name` | string | Full name |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `primary_email_address` | string | Primary email |
| ↳ `disabled` | boolean | Whether the user is disabled |
| ↳ `site_admin` | boolean | Whether the user is a site admin |
| ↳ `emails` | array | All email addresses |
| ↳ `employee_id` | string | Employee ID |
| ↳ `linked_candidate_ids` | array | IDs of candidates linked to this user |
| ↳ `created_at` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `count` | number | Number of users returned |
### `greenhouse_get_user`
Retrieves a specific Greenhouse user by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `userId` | string | Yes | The ID of the user to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `id` | number | User ID |
| `name` | string | Full name |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `primary_email_address` | string | Primary email address |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `emails` | array | All email addresses |
| `employee_id` | string | Employee ID |
| `linked_candidate_ids` | array | IDs of candidates linked to this user |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
### `greenhouse_list_departments`
Lists all departments configured in Greenhouse
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `departments` | array | List of departments |
| ↳ `id` | number | Department ID |
| ↳ `name` | string | Department name |
| ↳ `parent_id` | number | Parent department ID |
| ↳ `child_ids` | array | Child department IDs |
| ↳ `external_id` | string | External system ID |
| `count` | number | Number of departments returned |
### `greenhouse_list_offices`
Lists all offices configured in Greenhouse
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `offices` | array | List of offices |
| ↳ `id` | number | Office ID |
| ↳ `name` | string | Office name |
| ↳ `location` | object | Office location |
| ↳ `name` | string | Location name |
| ↳ `primary_contact_user_id` | number | Primary contact user ID |
| ↳ `parent_id` | number | Parent office ID |
| ↳ `child_ids` | array | Child office IDs |
| ↳ `external_id` | string | External system ID |
| `count` | number | Number of offices returned |
### `greenhouse_list_job_stages`
Lists all interview stages for a specific job in Greenhouse
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Greenhouse Harvest API key |
| `jobId` | string | Yes | The job ID to list stages for |
| `per_page` | number | No | Number of results per page \(1-500, default 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | json | List of candidates |
| `jobs` | json | List of jobs |
| `applications` | json | List of applications |
| `users` | json | List of users |
| `departments` | json | List of departments |
| `offices` | json | List of offices |
| `stages` | json | List of job stages |
| `count` | number | Number of results returned |
| `id` | number | Resource ID |
| `first_name` | string | First name |
| `last_name` | string | Last name |
| `name` | string | Resource name |
| `status` | string | Status |
| `email_addresses` | json | Email addresses |
| `phone_numbers` | json | Phone numbers |
| `tags` | json | Tags |
| `application_ids` | json | Associated application IDs |
| `recruiter` | json | Assigned recruiter |
| `coordinator` | json | Assigned coordinator |
| `current_stage` | json | Current interview stage |
| `source` | json | Application source |
| `hiring_team` | json | Hiring team members |
| `openings` | json | Job openings |
| `custom_fields` | json | Custom field values |
| `attachments` | json | File attachments |
| `educations` | json | Education history |
| `employments` | json | Employment history |
| `answers` | json | Application question answers |
| `prospect` | boolean | Whether this is a prospect |
| `confidential` | boolean | Whether the job is confidential |
| `is_private` | boolean | Whether the candidate is private |
| `can_email` | boolean | Whether the candidate can be emailed |
| `disabled` | boolean | Whether the user is disabled |
| `site_admin` | boolean | Whether the user is a site admin |
| `primary_email_address` | string | Primary email address |
| `created_at` | string | Creation timestamp \(ISO 8601\) |
| `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| `stages` | array | List of job stages in order |
| ↳ `id` | number | Stage ID |
| ↳ `name` | string | Stage name |
| ↳ `created_at` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updated_at` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `job_id` | number | Associated job ID |
| ↳ `priority` | number | Stage order priority |
| ↳ `active` | boolean | Whether the stage is active |
| ↳ `interviews` | array | Interview steps in this stage |
| ↳ `id` | number | Interview ID |
| ↳ `name` | string | Interview name |
| ↳ `schedulable` | boolean | Whether the interview is schedulable |
| ↳ `estimated_minutes` | number | Estimated duration in minutes |
| ↳ `default_interviewer_users` | array | Default interviewers |
| ↳ `id` | number | User ID |
| ↳ `name` | string | Full name |
| ↳ `first_name` | string | First name |
| ↳ `last_name` | string | Last name |
| ↳ `employee_id` | string | Employee ID |
| ↳ `interview_kit` | object | Interview kit details |
| ↳ `id` | number | Kit ID |
| ↳ `content` | string | Kit content \(HTML\) |
| ↳ `questions` | array | Interview kit questions |
| ↳ `id` | number | Question ID |
| ↳ `question` | string | Question text |
| `count` | number | Number of stages returned |

View File

@@ -122,7 +122,6 @@
"sftp",
"sharepoint",
"shopify",
"short_io",
"similarweb",
"slack",
"smtp",

View File

@@ -1,173 +0,0 @@
---
title: Short.io
description: Create and manage short links, domains, and analytics.
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="short_io"
color="#FFFFFF"
/>
{/* MANUAL-CONTENT-START:intro */}
[Short.io](https://short.io/) is a white-label URL shortener that lets you create branded short links on your own domain, track clicks, and manage links at scale. Short.io is designed for businesses that want professional short URLs, QR codes, and link analytics without relying on generic shorteners.
With Short.io in Sim, you can:
- **Create short links**: Generate branded short URLs from long URLs using your custom domain, with optional custom paths
- **List domains**: Retrieve all Short.io domains on your account to get domain IDs for listing links
- **List links**: List short links for a domain with pagination and optional date sort order
- **Delete links**: Remove a short link by its ID (e.g. lnk_abc123_abcdef)
- **Generate QR codes**: Create QR codes for any Short.io link with optional size, color, background color, and format (PNG or SVG); returns a base64 data URL
- **Get link statistics**: Fetch click analytics for a link including total clicks, human clicks, referrer/country/browser/OS/city breakdowns, UTM dimensions, time-series data, and date interval
These capabilities allow your Sim agents to automate link shortening, QR code generation, and analytics reporting directly in your workflows — from campaign tracking to link management and performance dashboards.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Short.io to generate branded short links, list domains and links, delete links, generate QR codes, and view link statistics. Requires your Short.io Secret API Key.
## Tools
### `short_io_create_link`
Create a short link using your Short.io custom domain.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Short.io Secret API Key |
| `domain` | string | Yes | Your registered Short.io custom domain |
| `originalURL` | string | Yes | The long URL to shorten |
| `path` | string | No | Optional custom path for the short link |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `shortURL` | string | The generated short link URL |
| `idString` | string | The unique Short.io link ID string |
| `originalURL` | string | The original long URL |
| `path` | string | The path/slug of the short link |
| `createdAt` | string | ISO 8601 creation timestamp |
### `short_io_list_domains`
List Short.io domains. Returns domain IDs and details for use in List Links.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Short.io Secret API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `domains` | array | List of domain objects \(id, hostname, etc.\) |
| `count` | number | Number of domains |
### `short_io_list_links`
List short links for a domain. Requires domain_id (from List Domains or dashboard). Max 150 per request.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Short.io Secret API Key |
| `domainId` | number | Yes | Domain ID \(from List Domains\) |
| `limit` | number | No | Max links to return \(1150\) |
| `pageToken` | string | No | Pagination token from previous response |
| `dateSortOrder` | string | No | Sort by date: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `links` | array | List of link objects \(idString, shortURL, originalURL, path, etc.\) |
| `count` | number | Number of links returned |
| `nextPageToken` | string | Token for next page |
### `short_io_delete_link`
Delete a short link by ID (e.g. lnk_abc123_abcdef). Rate limit 20/s.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Short.io Secret API Key |
| `linkId` | string | Yes | Link ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the link was deleted |
| `idString` | string | Deleted link ID |
### `short_io_get_qr_code`
Generate a QR code for a Short.io link (POST /links/qr/{linkIdString}).
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Short.io Secret API Key |
| `linkId` | string | Yes | Link ID \(e.g. lnk_abc123_abcdef\) |
| `color` | string | No | QR color hex \(e.g. 000000\) |
| `backgroundColor` | string | No | Background color hex \(e.g. FFFFFF\) |
| `size` | number | No | QR size 199 |
| `type` | string | No | Output format: png or svg |
| `useDomainSettings` | boolean | No | Use domain settings \(default true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `file` | file | Generated QR code image file |
### `short_io_get_analytics`
Fetch click statistics for a Short.io link (Statistics API: totalClicks, humanClicks, referer, country, etc.).
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Short.io Secret API Key |
| `linkId` | string | Yes | No description |
| `period` | string | Yes | Period: today, yesterday, last7, last30, total, week, month, lastmonth |
| `tz` | string | No | Timezone \(default UTC\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalClicks` | number | Total clicks |
| `humanClicks` | number | Human clicks |
| `totalClicksChange` | string | Change vs previous period |
| `humanClicksChange` | string | Human clicks change |
| `referer` | array | Referrer breakdown \(referer, score\) |
| `country` | array | Country breakdown \(countryName, country, score\) |
| `browser` | array | Browser breakdown \(browser, score\) |
| `os` | array | OS breakdown \(os, score\) |
| `city` | array | City breakdown \(city, name, countryCode, score\) |
| `device` | array | Device breakdown |
| `social` | array | Social source breakdown \(social, score\) |
| `utmMedium` | array | UTM medium breakdown |
| `utmSource` | array | UTM source breakdown |
| `utmCampaign` | array | UTM campaign breakdown |
| `clickStatistics` | object | Time-series click data \(datasets with x/y points per interval\) |
| `interval` | object | Date range \(startDate, endDate, prevStartDate, prevEndDate, tz\) |

View File

@@ -1,101 +0,0 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic'
const logger = createLogger('ShortIoQrAPI')
const ShortIoQrSchema = z.object({
apiKey: z.string().min(1, 'API key is required'),
linkId: z.string().min(1, 'Link ID is required'),
color: z.string().optional(),
backgroundColor: z.string().optional(),
size: z.number().min(1).max(99).optional(),
type: z.enum(['png', 'svg']).optional(),
useDomainSettings: z.boolean().optional(),
})
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Short.io QR request: ${authResult.error}`)
return NextResponse.json(
{ success: false, error: authResult.error || 'Authentication required' },
{ status: 401 }
)
}
const body = await request.json()
const validated = ShortIoQrSchema.parse(body)
const qrBody: Record<string, unknown> = {
useDomainSettings: validated.useDomainSettings ?? true,
}
if (validated.color) qrBody.color = validated.color
if (validated.backgroundColor) qrBody.backgroundColor = validated.backgroundColor
if (validated.size) qrBody.size = validated.size
if (validated.type) qrBody.type = validated.type
const response = await fetch(`https://api.short.io/links/qr/${validated.linkId}`, {
method: 'POST',
headers: {
Authorization: validated.apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify(qrBody),
})
if (!response.ok) {
const errorText = await response.text().catch(() => response.statusText)
logger.error(`[${requestId}] Short.io QR API error: ${errorText}`)
return NextResponse.json(
{ success: false, error: `Short.io API error: ${errorText}` },
{ status: response.status }
)
}
const contentType = response.headers.get('Content-Type') ?? 'image/png'
const fileBuffer = Buffer.from(await response.arrayBuffer())
const mimeType = contentType.split(';')[0]?.trim() || 'image/png'
const ext = validated.type === 'svg' ? 'svg' : 'png'
const fileName = `qr-${validated.linkId}.${ext}`
logger.info(`[${requestId}] QR code generated`, {
linkId: validated.linkId,
size: fileBuffer.length,
mimeType,
})
return NextResponse.json({
success: true,
output: {
file: {
name: fileName,
mimeType,
data: fileBuffer.toString('base64'),
size: fileBuffer.length,
},
},
})
} catch (error: unknown) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{
success: false,
error: `Validation error: ${error.errors.map((e) => e.message).join(', ')}`,
},
{ status: 400 }
)
}
const message = error instanceof Error ? error.message : 'Unknown error'
logger.error(`[${requestId}] Short.io QR error: ${message}`)
return NextResponse.json({ success: false, error: message }, { status: 500 })
}
}

View File

@@ -108,7 +108,6 @@ export function ChatDeploy({
onVersionActivated,
}: ChatDeployProps) {
const [imageUrl, setImageUrl] = useState<string | null>(null)
const [isDeleting, setIsDeleting] = useState(false)
const [internalShowDeleteConfirmation, setInternalShowDeleteConfirmation] = useState(false)
const showDeleteConfirmation =
@@ -122,6 +121,7 @@ export function ChatDeploy({
const [formData, setFormData] = useState<ChatFormData>(initialFormData)
const [errors, setErrors] = useState<FormErrors>({})
const formRef = useRef<HTMLFormElement>(null)
const [formInitCounter, setFormInitCounter] = useState(0)
const createChatMutation = useCreateChat()
const updateChatMutation = useUpdateChat()
@@ -222,13 +222,20 @@ export function ChatDeploy({
setChatSubmitting(true)
const isNewChat = !existingChat?.id
// Open window before async operation to avoid popup blockers
const newTab = isNewChat ? window.open('', '_blank') : null
try {
if (!validateForm(!!existingChat)) {
newTab?.close()
setChatSubmitting(false)
return
}
if (!isIdentifierValid && formData.identifier !== existingChat?.identifier) {
newTab?.close()
setError('identifier', 'Please wait for identifier validation to complete')
setChatSubmitting(false)
return
@@ -257,13 +264,18 @@ export function ChatDeploy({
onDeployed?.()
onVersionActivated?.()
if (chatUrl) {
window.open(chatUrl, '_blank', 'noopener,noreferrer')
if (newTab && chatUrl) {
newTab.opener = null
newTab.location.href = chatUrl
} else if (newTab) {
newTab.close()
}
setHasInitializedForm(false)
await onRefetchChat()
setHasInitializedForm(false)
setFormInitCounter((c) => c + 1)
} catch (error: any) {
newTab?.close()
if (error.message?.includes('identifier')) {
setError('identifier', error.message)
} else {
@@ -278,8 +290,6 @@ export function ChatDeploy({
if (!existingChat || !existingChat.id) return
try {
setIsDeleting(true)
await deleteChatMutation.mutateAsync({
chatId: existingChat.id,
workflowId,
@@ -287,6 +297,7 @@ export function ChatDeploy({
setImageUrl(null)
setHasInitializedForm(false)
setFormInitCounter((c) => c + 1)
await onRefetchChat()
onDeploymentComplete?.()
@@ -294,7 +305,6 @@ export function ChatDeploy({
logger.error('Failed to delete chat:', error)
setError('general', error.message || 'An unexpected error occurred while deleting')
} finally {
setIsDeleting(false)
setShowDeleteConfirmation(false)
}
}
@@ -363,7 +373,7 @@ export function ChatDeploy({
</div>
<AuthSelector
key={existingChat?.id ?? 'new'}
key={`${existingChat?.id ?? 'new'}-${formInitCounter}`}
authType={formData.authType}
password={formData.password}
emails={formData.emails}
@@ -424,12 +434,16 @@ export function ChatDeploy({
<Button
variant='default'
onClick={() => setShowDeleteConfirmation(false)}
disabled={isDeleting}
disabled={deleteChatMutation.isPending}
>
Cancel
</Button>
<Button variant='destructive' onClick={handleDelete} disabled={isDeleting}>
{isDeleting ? 'Deleting...' : 'Delete'}
<Button
variant='destructive'
onClick={handleDelete}
disabled={deleteChatMutation.isPending}
>
{deleteChatMutation.isPending ? 'Deleting...' : 'Delete'}
</Button>
</ModalFooter>
</ModalContent>
@@ -620,6 +634,12 @@ function AuthSelector({
emails.map((email) => ({ value: email, isValid: true }))
)
useEffect(() => {
if (!copySuccess) return
const timer = setTimeout(() => setCopySuccess(false), 2000)
return () => clearTimeout(timer)
}, [copySuccess])
const handleGeneratePassword = () => {
const newPassword = generatePassword(24)
onPasswordChange(newPassword)
@@ -628,7 +648,6 @@ function AuthSelector({
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
setCopySuccess(true)
setTimeout(() => setCopySuccess(false), 2000)
}
const addEmail = (email: string): boolean => {

View File

@@ -1,6 +1,6 @@
'use client'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useQueryClient } from '@tanstack/react-query'
import {
@@ -113,6 +113,7 @@ export function DeployModal({
const [showA2aDeleteConfirm, setShowA2aDeleteConfirm] = useState(false)
const [chatSuccess, setChatSuccess] = useState(false)
const chatSuccessTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const [isCreateKeyModalOpen, setIsCreateKeyModalOpen] = useState(false)
const [isApiInfoModalOpen, setIsApiInfoModalOpen] = useState(false)
@@ -232,6 +233,12 @@ export function DeployModal({
setActiveTab('general')
setDeployError(null)
setDeployWarnings([])
setChatSuccess(false)
}
return () => {
if (chatSuccessTimeoutRef.current) {
clearTimeout(chatSuccessTimeoutRef.current)
}
}
}, [open, workflowId])
@@ -377,15 +384,16 @@ export function DeployModal({
const handleChatDeployed = useCallback(async () => {
if (!workflowId) return
queryClient.invalidateQueries({ queryKey: deploymentKeys.info(workflowId) })
queryClient.invalidateQueries({ queryKey: deploymentKeys.versions(workflowId) })
queryClient.invalidateQueries({ queryKey: deploymentKeys.chatStatus(workflowId) })
await refetchDeployedState()
useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false)
if (chatSuccessTimeoutRef.current) {
clearTimeout(chatSuccessTimeoutRef.current)
}
setChatSuccess(true)
setTimeout(() => setChatSuccess(false), 2000)
chatSuccessTimeoutRef.current = setTimeout(() => setChatSuccess(false), 2000)
}, [workflowId, queryClient, refetchDeployedState])
const handleRefetchChat = useCallback(async () => {
@@ -394,14 +402,7 @@ export function DeployModal({
const handleChatFormSubmit = useCallback(() => {
const form = document.getElementById('chat-deploy-form') as HTMLFormElement
if (form) {
const updateTrigger = form.querySelector('[data-update-trigger]') as HTMLButtonElement
if (updateTrigger) {
updateTrigger.click()
} else {
form.requestSubmit()
}
}
form?.requestSubmit()
}, [])
const handleChatDelete = useCallback(() => {

View File

@@ -12,7 +12,7 @@ export const DatabricksBlock: BlockConfig<DatabricksResponse> = {
'Connect to Databricks to execute SQL queries against SQL warehouses, trigger and monitor job runs, manage clusters, and retrieve run outputs. Requires a Personal Access Token and workspace host URL.',
docsLink: 'https://docs.sim.ai/tools/databricks',
category: 'tools',
bgColor: '#FF3621',
bgColor: '#F9F7F4',
icon: DatabricksIcon,
subBlocks: [
{

View File

@@ -154,7 +154,9 @@ Example:
{
"clxf1nxlb000t0ml79ajwcsj0": true,
"clxf2q43u00010mlh12q9ggx1": false
}`,
}
Return ONLY the JSON object - no explanations, no extra text.`,
placeholder: 'Describe the mailing list subscriptions...',
},
},
@@ -183,7 +185,9 @@ Example:
"signupDate": "2024-01-15T00:00:00Z",
"isActive": true,
"seats": 5
}`,
}
Return ONLY the JSON object - no explanations, no extra text.`,
placeholder: 'Describe the custom properties...',
},
},
@@ -221,7 +225,9 @@ Example:
"name": "John Smith",
"confirmationUrl": "https://example.com/confirm?token=abc123",
"expiresIn": 24
}`,
}
Return ONLY the JSON object - no explanations, no extra text.`,
placeholder: 'Describe the template variables...',
},
},
@@ -261,7 +267,9 @@ Example:
"contentType": "application/pdf",
"data": "JVBERi0xLjQK..."
}
]`,
]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the attachments...',
},
},
@@ -300,7 +308,9 @@ Example:
"amount": 49.99,
"currency": "USD",
"isUpgrade": true
}`,
}
Return ONLY the JSON object - no explanations, no extra text.`,
placeholder: 'Describe the event properties...',
},
},
@@ -349,6 +359,7 @@ Example:
{ label: 'Boolean', id: 'boolean' },
{ label: 'Date', id: 'date' },
],
value: () => 'string',
condition: {
field: 'operation',
value: 'create_contact_property',
@@ -363,6 +374,7 @@ Example:
{ label: 'All Properties', id: 'all' },
{ label: 'Custom Only', id: 'custom' },
],
value: () => 'all',
condition: {
field: 'operation',
value: 'list_contact_properties',
@@ -497,23 +509,28 @@ Example:
outputs: {
success: { type: 'boolean', description: 'Whether the operation succeeded' },
id: { type: 'string', description: 'Contact ID (create/update operations)' },
contacts: { type: 'json', description: 'Array of matching contacts (find operation)' },
contacts: {
type: 'json',
description:
'Array of matching contacts (id, email, firstName, lastName, source, subscribed, userGroup, userId, mailingLists, optInStatus)',
},
message: { type: 'string', description: 'Status message (delete operation)' },
mailingLists: {
type: 'json',
description: 'Array of mailing lists (list mailing lists operation)',
description: 'Array of mailing lists (id, name, description, isPublic)',
},
transactionalEmails: {
type: 'json',
description: 'Array of transactional email templates (list transactional emails operation)',
description: 'Array of transactional email templates (id, name, lastUpdated, dataVariables)',
},
pagination: {
type: 'json',
description: 'Pagination info (list transactional emails operation)',
description:
'Pagination info (totalResults, returnedResults, perPage, totalPages, nextCursor, nextPage)',
},
properties: {
type: 'json',
description: 'Array of contact properties (list contact properties operation)',
description: 'Array of contact properties (key, label, type)',
},
},
}

View File

@@ -1,272 +0,0 @@
import { ShortIoIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { ToolResponse } from '@/tools/types'
export const ShortIoBlock: BlockConfig<ToolResponse> = {
type: 'short_io',
name: 'Short.io',
description: 'Create and manage short links, domains, and analytics.',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Short.io to generate branded short links, list domains and links, delete links, generate QR codes, and view link statistics. Requires your Short.io Secret API Key.',
docsLink: 'https://docs.sim.ai/tools/short_io',
category: 'tools',
bgColor: '#FFFFFF',
icon: ShortIoIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Create Link', id: 'create_link' },
{ label: 'List Domains', id: 'list_domains' },
{ label: 'List Links', id: 'list_links' },
{ label: 'Delete Link', id: 'delete_link' },
{ label: 'Get QR Code', id: 'get_qr_code' },
{ label: 'Get Link Statistics', id: 'get_analytics' },
],
value: () => 'create_link',
},
{
id: 'apiKey',
title: 'Secret API Key',
type: 'short-input',
mode: 'basic',
required: true,
password: true,
placeholder: 'sk_...',
},
{
id: 'domain',
title: 'Custom Domain',
type: 'short-input',
placeholder: 'link.yourbrand.com',
condition: { field: 'operation', value: 'create_link' },
required: true,
},
{
id: 'originalURL',
title: 'Original URL',
type: 'long-input',
placeholder: 'https://www.example.com/very/long/path/to/page',
condition: { field: 'operation', value: 'create_link' },
required: true,
},
{
id: 'path',
title: 'Custom Path (Optional)',
type: 'short-input',
placeholder: 'my-custom-path',
condition: { field: 'operation', value: 'create_link' },
required: false,
mode: 'advanced',
},
{
id: 'domainId',
title: 'Domain ID',
type: 'short-input',
placeholder: '12345',
condition: { field: 'operation', value: 'list_links' },
required: true,
},
{
id: 'limit',
title: 'Limit (1150)',
type: 'short-input',
placeholder: '50',
condition: { field: 'operation', value: 'list_links' },
required: false,
mode: 'advanced',
},
{
id: 'dateSortOrder',
title: 'Sort Order',
type: 'dropdown',
options: [
{ label: 'Descending', id: 'desc' },
{ label: 'Ascending', id: 'asc' },
],
condition: { field: 'operation', value: 'list_links' },
required: false,
mode: 'advanced',
value: () => 'desc',
},
{
id: 'pageToken',
title: 'Page Token',
type: 'short-input',
placeholder: 'Next page token',
condition: { field: 'operation', value: 'list_links' },
required: false,
mode: 'advanced',
},
{
id: 'linkId',
title: 'Short.io Link ID',
type: 'short-input',
placeholder: 'lnk_abc123_abcdef',
condition: {
field: 'operation',
value: ['get_qr_code', 'get_analytics', 'delete_link'],
},
required: true,
},
{
id: 'type',
title: 'QR Format',
type: 'dropdown',
options: [
{ label: 'PNG', id: 'png' },
{ label: 'SVG', id: 'svg' },
],
condition: { field: 'operation', value: 'get_qr_code' },
required: false,
value: () => 'png',
},
{
id: 'size',
title: 'QR Size (199)',
type: 'short-input',
placeholder: '10',
condition: { field: 'operation', value: 'get_qr_code' },
required: false,
mode: 'advanced',
},
{
id: 'color',
title: 'QR Color (hex)',
type: 'short-input',
placeholder: '000000',
condition: { field: 'operation', value: 'get_qr_code' },
required: false,
mode: 'advanced',
},
{
id: 'backgroundColor',
title: 'Background Color (hex)',
type: 'short-input',
placeholder: 'FFFFFF',
condition: { field: 'operation', value: 'get_qr_code' },
required: false,
mode: 'advanced',
},
{
id: 'period',
title: 'Statistics Period',
type: 'dropdown',
options: [
{ label: 'Today', id: 'today' },
{ label: 'Yesterday', id: 'yesterday' },
{ label: 'This Week', id: 'week' },
{ label: 'Last 7 Days', id: 'last_7_days' },
{ label: 'This Month', id: 'month' },
{ label: 'Last Month', id: 'lastmonth' },
{ label: 'Last 30 Days', id: 'last_30_days' },
{ label: 'All Time', id: 'all_time' },
],
condition: { field: 'operation', value: 'get_analytics' },
required: true,
value: () => 'last_30_days',
},
{
id: 'tz',
title: 'Timezone',
type: 'short-input',
placeholder: 'UTC',
condition: { field: 'operation', value: 'get_analytics' },
required: false,
mode: 'advanced',
},
],
tools: {
access: [
'short_io_create_link',
'short_io_list_domains',
'short_io_list_links',
'short_io_delete_link',
'short_io_get_qr_code',
'short_io_get_analytics',
],
config: {
tool: (params) => `short_io_${params.operation}`,
params: (params) => {
const { apiKey, operation, size, domainId, limit, dateSortOrder, ...rest } = params
const out: Record<string, unknown> = { ...rest, apiKey }
if (size !== undefined && size !== '') {
const n = Number(size)
if (!Number.isNaN(n) && n >= 1 && n <= 99) out.size = n
}
if (operation === 'list_links' && domainId !== undefined && domainId !== '') {
const d = Number(domainId)
if (!Number.isNaN(d)) out.domainId = d
}
if (operation === 'list_links' && limit !== undefined && limit !== '') {
const l = Number(limit)
if (!Number.isNaN(l) && l >= 1 && l <= 150) out.limit = l
}
if (operation === 'list_links' && dateSortOrder !== undefined && dateSortOrder !== '') {
out.dateSortOrder = dateSortOrder
}
return out
},
},
},
inputs: {
apiKey: { type: 'string', description: 'Secret API Key' },
operation: { type: 'string', description: 'Short.io operation to perform' },
domain: { type: 'string', description: 'Your registered Short.io custom domain' },
originalURL: { type: 'string', description: 'The original long URL to shorten' },
path: { type: 'string', description: 'Optional custom path for the short link' },
domainId: { type: 'number', description: 'Domain ID (from List Domains)' },
limit: { type: 'number', description: 'Max links to return (1150)' },
dateSortOrder: { type: 'string', description: 'Sort order: asc or desc' },
pageToken: { type: 'string', description: 'Pagination token for List Links' },
linkId: { type: 'string', description: 'The Short.io internal link ID string' },
type: { type: 'string', description: 'QR output format: png or svg' },
size: { type: 'number', description: 'QR size 199' },
color: { type: 'string', description: 'QR color hex' },
backgroundColor: { type: 'string', description: 'QR background color hex' },
period: {
type: 'string',
description: 'Statistics period (e.g. today, last_30_days, all_time)',
},
tz: { type: 'string', description: 'Timezone for statistics (e.g. UTC)' },
},
outputs: {
shortURL: { type: 'string', description: 'The generated short link' },
idString: { type: 'string', description: 'The Short.io link ID' },
originalURL: { type: 'string', description: 'The original long URL' },
path: { type: 'string', description: 'The path/slug of the short link' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
domains: { type: 'array', description: 'List of domains (from List Domains)' },
count: { type: 'number', description: 'Number of domains or links returned' },
links: { type: 'array', description: 'List of links (from List Links)' },
nextPageToken: { type: 'string', description: 'Pagination token for next page' },
deleted: { type: 'boolean', description: 'Whether the link was deleted' },
file: { type: 'file', description: 'Generated QR code image file' },
totalClicks: { type: 'number', description: 'Total clicks' },
humanClicks: { type: 'number', description: 'Human clicks' },
totalClicksChange: { type: 'string', description: 'Change in total clicks vs previous period' },
humanClicksChange: { type: 'string', description: 'Change in human clicks vs previous period' },
referer: { type: 'array', description: 'Referrer breakdown (referer, score)' },
country: { type: 'array', description: 'Country breakdown (countryName, country, score)' },
browser: { type: 'array', description: 'Browser breakdown (browser, score)' },
os: { type: 'array', description: 'OS breakdown (os, score)' },
city: { type: 'array', description: 'City breakdown (city, name, countryCode, score)' },
device: { type: 'array', description: 'Device breakdown' },
social: { type: 'array', description: 'Social source breakdown (social, score)' },
utmMedium: { type: 'array', description: 'UTM medium breakdown' },
utmSource: { type: 'array', description: 'UTM source breakdown' },
utmCampaign: { type: 'array', description: 'UTM campaign breakdown' },
clickStatistics: {
type: 'json',
description: 'Time-series click data (datasets with x/y per interval)',
},
interval: {
type: 'json',
description: 'Date range (startDate, endDate, prevStartDate, prevEndDate, tz)',
},
},
}

View File

@@ -141,7 +141,6 @@ import { ServiceNowBlock } from '@/blocks/blocks/servicenow'
import { SftpBlock } from '@/blocks/blocks/sftp'
import { SharepointBlock } from '@/blocks/blocks/sharepoint'
import { ShopifyBlock } from '@/blocks/blocks/shopify'
import { ShortIoBlock } from '@/blocks/blocks/short_io'
import { SimilarwebBlock } from '@/blocks/blocks/similarweb'
import { SlackBlock } from '@/blocks/blocks/slack'
import { SmtpBlock } from '@/blocks/blocks/smtp'
@@ -346,7 +345,6 @@ export const registry: Record<string, BlockConfig> = {
sftp: SftpBlock,
sharepoint: SharepointBlock,
shopify: ShopifyBlock,
short_io: ShortIoBlock,
similarweb: SimilarwebBlock,
slack: SlackBlock,
smtp: SmtpBlock,

View File

@@ -76,6 +76,7 @@ export function ApiIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function ConditionalIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3997,7 +3998,7 @@ export function LoopsIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 256 256' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fill='currentColor'
fill='#FD4E00'
d='M192.352 88.042c0-7.012-5.685-12.697-12.697-12.697s-12.697 5.685-12.697 12.697c0 .634.052 1.255.142 1.866a25.248 25.248 0 0 0-4.9-.49c-14.006 0-25.36 11.354-25.36 25.36 0 1.63.16 3.222.456 4.765a37.8 37.8 0 0 0-9.296-1.173c-20.95 0-37.935 16.985-37.935 37.935S107.05 194.24 128 194.24s37.935-16.985 37.935-37.935a37.7 37.7 0 0 0-3.78-16.555 25.2 25.2 0 0 0 12.487-3.336 25.2 25.2 0 0 0 4.558 3.336v.02c14.006 0 25.36-11.354 25.36-25.36 0-12.48-9.018-22.855-20.888-24.996a12.6 12.6 0 0 0 8.68-11.972m-77.05 68.263c0-7.012 5.685-12.697 12.697-12.697s12.697 5.685 12.697 12.697c0 7.013-5.685 12.697-12.697 12.697s-12.697-5.685-12.697-12.697'
/>
</svg>
@@ -4541,7 +4542,7 @@ export function DatabricksIcon(props: SVGProps<SVGSVGElement>) {
<svg {...props} viewBox='0 0 241 266' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M228.085 109.654L120.615 171.674L5.53493 105.41L0 108.475V156.582L120.615 225.911L228.085 164.128V189.596L120.615 251.615L5.53493 185.351L0 188.417V196.67L120.615 266L241 196.67V148.564L235.465 145.498L120.615 211.527L12.9148 149.743V124.275L120.615 186.059L241 116.729V69.3298L235.004 65.7925L120.615 131.585L18.4498 73.1028L120.615 14.3848L204.562 62.7269L211.942 58.4823V52.5869L120.615 0L0 69.3298V76.8759L120.615 146.206L228.085 84.1862V109.654Z'
fill='#F9F7F4'
fill='#FF3621'
/>
</svg>
)
@@ -6011,20 +6012,3 @@ export function HexIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function ShortIoIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 64 65' fill='none' xmlns='http://www.w3.org/2000/svg'>
<rect width='64' height='65' fill='#FFFFFF' />
<path
d='M41.1 45.7c0 2-.8 3.5-2.5 4.6-1.6 1-3.8 1.6-6.5 1.6-3.4 0-6-.8-8-2.3-2-1.6-3-3.6-3.2-6.1l-16.3-.4c0 4.1 1.2 7.8 3.6 11.1A24 24 0 0 0 18 62c2.2 1 4.5 1.7 7 2.2l.4.1H0V.2h24.9A25.4 25.4 0 0 0 9.3 9.5C7.1 12.5 6 15.9 6 19.7c0 4.2.9 7.6 2.6 10.1 1.7 2.5 4 4.4 6.8 5.7 2.8 1.3 6.3 2.3 10.6 3.2 4.4.9 7.5 1.6 9.5 2.2 1.9.5 3.3 1.1 4.3 1.9.8.6 1.3 1.6 1.3 2.9Z'
fill='#0BB07D'
/>
<path d='M25.3 64.2h-.6l.1-.1.5.1Z' fill='#33333D' />
<path
d='M64 64.2H38.1a28 28 0 0 0 7.1-2.2 23 23 0 0 0 9.4-7.6c2.2-3.2 3.4-6.8 3.4-10.8a17 17 0 0 0-2.6-9.8c-1.7-2.4-4-4.3-6.9-5.5a54.4 54.4 0 0 0-10.8-3.1c-4.3-.8-7.3-1.5-9.2-2.1a12 12 0 0 1-4.2-1.8c-.9-.7-1.3-1.7-1.3-3 0-1.9.7-3.3 2.2-4.3 1.5-1 3.4-1.5 5.8-1.5 2.7 0 4.9.7 6.5 2.1a7.8 7.8 0 0 1 2.7 5.4h16.4c0-3.8-1.1-7.3-3.3-10.5a23 23 0 0 0-9.1-7.4c-2.1-1-4.4-1.7-6.8-2.1H64v64.2Z'
fill='#383738'
/>
</svg>
)
}

View File

@@ -1,3 +1,4 @@
import { useCallback } from 'react'
import { createLogger } from '@sim/logger'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
@@ -209,6 +210,13 @@ export function useChatDeploymentInfo(workflowId: string | null, options?: { ena
enabled: Boolean(chatId) && statusQuery.isSuccess && (options?.enabled ?? true),
})
const refetch = useCallback(async () => {
const statusResult = await statusQuery.refetch()
if (statusResult.data?.deployment?.id) {
await detailQuery.refetch()
}
}, [statusQuery.refetch, detailQuery.refetch])
return {
isLoading:
statusQuery.isLoading || Boolean(statusQuery.data?.isDeployed && detailQuery.isLoading),
@@ -216,12 +224,7 @@ export function useChatDeploymentInfo(workflowId: string | null, options?: { ena
error: statusQuery.error ?? detailQuery.error,
chatExists: statusQuery.data?.isDeployed ?? false,
existingChat: detailQuery.data ?? null,
refetch: async () => {
await statusQuery.refetch()
if (statusQuery.data?.deployment?.id) {
await detailQuery.refetch()
}
},
refetch,
}
}

View File

@@ -31,7 +31,7 @@ export const greenhouseGetApplicationTool: ToolConfig<
request: {
url: (params: GreenhouseGetApplicationParams) =>
`https://harvest.greenhouse.io/v1/applications/${params.applicationId}`,
`https://harvest.greenhouse.io/v1/applications/${params.applicationId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetApplicationParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,

View File

@@ -31,7 +31,7 @@ export const greenhouseGetCandidateTool: ToolConfig<
request: {
url: (params: GreenhouseGetCandidateParams) =>
`https://harvest.greenhouse.io/v1/candidates/${params.candidateId}`,
`https://harvest.greenhouse.io/v1/candidates/${params.candidateId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetCandidateParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,

View File

@@ -25,7 +25,7 @@ export const greenhouseGetJobTool: ToolConfig<GreenhouseGetJobParams, Greenhouse
request: {
url: (params: GreenhouseGetJobParams) =>
`https://harvest.greenhouse.io/v1/jobs/${params.jobId}`,
`https://harvest.greenhouse.io/v1/jobs/${params.jobId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetJobParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,

View File

@@ -25,7 +25,7 @@ export const greenhouseGetUserTool: ToolConfig<GreenhouseGetUserParams, Greenhou
request: {
url: (params: GreenhouseGetUserParams) =>
`https://harvest.greenhouse.io/v1/users/${params.userId}`,
`https://harvest.greenhouse.io/v1/users/${params.userId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetUserParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,

View File

@@ -1,14 +1,14 @@
import { greenhouseGetApplicationTool } from '@/tools/greenhouse/get-application'
import { greenhouseGetCandidateTool } from '@/tools/greenhouse/get-candidate'
import { greenhouseGetJobTool } from '@/tools/greenhouse/get-job'
import { greenhouseGetUserTool } from '@/tools/greenhouse/get-user'
import { greenhouseListApplicationsTool } from '@/tools/greenhouse/list-applications'
import { greenhouseListCandidatesTool } from '@/tools/greenhouse/list-candidates'
import { greenhouseListDepartmentsTool } from '@/tools/greenhouse/list-departments'
import { greenhouseListJobStagesTool } from '@/tools/greenhouse/list-job-stages'
import { greenhouseListJobsTool } from '@/tools/greenhouse/list-jobs'
import { greenhouseListOfficesTool } from '@/tools/greenhouse/list-offices'
import { greenhouseListUsersTool } from '@/tools/greenhouse/list-users'
import { greenhouseGetApplicationTool } from '@/tools/greenhouse/get_application'
import { greenhouseGetCandidateTool } from '@/tools/greenhouse/get_candidate'
import { greenhouseGetJobTool } from '@/tools/greenhouse/get_job'
import { greenhouseGetUserTool } from '@/tools/greenhouse/get_user'
import { greenhouseListApplicationsTool } from '@/tools/greenhouse/list_applications'
import { greenhouseListCandidatesTool } from '@/tools/greenhouse/list_candidates'
import { greenhouseListDepartmentsTool } from '@/tools/greenhouse/list_departments'
import { greenhouseListJobStagesTool } from '@/tools/greenhouse/list_job_stages'
import { greenhouseListJobsTool } from '@/tools/greenhouse/list_jobs'
import { greenhouseListOfficesTool } from '@/tools/greenhouse/list_offices'
import { greenhouseListUsersTool } from '@/tools/greenhouse/list_users'
export {
greenhouseGetApplicationTool,

View File

@@ -87,7 +87,7 @@ export const greenhouseListCandidatesTool: ToolConfig<
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
if (params.job_id) url.searchParams.append('job_id', params.job_id)
if (params.email) url.searchParams.append('email', params.email)
if (params.email) url.searchParams.append('email_address', params.email)
if (params.candidate_ids) url.searchParams.append('candidate_ids', params.candidate_ids)
return url.toString()
},

View File

@@ -45,7 +45,7 @@ export const greenhouseListJobStagesTool: ToolConfig<
request: {
url: (params: GreenhouseListJobStagesParams) => {
const url = new URL(`https://harvest.greenhouse.io/v1/jobs/${params.jobId}/stages`)
const url = new URL(`https://harvest.greenhouse.io/v1/jobs/${params.jobId.trim()}/stages`)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
return url.toString()

View File

@@ -95,13 +95,13 @@ export const loopsCreateContactTool: ToolConfig<
Object.assign(body, props)
}
body.email = params.email
if (params.firstName) body.firstName = params.firstName
if (params.lastName) body.lastName = params.lastName
if (params.source) body.source = params.source
body.email = params.email.trim()
if (params.firstName) body.firstName = params.firstName.trim()
if (params.lastName) body.lastName = params.lastName.trim()
if (params.source) body.source = params.source.trim()
if (params.subscribed != null) body.subscribed = params.subscribed
if (params.userGroup) body.userGroup = params.userGroup
if (params.userId) body.userId = params.userId
if (params.userGroup) body.userGroup = params.userGroup.trim()
if (params.userId) body.userId = params.userId.trim()
if (params.mailingLists) {
body.mailingLists =

View File

@@ -46,8 +46,8 @@ export const loopsDeleteContactTool: ToolConfig<
throw new Error('At least one of email or userId is required to delete a contact')
}
const body: Record<string, unknown> = {}
if (params.email) body.email = params.email
if (params.userId) body.userId = params.userId
if (params.email) body.email = params.email.trim()
if (params.userId) body.userId = params.userId.trim()
return body
},
},

View File

@@ -37,8 +37,8 @@ export const loopsFindContactTool: ToolConfig<LoopsFindContactParams, LoopsFindC
throw new Error('At least one of email or userId is required to find a contact')
}
const base = 'https://app.loops.so/api/v1/contacts/find'
if (params.email) return `${base}?email=${encodeURIComponent(params.email)}`
return `${base}?userId=${encodeURIComponent(params.userId!)}`
if (params.email) return `${base}?email=${encodeURIComponent(params.email.trim())}`
return `${base}?userId=${encodeURIComponent(params.userId!.trim())}`
},
method: 'GET',
headers: (params) => ({

View File

@@ -64,8 +64,8 @@ export const loopsSendEventTool: ToolConfig<LoopsSendEventParams, LoopsSendEvent
eventName: params.eventName,
}
if (params.email) body.email = params.email
if (params.userId) body.userId = params.userId
if (params.email) body.email = params.email.trim()
if (params.userId) body.userId = params.userId.trim()
if (params.eventProperties) {
body.eventProperties =

View File

@@ -64,8 +64,8 @@ export const loopsSendTransactionalEmailTool: ToolConfig<
}),
body: (params) => {
const body: Record<string, unknown> = {
email: params.email,
transactionalId: params.transactionalId,
email: params.email.trim(),
transactionalId: params.transactionalId.trim(),
}
if (params.dataVariables) {

View File

@@ -99,13 +99,13 @@ export const loopsUpdateContactTool: ToolConfig<
Object.assign(body, props)
}
if (params.email) body.email = params.email
if (params.userId) body.userId = params.userId
if (params.firstName) body.firstName = params.firstName
if (params.lastName) body.lastName = params.lastName
if (params.source) body.source = params.source
if (params.email) body.email = params.email.trim()
if (params.userId) body.userId = params.userId.trim()
if (params.firstName) body.firstName = params.firstName.trim()
if (params.lastName) body.lastName = params.lastName.trim()
if (params.source) body.source = params.source.trim()
if (params.subscribed != null) body.subscribed = params.subscribed
if (params.userGroup) body.userGroup = params.userGroup
if (params.userGroup) body.userGroup = params.userGroup.trim()
if (params.mailingLists) {
body.mailingLists =

View File

@@ -1744,14 +1744,6 @@ import {
shopifyUpdateOrderTool,
shopifyUpdateProductTool,
} from '@/tools/shopify'
import {
shortIoCreateLinkTool,
shortIoDeleteLinkTool,
shortIoGetAnalyticsTool,
shortIoGetQrCodeTool,
shortIoListDomainsTool,
shortIoListLinksTool,
} from '@/tools/short_io'
import {
similarwebBounceRateTool,
similarwebPagesPerVisitTool,
@@ -2581,12 +2573,6 @@ export const tools: Record<string, ToolConfig> = {
tavily_extract: tavilyExtractTool,
tavily_crawl: tavilyCrawlTool,
tavily_map: tavilyMapTool,
short_io_create_link: shortIoCreateLinkTool,
short_io_list_domains: shortIoListDomainsTool,
short_io_list_links: shortIoListLinksTool,
short_io_delete_link: shortIoDeleteLinkTool,
short_io_get_qr_code: shortIoGetQrCodeTool,
short_io_get_analytics: shortIoGetAnalyticsTool,
supabase_query: supabaseQueryTool,
supabase_insert: supabaseInsertTool,
supabase_get_row: supabaseGetRowTool,

View File

@@ -1,82 +0,0 @@
import type { ShortIoCreateLinkParams } from '@/tools/short_io/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const shortIoCreateLinkTool: ToolConfig<ShortIoCreateLinkParams, ToolResponse> = {
id: 'short_io_create_link',
name: 'Short.io Create Link',
description: 'Create a short link using your Short.io custom domain.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Short.io Secret API Key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your registered Short.io custom domain',
},
originalURL: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The long URL to shorten',
},
path: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional custom path for the short link',
},
},
request: {
url: 'https://api.short.io/links',
method: 'POST',
headers: (params) => ({
Authorization: params.apiKey,
'Content-Type': 'application/json',
}),
body: (params) => {
const bodyData: Record<string, string> = {
domain: params.domain,
originalURL: params.originalURL,
}
if (params.path) {
bodyData.path = params.path
}
return bodyData
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text().catch(() => response.statusText)
return {
success: false,
output: { shortURL: '', idString: '', originalURL: '', path: null, createdAt: null },
error: `Failed to create short link: ${errorText}`,
}
}
const data = await response.json().catch(() => ({}))
return {
success: true,
output: {
shortURL: data.shortURL,
idString: data.idString,
originalURL: data.originalURL,
path: data.path ?? null,
createdAt: data.createdAt ?? null,
},
}
},
outputs: {
shortURL: { type: 'string', description: 'The generated short link URL' },
idString: { type: 'string', description: 'The unique Short.io link ID string' },
originalURL: { type: 'string', description: 'The original long URL' },
path: { type: 'string', description: 'The path/slug of the short link', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
},
}

View File

@@ -1,48 +0,0 @@
import type { ShortIoDeleteLinkParams } from '@/tools/short_io/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const shortIoDeleteLinkTool: ToolConfig<ShortIoDeleteLinkParams, ToolResponse> = {
id: 'short_io_delete_link',
name: 'Short.io Delete Link',
description: 'Delete a short link by ID (e.g. lnk_abc123_abcdef). Rate limit 20/s.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Short.io Secret API Key',
},
linkId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Link ID to delete',
},
},
request: {
url: (params) => `https://api.short.io/links/${encodeURIComponent(params.linkId.trim())}`,
method: 'DELETE',
headers: (params) => ({
Authorization: params.apiKey,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const err = await response.text().catch(() => response.statusText)
return { success: false, output: { deleted: false, idString: '' }, error: err }
}
const data = await response.json().catch(() => ({}))
return {
success: true,
output: {
deleted: data.success === true,
idString: data.idString ?? '',
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the link was deleted' },
idString: { type: 'string', description: 'Deleted link ID', optional: true },
},
}

View File

@@ -1,112 +0,0 @@
import type { ShortIoGetAnalyticsParams } from '@/tools/short_io/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
const STATS_PERIOD_MAP: Record<string, string> = {
today: 'today',
yesterday: 'yesterday',
last_7_days: 'last7',
last_30_days: 'last30',
all_time: 'total',
week: 'week',
month: 'month',
lastmonth: 'lastmonth',
}
export const shortIoGetAnalyticsTool: ToolConfig<ShortIoGetAnalyticsParams, ToolResponse> = {
id: 'short_io_get_analytics',
name: 'Short.io Get Link Statistics',
description:
'Fetch click statistics for a Short.io link (Statistics API: totalClicks, humanClicks, referer, country, etc.).',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Short.io Secret API Key',
},
linkId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Link ID' },
period: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Period: today, yesterday, last7, last30, total, week, month, lastmonth',
},
tz: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Timezone (default UTC)',
},
},
request: {
url: (params) => {
const base = `https://statistics.short.io/statistics/link/${encodeURIComponent(params.linkId.trim())}`
const period = STATS_PERIOD_MAP[params.period] ?? params.period ?? 'last30'
const q = new URLSearchParams({ period })
if (params.tz) q.set('tz', params.tz)
return `${base}?${q.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const err = await response.text().catch(() => response.statusText)
return { success: false, output: { totalClicks: 0, humanClicks: 0 }, error: err }
}
const data = await response.json().catch(() => ({}))
const totalClicks = data.totalClicks ?? 0
const humanClicks = data.humanClicks ?? totalClicks
return {
success: true,
output: {
totalClicks,
humanClicks,
totalClicksChange: data.totalClicksChange ?? null,
humanClicksChange: data.humanClicksChange ?? null,
referer: data.referer ?? [],
country: data.country ?? [],
browser: data.browser ?? [],
os: data.os ?? [],
city: data.city ?? [],
device: data.device ?? [],
social: data.social ?? [],
utmMedium: data.utm_medium ?? [],
utmSource: data.utm_source ?? [],
utmCampaign: data.utm_campaign ?? [],
clickStatistics: data.clickStatistics ?? null,
interval: data.interval ?? null,
},
}
},
outputs: {
totalClicks: { type: 'number', description: 'Total clicks' },
humanClicks: { type: 'number', description: 'Human clicks' },
totalClicksChange: { type: 'string', description: 'Change vs previous period', optional: true },
humanClicksChange: { type: 'string', description: 'Human clicks change', optional: true },
referer: { type: 'array', description: 'Referrer breakdown (referer, score)' },
country: { type: 'array', description: 'Country breakdown (countryName, country, score)' },
browser: { type: 'array', description: 'Browser breakdown (browser, score)' },
os: { type: 'array', description: 'OS breakdown (os, score)' },
city: { type: 'array', description: 'City breakdown (city, name, countryCode, score)' },
device: { type: 'array', description: 'Device breakdown' },
social: { type: 'array', description: 'Social source breakdown (social, score)' },
utmMedium: { type: 'array', description: 'UTM medium breakdown' },
utmSource: { type: 'array', description: 'UTM source breakdown' },
utmCampaign: { type: 'array', description: 'UTM campaign breakdown' },
clickStatistics: {
type: 'object',
description: 'Time-series click data (datasets with x/y points per interval)',
optional: true,
},
interval: {
type: 'object',
description: 'Date range (startDate, endDate, prevStartDate, prevEndDate, tz)',
optional: true,
},
},
}

View File

@@ -1,93 +0,0 @@
import type { ShortIoGetQrParams } from '@/tools/short_io/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const shortIoGetQrCodeTool: ToolConfig<ShortIoGetQrParams, ToolResponse> = {
id: 'short_io_get_qr_code',
name: 'Short.io Generate QR Code',
description: 'Generate a QR code for a Short.io link (POST /links/qr/{linkIdString}).',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Short.io Secret API Key',
},
linkId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Link ID (e.g. lnk_abc123_abcdef)',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'QR color hex (e.g. 000000)',
},
backgroundColor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Background color hex (e.g. FFFFFF)',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'QR size 199',
},
type: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Output format: png or svg',
},
useDomainSettings: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Use domain settings (default true)',
},
},
request: {
url: '/api/tools/short_io/qr',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
apiKey: params.apiKey,
linkId: params.linkId,
useDomainSettings: params.useDomainSettings ?? true,
}
if (params.color != null && params.color !== '') body.color = params.color
if (params.backgroundColor != null && params.backgroundColor !== '')
body.backgroundColor = params.backgroundColor
if (params.size != null && params.size >= 1 && params.size <= 99) body.size = params.size
if (params.type === 'svg' || params.type === 'png') body.type = params.type
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json().catch(() => ({}))
if (!response.ok || !data.success) {
return {
success: false,
output: {},
error: data.error || response.statusText,
}
}
return {
success: true,
output: data.output,
}
},
outputs: {
file: {
type: 'file',
description: 'Generated QR code image file',
},
},
}

View File

@@ -1,7 +0,0 @@
export { shortIoCreateLinkTool } from '@/tools/short_io/create_link'
export { shortIoDeleteLinkTool } from '@/tools/short_io/delete_link'
export { shortIoGetAnalyticsTool } from '@/tools/short_io/get_analytics'
export { shortIoGetQrCodeTool } from '@/tools/short_io/get_qr_code'
export { shortIoListDomainsTool } from '@/tools/short_io/list_domains'
export { shortIoListLinksTool } from '@/tools/short_io/list_links'
export * from './types'

View File

@@ -1,41 +0,0 @@
import type { ShortIoListDomainsParams } from '@/tools/short_io/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const shortIoListDomainsTool: ToolConfig<ShortIoListDomainsParams, ToolResponse> = {
id: 'short_io_list_domains',
name: 'Short.io List Domains',
description: 'List Short.io domains. Returns domain IDs and details for use in List Links.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Short.io Secret API Key',
},
},
request: {
url: 'https://api.short.io/api/domains',
method: 'GET',
headers: (params) => ({
Authorization: params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const err = await response.text().catch(() => response.statusText)
return { success: false, output: { domains: [], count: 0 }, error: err }
}
const data = await response.json().catch(() => ({}))
const list = Array.isArray(data) ? data : (data.domains ?? data.list ?? [])
return {
success: true,
output: { domains: list, count: list.length },
}
},
outputs: {
domains: { type: 'array', description: 'List of domain objects (id, hostname, etc.)' },
count: { type: 'number', description: 'Number of domains' },
},
}

View File

@@ -1,86 +0,0 @@
import type { ShortIoListLinksParams } from '@/tools/short_io/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const shortIoListLinksTool: ToolConfig<ShortIoListLinksParams, ToolResponse> = {
id: 'short_io_list_links',
name: 'Short.io List Links',
description:
'List short links for a domain. Requires domain_id (from List Domains or dashboard). Max 150 per request.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Short.io Secret API Key',
},
domainId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Domain ID (from List Domains)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Max links to return (1150)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token from previous response',
},
dateSortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by date: asc or desc',
},
},
request: {
url: (params) => {
const u = new URL('https://api.short.io/api/links')
u.searchParams.set('domain_id', String(params.domainId))
if (params.limit != null && params.limit >= 1 && params.limit <= 150) {
u.searchParams.set('limit', String(params.limit))
}
if (params.pageToken) u.searchParams.set('pageToken', params.pageToken)
if (params.dateSortOrder === 'asc' || params.dateSortOrder === 'desc') {
u.searchParams.set('dateSortOrder', params.dateSortOrder)
}
return u.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const err = await response.text().catch(() => response.statusText)
return { success: false, output: { links: [], count: 0 }, error: err }
}
const data = await response.json().catch(() => ({}))
const links = data.links ?? []
const count = data.count ?? links.length
return {
success: true,
output: {
links,
count,
nextPageToken: data.nextPageToken ?? undefined,
},
}
},
outputs: {
links: {
type: 'array',
description: 'List of link objects (idString, shortURL, originalURL, path, etc.)',
},
count: { type: 'number', description: 'Number of links returned' },
nextPageToken: { type: 'string', description: 'Token for next page', optional: true },
},
}

View File

@@ -1,40 +0,0 @@
export interface ShortIoCreateLinkParams {
apiKey: string
domain: string
originalURL: string
path?: string
}
export interface ShortIoListDomainsParams {
apiKey: string
}
export interface ShortIoListLinksParams {
apiKey: string
domainId: number
limit?: number
pageToken?: string
dateSortOrder?: 'asc' | 'desc'
}
export interface ShortIoDeleteLinkParams {
apiKey: string
linkId: string
}
export interface ShortIoGetQrParams {
apiKey: string
linkId: string
color?: string
backgroundColor?: string
size?: number
type?: 'png' | 'svg'
useDomainSettings?: boolean
}
export interface ShortIoGetAnalyticsParams {
apiKey: string
linkId: string
period: string
tz?: string
}