Repository navigation
Add the String Web Access component #22047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
asavor
wants to merge
7
commits into
PipedreamHQ:master
Choose a base branch
from
usestring:add-string-web-access-components
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+745
−0
Open
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ac592d2
Add String Web Access app and actions
asavor 9a35daf
Drop the unsupported Bing search-engine option
asavor 553b93d
Merge remote-tracking branch 'origin/master' into add-string-web-acce…
asavor 6353a60
Address review feedback on the String Web Access actions
asavor a16f61b
Describe String Web Access by what it returns
asavor 2cb2abe
Resume a crawl whose approval was interrupted
asavor 7d14aed
Merge branch 'master' into add-string-web-access-components
asavor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Overview | ||
|
|
||
| The String Web Access API returns any page on the web as clean, LLM-ready Markdown, runs a | ||
| structured web search, and maps a site's URLs. Proxy rotation, anti-bot handling, CAPTCHA solving | ||
| and JavaScript rendering happen server-side, so a workflow step gets the page instead of a block | ||
| screen. Best for pages that rate-limit, geo-gate or refuse automated traffic — the ones where a | ||
| plain HTTP step returns a 403 and your workflow quietly stops. | ||
|
|
||
| # Example Use Cases | ||
|
|
||
| **Competitor price watch into a warehouse** | ||
|
|
||
| - Fetch a product page on a schedule, extract fields with a JSON schema, and append the result to | ||
| Google Sheets, Postgres or BigQuery. No parser to maintain when the page changes shape. | ||
|
|
||
| **Research agent with live sources** | ||
|
|
||
| - Run Search Web on an incoming question, fetch the top results as Markdown, and pass them to | ||
| OpenAI or Claude in the same workflow. The model reads pages, not block screens. | ||
|
|
||
| **Inbound lead enrichment** | ||
|
|
||
| - When a form submission arrives, fetch the company's site, pull the fields you need with a schema, | ||
| and write them back to HubSpot or Salesforce before the sales rep opens the record. | ||
|
|
||
| # Getting Started | ||
|
|
||
| 1. Create a free account at [portal.usestring.ai](https://portal.usestring.ai/sign-up). The first | ||
| 5,000 requests are free. | ||
| 2. Open [Settings](https://portal.usestring.ai/settings) and copy an API key. | ||
| 3. In Pipedream, connect the String Web Access account and paste the key. Pipedream sends it as a | ||
| bearer token, so the key never appears in a step's code. | ||
|
|
||
| # Troubleshooting | ||
|
|
||
| **402 Payment Required** — the key is valid but the account balance can't cover the request. Top up | ||
| in the portal. | ||
|
|
||
| **A fetch returns a block page** — set **Render JavaScript** to true, or set a **Country Code** to | ||
| route through a proxy in the country the site serves. Failed requests are not billed. | ||
|
|
||
| **Map Site URLs costs more than expected** — the crawl is quoted before it runs and stops at the | ||
| **Budget (USD)** you set, so lower the budget or the page cap. |
56 changes: 56 additions & 0 deletions
56
components/string_web_access/actions/extract-data/extract-data.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import app from "../../string_web_access.app.mjs"; | ||
|
|
||
| export default { | ||
| key: "string_web_access-extract-data", | ||
| name: "Extract Data From URL", | ||
| description: "Fetch a URL and return only the fields described by a JSON schema. [See the documentation](https://portal.usestring.ai/docs/fetch/structured-extraction)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| readOnlyHint: true, | ||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| url: { | ||
| propDefinition: [ | ||
| app, | ||
| "url", | ||
| ], | ||
| description: "The URL to extract fields from", | ||
| }, | ||
| jsonSchema: { | ||
| type: "object", | ||
| label: "JSON Schema", | ||
| description: "The shape to extract, as a [JSON Schema](https://portal.usestring.ai/docs/fetch/structured-extraction) object. E.g. `{\"type\":\"object\",\"properties\":{\"price\":{\"type\":\"string\"}}}`", | ||
| }, | ||
| executeJS: { | ||
| propDefinition: [ | ||
| app, | ||
| "executeJS", | ||
| ], | ||
| }, | ||
| countryCode: { | ||
| propDefinition: [ | ||
| app, | ||
| "countryCode", | ||
| ], | ||
| }, | ||
| }, | ||
| async run({ $ }) { | ||
| const response = await this.app.fetchUrl({ | ||
| $, | ||
| data: { | ||
| url: this.url, | ||
| format: "json", | ||
| jsonSchema: this.jsonSchema, | ||
| executeJS: this.executeJS, | ||
| countryCode: this.countryCode, | ||
| }, | ||
| }); | ||
|
|
||
| $.export("$summary", `Successfully extracted fields from ${this.url}`); | ||
| return response; | ||
| }, | ||
| }; |
76 changes: 76 additions & 0 deletions
76
components/string_web_access/actions/fetch-url/fetch-url.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import app from "../../string_web_access.app.mjs"; | ||
|
|
||
| export default { | ||
| key: "string_web_access-fetch-url", | ||
| name: "Fetch URL", | ||
| description: "Fetch a URL and return the page as Markdown, raw content, or a JSON envelope. [See the documentation](https://portal.usestring.ai/docs/api-reference/fetch)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| readOnlyHint: true, | ||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| url: { | ||
| propDefinition: [ | ||
| app, | ||
| "url", | ||
| ], | ||
| }, | ||
| format: { | ||
| propDefinition: [ | ||
| app, | ||
| "format", | ||
| ], | ||
| }, | ||
| mainContentOnly: { | ||
| type: "boolean", | ||
| label: "Main Content Only", | ||
| description: "Drop navigation, headers and footers. Only affects Markdown responses.", | ||
| optional: true, | ||
| }, | ||
| executeJS: { | ||
| propDefinition: [ | ||
| app, | ||
| "executeJS", | ||
| ], | ||
| }, | ||
| countryCode: { | ||
| propDefinition: [ | ||
| app, | ||
| "countryCode", | ||
| ], | ||
| }, | ||
| solveCaptcha: { | ||
| propDefinition: [ | ||
| app, | ||
| "solveCaptcha", | ||
| ], | ||
| }, | ||
| headers: { | ||
| propDefinition: [ | ||
| app, | ||
| "headers", | ||
| ], | ||
| }, | ||
| }, | ||
| async run({ $ }) { | ||
| const response = await this.app.fetchUrl({ | ||
| $, | ||
| data: { | ||
| url: this.url, | ||
| format: this.format ?? "markdown", | ||
| mainContentOnly: this.mainContentOnly, | ||
| executeJS: this.executeJS, | ||
| countryCode: this.countryCode, | ||
| solveCaptcha: this.solveCaptcha, | ||
| headers: this.headers, | ||
| }, | ||
| }); | ||
|
|
||
| $.export("$summary", `Successfully fetched ${this.url}`); | ||
| return response; | ||
| }, | ||
| }; | ||
146 changes: 146 additions & 0 deletions
146
components/string_web_access/actions/map-site-urls/map-site-urls.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| import app from "../../string_web_access.app.mjs"; | ||
|
|
||
| const POLLING_INTERVAL = 5000; | ||
| const TERMINAL_STATUSES = [ | ||
| "completed", | ||
| "failed", | ||
| "canceled", | ||
| "token_cap_exceeded", | ||
| ]; | ||
|
|
||
| export default { | ||
| key: "string_web_access-map-site-urls", | ||
| name: "Map Site URLs", | ||
| description: "Crawl a site within a spend cap and return the URLs it discovered. [See the documentation](https://portal.usestring.ai/docs/api-reference/sitemap)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| readOnlyHint: true, | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| url: { | ||
| propDefinition: [ | ||
| app, | ||
| "url", | ||
| ], | ||
| label: "Start URL", | ||
| description: "Where the crawl starts. It stays on this hostname.", | ||
| }, | ||
| budgetUsd: { | ||
| type: "string", | ||
| label: "Budget (USD)", | ||
| description: "Hard spend cap for this crawl, e.g. `1.00`. The job stops rather than exceeding it, so a workflow can never approve an open-ended bill.", | ||
| }, | ||
| maxPages: { | ||
| type: "integer", | ||
| label: "Max Pages", | ||
| description: "Maximum pages to crawl, 1-10000", | ||
| optional: true, | ||
| default: 100, | ||
| }, | ||
| maxDepth: { | ||
| type: "integer", | ||
| label: "Max Depth", | ||
| description: "Maximum link depth from the start URL, 1-100", | ||
| optional: true, | ||
| default: 2, | ||
| }, | ||
| pathPrefix: { | ||
| type: "string", | ||
| label: "Path Prefix", | ||
| description: "Only crawl URLs whose path starts with this, e.g. `/docs`", | ||
| optional: true, | ||
| }, | ||
| useSitemap: { | ||
| type: "boolean", | ||
| label: "Seed From sitemap.xml", | ||
| description: "Also seed from the site's root `sitemap.xml`. Costs one extra page and finds pages that internal links miss.", | ||
| optional: true, | ||
| }, | ||
| maxWaitSeconds: { | ||
| type: "integer", | ||
| label: "Max Wait (seconds)", | ||
| description: "How long to wait for the crawl. If it is still running, the job ID is returned so a later step can collect the URLs.", | ||
| optional: true, | ||
| default: 300, | ||
| }, | ||
| }, | ||
| methods: { | ||
| sleep(ms) { | ||
| return new Promise((resolve) => setTimeout(resolve, ms)); | ||
| }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| }, | ||
| async run({ $ }) { | ||
| const quote = await this.app.submitSitemap({ | ||
| $, | ||
| data: { | ||
| url: this.url, | ||
| budgetUsd: Number(this.budgetUsd), | ||
| maxPages: this.maxPages, | ||
| maxDepth: this.maxDepth, | ||
| pathPrefix: this.pathPrefix, | ||
| useSitemap: this.useSitemap, | ||
| }, | ||
| }); | ||
|
|
||
| await this.app.approveSitemap({ | ||
| $, | ||
| jobId: quote.jobId, | ||
| }); | ||
|
|
||
| const deadline = Date.now() + ((this.maxWaitSeconds ?? 300) * 1000); | ||
| let status = "running"; | ||
|
|
||
| while (Date.now() < deadline) { | ||
| await this.sleep(POLLING_INTERVAL); | ||
| const state = await this.app.getSitemapStatus({ | ||
| $, | ||
| jobId: quote.jobId, | ||
| }); | ||
| status = state.status; | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| if (status === "partial_state") { | ||
| await this.app.approveSitemap({ | ||
| $, | ||
| jobId: quote.jobId, | ||
| }); | ||
| continue; | ||
| } | ||
| if (TERMINAL_STATUSES.includes(status)) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (!TERMINAL_STATUSES.includes(status)) { | ||
| $.export("$summary", `Crawl ${quote.jobId} is still running; collect its URLs with the job ID`); | ||
| return { | ||
| ...quote, | ||
| status, | ||
| urls: [], | ||
| }; | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| const results = await this.app.getSitemapUrls({ | ||
| $, | ||
| jobId: quote.jobId, | ||
| params: { | ||
| limit: 1000, | ||
| offset: 0, | ||
| }, | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| $.export("$summary", `Discovered ${results.total} URL${results.total === 1 | ||
| ? "" | ||
| : "s"} on ${this.url}`); | ||
| return { | ||
| jobId: quote.jobId, | ||
| status, | ||
| estimatedCostUsd: quote.estimatedCostUsd, | ||
| estimatedPages: quote.estimatedPages, | ||
| ...results, | ||
| }; | ||
| }, | ||
| }; | ||
64 changes: 64 additions & 0 deletions
64
components/string_web_access/actions/search-web/search-web.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import app from "../../string_web_access.app.mjs"; | ||
|
|
||
| export default { | ||
| key: "string_web_access-search-web", | ||
| name: "Search Web", | ||
| description: "Search the web and return the organic results. [See the documentation](https://portal.usestring.ai/docs/api-reference/search)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| readOnlyHint: true, | ||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| query: { | ||
| type: "string", | ||
| label: "Query", | ||
| description: "What to search for", | ||
| }, | ||
| engine: { | ||
| type: "string", | ||
| label: "Engine", | ||
| description: "Which search engine to query", | ||
| options: [ | ||
| "google", | ||
| "duckduckgo", | ||
| "brave", | ||
| "mojeek", | ||
| ], | ||
| optional: true, | ||
| default: "google", | ||
| }, | ||
| country: { | ||
| type: "string", | ||
| label: "Country", | ||
| description: "ISO 3166-1 alpha-2 country code used to localize results, e.g. `US` or `GB`", | ||
| optional: true, | ||
| }, | ||
| language: { | ||
| type: "string", | ||
| label: "Language", | ||
| description: "Language tag for the results, e.g. `en` or `pt-br`", | ||
| optional: true, | ||
| }, | ||
| }, | ||
| async run({ $ }) { | ||
| const response = await this.app.search({ | ||
| $, | ||
| data: { | ||
| query: this.query, | ||
| engine: this.engine, | ||
| country: this.country, | ||
| language: this.language, | ||
| }, | ||
| }); | ||
|
|
||
| const count = response?.results?.length ?? 0; | ||
| $.export("$summary", `Found ${count} result${count === 1 | ||
| ? "" | ||
| : "s"} for "${this.query}"`); | ||
| return response; | ||
| }, | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.