Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2725c7f
Add Yutori integration
deviparikh Mar 25, 2026
3d8763e
Address CodeRabbit review feedback
deviparikh Mar 25, 2026
90ba9c6
Merge upstream/master into feat/yutori-integration
deviparikh Mar 31, 2026
0792ba7
Fix: do not advance lastTimestamp when pagination is truncated
deviparikh Mar 31, 2026
2c9dae0
Harden timestamp handling: normalize and sort instead of assuming API…
deviparikh Mar 31, 2026
b89254a
Fix: emit ts as numeric epoch ms via toEpochMs()
deviparikh Mar 31, 2026
8fb397d
Fix: handle numeric timestamp strings in toEpochMs
deviparikh Apr 1, 2026
5aac3ca
Merge branch 'master' into feat/yutori-integration
GTFalcao Apr 2, 2026
d0b4860
Redesign new-scout-update as lifecycle source (create on deploy, dele…
deviparikh Apr 2, 2026
d1225b8
Remove create-scout and delete-scout actions
deviparikh Apr 2, 2026
689a5a9
Remove list-scouts, mark-scout-done, restart-scout actions; add user-…
deviparikh Apr 2, 2026
eeecfb2
Refactor Yutori scout lifecycle source
deviparikh Apr 3, 2026
d3b6655
Update lockfile for Yutori integration
deviparikh Apr 3, 2026
ce2b3de
Merge upstream master into Yutori integration
deviparikh Apr 3, 2026
2176630
Merge upstream master into feat/yutori-integration
deviparikh Apr 20, 2026
a7b4f44
Update components/yutori/actions/get-scout/get-scout.mjs
deviparikh Apr 21, 2026
90aca0b
Update components/yutori/actions/get-scout-updates/get-scout-updates.mjs
deviparikh Apr 21, 2026
f6d45ec
Update components/yutori/actions/get-research-task-result/get-researc…
deviparikh Apr 21, 2026
95c528a
Update components/yutori/sources/new-scout-update/new-scout-update.mjs
deviparikh Apr 21, 2026
eb5fb2d
Update components/yutori/sources/new-scout-update/new-scout-update.mjs
deviparikh Apr 21, 2026
cdaf485
Update components/yutori/actions/get-browsing-task-result/get-browsin…
deviparikh Apr 21, 2026
0441592
docs(yutori): simplify README overview
deviparikh Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions components/yutori/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Yutori

## Overview

[Yutori](https://yutori.com) is reimagining how people interact with the web. The [Yutori API](https://yutori.com/api) is an AI web agent platform. Give it a task and it navigates websites, fills forms, extracts data, and completes multi-step workflows using a real cloud browser — or runs deep research across 100+ sources. You can also set up Scouts: recurring monitors that watch any part of the web on a schedule and notify you when something relevant happens.

All components authenticate with a Yutori API key. Get yours at [platform.yutori.com](https://platform.yutori.com). You'll enter it once in Pipedream Connect and it applies to all Yutori actions in your workflows.

## Example Use Cases

- **Competitive monitoring** *(Scout)*: Add the **New Scout Update** trigger, set the query to "alert me when [competitor]'s pricing page changes", and add a Slack step — deploy once and get notified automatically whenever prices change.
- **Job alert** *(Scout)*: Add the **New Scout Update** trigger, set the query to "notify me when new Python engineer roles appear on Stripe's careers page", and add a Send Email step — you'll get an email for every new matching role.
- **Sales research** *(Research)*: Trigger a workflow on a schedule or webhook, add a **Run Research Task** step with a company name as the query, wait for the result, and push the findings summary into Notion or HubSpot before a sales call.
- **Web automation** *(Browsing)*: Trigger a workflow however you like, add a **Run Browsing Task** step with a URL and plain-English instructions (e.g. "fill out this form" or "extract the pricing table"), and pipe the result into a Google Sheet.

## Getting Started

1. Sign up and get an API key at [platform.yutori.com](https://platform.yutori.com)
2. In Pipedream, add a new Yutori connection and enter your API key
3. Add any Yutori action or trigger to your workflow

**Getting browsing and research results (two options):**
- **Webhook (recommended)**: Create a second Pipedream workflow with an HTTP trigger, copy its URL, and paste it into the **Webhook URL** field on Run Browsing Task or Run Research Task. Yutori pushes the result instantly when the task completes.
- **Poll**: Add a Delay step (~15 min) after starting the task, then use **Get Browsing Task Result** or **Get Research Task Result** to fetch the outcome.

**Getting scout findings:** The **New Scout Update** trigger polls automatically (default every 15 minutes) — no extra setup needed.

## Troubleshooting

- **Tasks take 5–15 minutes** — don't poll for results immediately; use the webhook pattern or add a Delay step
- **Webhook not firing** — make sure the webhook URL is publicly accessible (a Pipedream HTTP trigger URL works out of the box)
- **401 Unauthorized** — verify your API key is active at [platform.yutori.com](https://platform.yutori.com)
- **Scout not producing updates** — check that the workflow is still deployed and enabled in Pipedream
- **Need help?** — email [support@yutori.com](mailto:support@yutori.com) or check the [documentation](https://docs.yutori.com)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import yutori from "../../yutori.app.mjs";

export default {
key: "yutori-get-browsing-task-result",
name: "Get Browsing Task Result",
description: "Fetch the current status and result of a browsing task started with **Run Browsing Task**. Place a **Delay** step (15 minutes recommended) before this action to give the task time to complete. If the task is still running, the step returns the current status — re-run or increase the delay if needed. [See the documentation](https://docs.yutori.com/reference/browsing-get)",
version: "0.0.1",
type: "action",
annotations: {
openWorldHint: true,
destructiveHint: false,
readOnlyHint: true,
},
props: {
yutori,
taskId: {
type: "string",
label: "Task ID",
description: "The task ID returned by the **Run Browsing Task** step, e.g. `{{steps.run_browsing_task.$return_value.task_id}}`",
},
},
async run({ $ }) {
const result = await this.yutori.getBrowsingTask($, this.taskId);
$.export("$summary", `Browsing task ${result.status}: ${this.taskId}`);
return result;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import yutori from "../../yutori.app.mjs";

export default {
key: "yutori-get-research-task-result",
name: "Get Research Task Result",
description: "Fetch the current status and result of a research task started with **Run Research Task**. Place a **Delay** step (15 minutes recommended) before this action to give the task time to complete. Research tasks typically take 5–15 minutes. If the task is still running, the step returns the current status — re-run or increase the delay if needed. [See the documentation](https://docs.yutori.com/reference/research-get)",
version: "0.0.1",
type: "action",
annotations: {
openWorldHint: true,
destructiveHint: false,
readOnlyHint: true,
},
props: {
yutori,
taskId: {
type: "string",
label: "Task ID",
description: "The task ID returned by the **Run Research Task** step, e.g. `{{steps.run_research_task.$return_value.task_id}}`",
},
},
async run({ $ }) {
const result = await this.yutori.getResearchTask($, this.taskId);
$.export("$summary", `Research task ${result.status}: ${this.taskId}`);
return result;
},
};
46 changes: 46 additions & 0 deletions components/yutori/actions/get-scout-updates/get-scout-updates.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import yutori from "../../yutori.app.mjs";

export default {
key: "yutori-get-scout-updates",
name: "Get Scout Updates",
description: "Fetch the latest findings from a specific Yutori Scout. Returns updates in reverse chronological order. Use the **New Scout Update** trigger for event-driven workflows; use this action when you need to pull findings on demand. [See the documentation](https://docs.yutori.com/reference/scouting-updates-get)",
version: "0.0.1",
type: "action",
annotations: {
openWorldHint: true,
destructiveHint: false,
readOnlyHint: true,
},
props: {
yutori,
scoutId: {
type: "string",
label: "Scout ID",
description: "The ID of the scout whose updates to fetch, e.g. from the Yutori dashboard or a previous **Get Scout** step",
},
pageSize: {
type: "integer",
label: "Limit",
description: "Maximum number of updates to return (1–100)",
optional: true,
default: 10,
min: 1,
max: 100,
},
},
async run({ $ }) {
const params = {};
if (this.pageSize) params.page_size = this.pageSize;

const result = await this.yutori.getScoutUpdates($, this.scoutId, params);
const updates = result?.updates ?? result?.items ?? [];
const count = Array.isArray(updates)
? updates.length
: "?";

$.export("$summary", `Retrieved ${count} update${count === 1
? ""
: "s"} for scout ${this.scoutId}`);
return result;
},
};
29 changes: 29 additions & 0 deletions components/yutori/actions/get-scout/get-scout.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import yutori from "../../yutori.app.mjs";

export default {
key: "yutori-get-scout",
name: "Get Scout",
description: "Fetch the details and current status of a specific Yutori Scout by its ID. Returns the scout's configuration, status (active, paused, completed), and metadata. [See the documentation](https://docs.yutori.com/reference/scouting-get)",
version: "0.0.1",
type: "action",
annotations: {
openWorldHint: true,
destructiveHint: false,
readOnlyHint: true,
},
props: {
yutori,
scoutId: {
type: "string",
label: "Scout ID",
description: "The ID of the scout to retrieve, e.g. from the Yutori dashboard or a previous **Get Scout** / **Get Scout Updates** step",
},
},
async run({ $ }) {
const scout = await this.yutori.getScout($, this.scoutId);
const name = scout?.display_name ?? scout?.query?.slice(0, 60) ?? this.scoutId;

$.export("$summary", `Scout: ${name}`);
return scout;
},
};
59 changes: 59 additions & 0 deletions components/yutori/actions/run-browsing-task/run-browsing-task.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import yutori from "../../yutori.app.mjs";

export default {
key: "yutori-run-browsing-task",
name: "Run Browsing Task",
description: "Use Yutori's cloud browser agent to navigate a website, fill forms, extract data, or complete any multi-step task — just describe it in plain English. Returns a task ID immediately; provide a **Webhook URL** to receive the full result the moment the task finishes. [See the documentation](https://docs.yutori.com/reference/browsing-create)",
version: "0.0.1",
type: "action",
annotations: {
openWorldHint: true,
destructiveHint: false,
readOnlyHint: false,
},
props: {
yutori,
task: {
type: "string",
label: "Task",
description: "Natural language description of what to do on the website, e.g. `Find the cheapest flight from NYC to LA next Friday`",
},
startUrl: {
type: "string",
label: "Start URL",
description: "The URL to start browsing from, e.g. `https://google.com/flights`",
},
maxSteps: {
type: "integer",
label: "Max Steps",
description: "Maximum number of browser actions the agent can take (1–100)",
optional: true,
min: 1,
max: 100,
default: 50,
},
webhookUrl: {
propDefinition: [
yutori,
"webhookUrl",
],
description: "URL to receive the result when the task completes. Recommended for reliable delivery — Yutori will POST the result to this URL.",
},
},
async run({ $ }) {
const payload = {
task: this.task,
start_url: this.startUrl,
max_steps: this.maxSteps,
};
if (this.webhookUrl) {
payload.webhook_url = this.webhookUrl;
payload.webhook_format = "scout";
}

const created = await this.yutori.createBrowsingTask($, payload);

$.export("$summary", `Browsing task started: ${this.task.slice(0, 60)}`);
return created;
},
};
60 changes: 60 additions & 0 deletions components/yutori/actions/run-research-task/run-research-task.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import yutori from "../../yutori.app.mjs";

export default {
key: "yutori-run-research-task",
name: "Run Research Task",
description: "Use Yutori to run deep web research across 100+ sources and get a comprehensive findings report — competitive analysis, market research, pricing comparisons, and more. Returns a task ID immediately; provide a **Webhook URL** to receive the full findings the moment research completes. [See the documentation](https://docs.yutori.com/reference/research-create)",
version: "0.0.1",
type: "action",
annotations: {
openWorldHint: true,
destructiveHint: false,
readOnlyHint: false,
},
props: {
yutori,
query: {
propDefinition: [
yutori,
"query",
],
label: "Research Query",
description: "What to research, e.g. `What are the top 5 CRM tools for startups in 2025, with pricing?`",
},
userTimezone: {
propDefinition: [
yutori,
"userTimezone",
],
},
userLocation: {
propDefinition: [
yutori,
"userLocation",
],
},
webhookUrl: {
propDefinition: [
yutori,
"webhookUrl",
],
description: "URL to receive the result when research completes. Recommended for reliable delivery — Yutori will POST the findings to this URL.",
},
},
async run({ $ }) {
const payload = {
query: this.query,
};
if (this.userTimezone) payload.user_timezone = this.userTimezone;
if (this.userLocation) payload.user_location = this.userLocation;
if (this.webhookUrl) {
payload.webhook_url = this.webhookUrl;
payload.webhook_format = "scout";
}

const created = await this.yutori.createResearchTask($, payload);

$.export("$summary", `Research task started: ${this.query.slice(0, 60)}`);
return created;
},
};
7 changes: 5 additions & 2 deletions components/yutori/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/yutori",
"version": "0.0.1",
"version": "0.1.0",
"description": "Pipedream Yutori Components",
"main": "yutori.app.mjs",
"keywords": [
Expand All @@ -11,5 +11,8 @@
"author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^3.1.1"
}
}
}
Loading