Founder CRM

Flow files

Getting started

The easiest way to make a flow is the chat in the side panel. Say what you want. It writes the steps. When it looks right, download the markdown file and send it to your team. They import it in Flows.

You can also have an agent write the file. Point it at this page so it uses the right keys.

This guide is that file format: a .md with a recipe between --- lines.

Minimal example

Makers of today's top Product Hunt launch.

---
type: flow // must be flow
format: 1 // leave this as 1
id: ph-top-makers-today // import updates this id
name: "Today's top PH makers" // card title
description: Makers of today's top Product Hunt launch. // card subtitle
start: "https://www.producthunt.com/" // first URL
steps:
  - type: open // go to Product Hunt
    label: Open Product Hunt today
    url: "https://www.producthunt.com/"
  - type: visit_links // open matching hrefs on this page
    label: Open the top product
    urlIncludes: "/products/|/posts/" // homepage product cards
    limit: 1 // top product only
    page:
      - type: click // runs on that product page
        label: Expand Launch Team
        text: Show more
      - type: extract // add people from this page
        label: Add Launch Team makers
        collect: dom
        instruction: Launch Team makers of this product only. Skip Hunter-only.
heal:
  expectMinPeople: 1 // fail the run if nobody is added
  success: Makers from today's top Product Hunt launch.
  notes: Homepage cards are /products/ or /posts/. Launch Team starts collapsed.
---

Import the file in Flows. The card title comes from name. The subtitle comes from description.

What goes in the file

Every file needs type: flow, format: 1, id, name, start, and a steps list. Each step needs type and label. Unknown keys fail import.

start is the first URL. {{id}} pulls a value you type when the flow runs.

steps is the recipe. Most people jobs are a short list: get to the right pages, add people, drop the misses, then read X.

heal is for people scrapes. It says what a good run looks like. Skip it when you only click (upvote, and so on).

Ready-made files you can import are on Templates.

How it works

Write steps in this order. Keep the list short.

  1. Browse: get to the right page
  2. Add: put people on the list
  3. Filter: drop people who fail a check
  4. Enrich: fill fields from a profile
  5. Act: click a button, like upvote

One Add, then Filter while you peek, then Enrich. Do not hide a scrape plan in instruction. That field is who to keep on the page.

Browse

Get onto the page the rest of the recipe needs.

  • open goes to a URL. First real step in most files.
  • click clicks a button or link (text: Show more, or a selector).
  • browse looks around like a person. Use it before an action so the click does not fire on a cold load.
  • scroll jumps the viewport to load more. Prefer paginate on X Latest instead of chaining this to collect people.
  • visit_links without a page extract opens matching hrefs and stops there.
- type: open
  label: Open Product Hunt today
  url: "https://www.producthunt.com/"

wait is a dead pause. Prefer browse when the job should look human.

Add

Put people on the list from a page you can already see, or from pages you open on purpose.

  • extract reads the current page. collect: dom is the default. Add instruction so it knows who counts (Launch Team makers, tweet authors).
  • paginate walks several X Latest viewports and adds authors as it goes.
  • visit_links snapshots the current page, keeps hrefs that match urlIncludes, opens the first limit unique pages, then runs page on each tab.
- type: visit_links
  label: Add makers from top products
  urlIncludes: "/products/|/posts/"
  limit: 8
  page:
    - type: click
      label: Expand Launch Team
      text: Show more
    - type: extract
      label: Add Launch Team makers
      collect: dom
      instruction: Launch Team makers of this product only. Skip Hunter-only.

limit on visit_links is pages, not people. Do not copy that 8 onto visit_profiles.

Work that should run on every opened page belongs under page. A sibling step after visit_links runs once, on whatever tab is left.

Filter

Drop people who fail a check. The list updates as they fail.

  • keep + urlIncludes keeps a person if their url, citation, or profile links match (twitter.com|x.com).
  • keep: can_dm keeps people with Message next to Follow on x.com.
  • keep: match is a judgment call. It needs instruction ("complaints", "worth a reply").

Put keep under visit_profiles.page when the check needs a peek (Links on a PH profile, Message on X). A top-level keep only uses data already on the person.

- type: visit_profiles
  label: Keep people with X
  urlIncludes: producthunt.com/@
  page:
    - type: extract
      label: Read Links
      collect: dom
    - type: keep
      label: Keep if Twitter or X is listed
      urlIncludes: twitter.com|x.com

has_x is an old alias. Write urlIncludes: twitter.com|x.com instead.

Enrich

Open each collected person and fill the card (photo, bio, followers, website).

- type: visit_profiles
  label: Read the X profile
  urlIncludes: x.com
  page:
    - type: extract
      label: Read the X profile
      collect: dom

No keep means everyone stays. urlIncludes here selects people you already have, not hrefs on the current page.

Do not add wait or browse on every x.com profile. The peek already waits for the photo.

Act

Click a button or link on the page. Point at it with the words on screen (text: Show more) or a CSS selector. Reddit upvote is script: reddit-upvote.

- type: click
  label: Upvote
  script: reddit-upvote
  text: upvote

Action recipes often skip people entirely. Omit heal when you do not collect anyone.

Repeat over inputs

inputs are values you type each run. each runs the whole steps list once per list item. {{id}} substitutes into start and into step url, text, selector, and instruction.

inputs:
  - id: link
    type: list
    itemType: url
    label: Reddit links
    placeholder: "https://www.reddit.com/r/…"
each: link
start: "{{link}}"

typeurltextnumberselectlist

each is not a step. Do not put it on a step. page is the nest for links and people.

page is a short playbook that runs on every opened href or person. One level only. Allowed inside page: click, extract, browse, wait, scroll, keep.

urlIncludes always matches the list that step walks.

StepWhat it matches
visit_linksHrefs on the current page
visit_profilesCollected person.url / citationUrl
keepThose person fields after the peek

Homepage cards on Product Hunt are /products/ or /posts/. PH @ urls appear after you extract makers. visit_links with urlIncludes: producthunt.com/@ on the homepage finds nothing. visit_profiles with urlIncludes: /products/ also finds nothing. That step does not read the page.

A people scrape

Today's Product Hunt makers you can DM.

---
type: flow
format: 1
id: ph-makers-i-can-dm
name: Today's PH makers I can DM
description: Makers of today's top PH launches who have X and open DMs.
start: "https://www.producthunt.com/"
steps:
  - type: open
    label: Open Product Hunt today
    url: "https://www.producthunt.com/"
  - type: visit_links
    label: Add makers from top products
    urlIncludes: "/products/|/posts/"
    limit: 8
    page:
      - type: click
        label: Expand Launch Team
        text: Show more
      - type: extract
        label: Add Launch Team makers
        collect: dom
        instruction: Launch Team makers of this product only. Skip Hunter-only.
  - type: visit_profiles
    label: Keep people with X
    urlIncludes: producthunt.com/@
    page:
      - type: extract
        label: Read Links
        collect: dom
      - type: keep
        label: Keep if Twitter or X is listed
        urlIncludes: twitter.com|x.com
  - type: visit_profiles
    label: Keep people I can DM
    urlIncludes: x.com
    page:
      - type: extract
        label: Read the X profile
        collect: dom
      - type: keep
        label: Keep if I can DM
        keep: can_dm
heal:
  expectMinPeople: 2
  success: Makers from today's top launches who have X and a Message button.
  notes: Homepage cards are /products/ or /posts/. X lives under Links on /@ profiles.
---

Browse the homepage, Add makers from the top product pages, Filter to people with X, then Filter again to open DMs. The second visit_profiles is also Enrich: the peek fills the X card.

An action

Upvote one or more Reddit links. No people, no heal.

---
type: flow
format: 1
id: reddit-upvote
name: Upvote a Reddit link
description: Paste Reddit posts or comments. Open each, then upvote.
start: "{{link}}"
each: link
inputs:
  - id: link
    type: list
    itemType: url
    label: Reddit links
    placeholder: "https://www.reddit.com/r/…"
steps:
  - type: open
    label: Open the post
    url: "{{link}}"
    rewrite: reddit-thread
  - type: browse
    label: Look around
    script: human
    ms: 30000
    ready: shreddit-post
  - type: click
    label: Upvote
    script: reddit-upvote
    text: upvote
---

rewrite: reddit-thread opens the post page and keeps the comment id.

Before you import

  • Every step has type and label.
  • format: 1.
  • description is card copy (140 characters). Not heal.success.
  • Loops use page or each, not a sibling click you meant to run N times.
  • urlIncludes matches the list that step walks.
  • People scrapes have heal.expectMinPeople.
  • No wait or browse on every x.com profile.
  • Do not invent fields. Unknown keys fail import.

Find tasks

For a task discovery flow, put find_tasks on each candidate page. Give the AI the action, product context, fit criteria, and time window. Results retain an exact destination and source evidence without creating a contact. Saved runs add Tasks automatically; builder tests remain previews.

- type: visit_links
  label: Read candidate threads
  urlIncludes: /comments/
  limit: 5
  page:
    - type: find_tasks
      label: Find useful replies
      instruction: Find comment tasks on questions from the past 24 hours asking how to find first SaaS customers. Keep only a clear opportunity to offer a practical daily outreach routine. Preserve the direct thread URL and quote the request. Skip ads, resolved questions, and uncertain ages. Do not post anything.
      limit: 3

Choose and verify the search URL before this step. Omit people heal thresholds. Inspect tasks in the preview, including their destination, evidence, and suggested approach, before saving the recipe.

Reference

A flow file is a .md with a recipe between --- lines. Import also accepts JSON with the same keys. Unknown keys fail import.

The file

---
type: flow
format: 1
id: my-flow
name: Short title
description: One or two lines for the card. 140 characters max.
start: "https://example.com/"
steps:
  - type: open
    label: Open the page
    url: "https://example.com/"
---
KeyRequiredWhat it is
typeyesMust be flow.
formatyes on new filesMust be 1.
idyes to updateImport upserts this id.
nameyesCard title.
descriptionyes for people jobsCard copy. Not heal.success.
startyesFirst URL, or {{token}}.
inputsnoValues you type each run.
eachnoInput id. Run steps once per list item.
briefnoSaved objective, product, audience, approach, frequency, search window and useful volume for task discovery.
stepsyesOrdered recipe.
healpeople scrapesWhat a good run looks like.

description can also sit in the body after the closing ---. Frontmatter wins.

Flow context

The builder starts with up to three short questions for missing context. It can follow up on important gaps or a corrected goal, then proceeds directly when it has enough information. The optional brief stores those answers. objective is required when a brief is present; product, audience, value, approach, frequency, searchWindow and volume are optional strings. Export and import preserve the brief. Each execution snapshots it and supplies it to candidate screening and task suggestions without changing the saved step instructions.

For promotion flows, record the actual product benefit and preferred approach. Suggestions should connect that benefit to the observed request, offer useful help first, and avoid unsupported product claims.

Ask about cadence when it changes discovery. One-off searches can cover all time; recurring fresh-opportunity searches usually use a rolling 24-hour window for daily runs or seven days for weekly runs. Explicit age limits and evergreen research goals take precedence. Store relative windows so they apply at each run, and reflect them in both source filters and review criteria. A requested volume guides coverage, never padding. Frequency in the brief is context, not an enabled schedule.

Inputs

inputs:
  - id: link
    type: list
    itemType: url
    label: Reddit links
    placeholder: "https://www.reddit.com/r/…"
each: link
start: "{{link}}"

typeurltextnumberselectlist

itemType on a listurltext

options on select: a string array.

{{id}} substitutes into start and step url / text / selector / instruction.

Heal

heal:
  expectMinPeople: 2
  success: What a good people scrape looks like.
  notes: What to try if it drifted.

Required for people scrapes. Omit for action recipes. Never use success as the card description.

Steps

Every step needs type and label. Omit empty keys. One level of page only. page steps cannot nest another page.

Allowed page typesclickfind_tasksextractbrowsewaitscrollkeep

find_tasks

Find concrete tasks on the current page using AI. Use this for opportunity discovery. A task includes an action title, kind, exact target URL, relevance context, suggested approach, and a source excerpt. It does not require a person or perform the action.

  • instruction (required): desired action, product context, relevance criteria, time window, and exclusions. This runs again on every saved run.
  • limit (optional): maximum tasks per inspected page, 1-20. Default 10.
  • Allowed at the top level or in visit_links.page and visit_profiles.page.
  • Requires a configured AI provider. Destinations must appear in the snapshot and source excerpts must match visible text. Failures never fall back to unfiltered people.
  • Omit heal. Empty relevant results are valid. Blocked or unreadable pages fail visibly.

Tests preview tasks. Saved runs add tasks automatically. Imports deduplicate by action kind and destination, across flows and including completed/cancelled tasks. To deliberately repeat an action at the same destination, create a manual task or a follow-up.

open

Go to a URL.

KeyWhat it is
urlFull https URL, or {{token}} from an input.
rewritereddit-thread: open the post page, keep the comment id.

First real step after an optional interpret.

wait

Dead pause. Default 800ms. Prefer browse when the job should look human.

KeyWhat it is
msMilliseconds.

browse

Look around like a person (inertia swipes, reading pauses).

KeyWhat it is
scripthuman.
msHow long (default 30000).
timesOptional swipe cap.
readyCSS selector that means the page is ready.
selectorOptional target to work toward.

scroll

Fast viewport jumps to load more. Use paginate on X Latest instead of chaining this to collect people.

KeyWhat it is
timesViewports (1 to 8).

click

Click a button or link.

KeyWhat it is
textVisible label (PH: Show more).
selectorCSS selector.
scriptreddit-upvote for Reddit votes.

A top-level click runs once. Under visit_links.page it runs on every opened href.

extract

Add people from the current page, or peek the current profile when nested under visit_profiles.page.

KeyWhat it is
collectdom (default) or ai. Prefer dom.
instructionWho to keep on the page. Required for ai.
urlIncludesOn a lone extract: only keep people whose url matches.

paginate

Add people across several X Latest viewports.

KeyWhat it is
timesPages (3 to 8).
limitStop after this many people.
collectUsually dom.

Map over hrefs on the current page.

  1. Snapshot the page.
  2. Keep hrefs that match urlIncludes, first limit unique pages.
  3. Open each href.
  4. Run page on that tab.
KeyWhat it is
urlIncludesHref needles, `\` separated. Page snapshot only.
limitPages to open, not people. 5 to 8 for top products.
pagePer-href playbook.

Old shorthand: text / collect / instruction if page is omitted. Prefer page.

visit_profiles

Map over collected people.

  1. Take people whose url / citationUrl match urlIncludes.
  2. Open each person’s url.
  3. Run page (extract peeks, keep drops as you go).
KeyWhat it is
urlIncludesPerson urls, not the current page.
pagePer-person playbook.
limitOmit. Default is everyone (cap 80).

Old shorthand: keep (can_dm, has_x) if page is omitted. Prefer page keep.

keep

Filter people already on the list.

KeyWhat it is
urlIncludesKeep if person url, citationUrl, or profile links match.
keepcan_dm: Message next to Follow on x.com. match: needs instruction. has_x: old alias for twitter/x urlIncludes.

Put keep on visit_profiles.page when the check needs a peek. A top-level keep only uses data already on the person.

interpret

Optional first step. Reads a prompt from the trigger URL and fills inputs. Needs instruction. Do not use as the default.

Named values

A few keys take a fixed word. Use text, selector, or urlIncludes when a visible label or URL substring is enough.

KeyValues
script on browsehuman
script on clickreddit-upvote
rewrite on openreddit-thread
keep on keepcan_dm, match (has_x is an old alias)
collectdom, ai

Task candidate screening

When visit_links contains find_tasks in page, it screens up to 60 unique matching destinations in one AI call before opening up to limit promising pages (default 12). The criteria come from nested find_tasks instructions. Short titles are candidates, not verified tasks. Full-page review produces structured task JSON or a short skip reason. The step details show screening and review outcomes without raw profile links. Existing people recipes retain their first-link behavior.