Listing check
Every week, each live listing read back: still up, still linking to you, and still followed, with what changed since you submitted it.
- Kind
- Workflow
- Price
- 0.8¢ per 40 listings
- Your time
- 2 minutes
- Approval
- Runs on its own
- Deepens
- Launches
Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first.
curl -X POST "https: //api.sb/ leaseline.app/ posts/ check? mode= try" \
-H "Authorization: Bearer $SB_TOKEN" \
-H "Content-Type: application/ json" \
-d '{"goal": "Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first.", "budget": {"amount": 0.5, "currency": "USD"}}'
npx startup-builder do Post.check --try --data '{"goal": "Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first.", "budget": {"amount": 0.5, "currency": "USD"}}'
{
"name": "do",
"arguments": {
"mode": "try",
"code": "return await $.Post.check({ goal: 'Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first.', budget: { amount: 0.5, currency: 'USD' } })"
}
}
import { $ } from 'startup-builder'
const result = await $.try.Post.check({ goal: 'Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first.', budget: { amount: 0.5, currency: 'USD' } })
{
"mode": "try",
"stored": false,
"estimate": "About 0.8¢ per 40 listings",
"sample": {
"listing": "SaaSHub",
"field": "rel",
"was": "follow",
"now": "nofollow",
"since": "2026-11-02"
}
}
What you get
- 40 listings · Every week, each live listing read back: still up, still linking to you, and still followed, with what changed since you submitted it. · Post
checked· Listings read this week.live· Listings up and linking to you.drifted[]· Each change: the link’srel(follow to nofollow), the URL, the name, orgone, with what it was and what it is now.
How long: About 30 seconds.
Trigger
It runs weekly, and daily from Directory submissions until each new listing is live.
every.week(($) => $.Post.check({ posts: 'listings' }))Steps
Your agent does these steps, one row per step; each step’s check is part of its row and its price.
| Step | Verb | Returns | Price | Approval |
|---|---|---|---|---|
| 1. List the live listings | Collection.query | Each listing’s URL and the fields you submitted | Free | Runs on its own |
| 2. Read each listing page | Page.audit | The live page: your link, its rel, the name and the description | About 0.8¢ | Runs on its own |
| 3. Compare with what you submitted | Collection.query | A drift row for each changed field; a listing missing two weeks running becomes rejected | Free | Runs on its own |
| 4. Record each drift | Post.update | The post updated with the change, whose Event any Trigger can act on | Free | Runs on its own |
Calls: Collection.query, then Page.audit, then Collection.query, then Post.update.
About 0.8¢ per 40 listings, each time it runs, capped at 10¢.
Example: give it a goal
Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first.
The plan it shows runs 4 steps, priced before it spends anything; Run it above shows the result it returns.
When something fails
Each failure, and what happens next
| When | Then |
|---|---|
| A listing page cannot be read | It is retried the next day, not marked gone; one bad read never reports a lost link. |
| A listing is missing two weeks running | It becomes rejected with a task to resubmit. |
| You call it twice in one day | The second call returns the stored reading at $0: it is idempotent per post and day. |
Each step retries three times, then falls through to the next route; one item's failure lands in skipped with its reason, never failing the call. Before a step would cross maxCost it stops and returns what it has, with a resume link. Each step's idempotency key is the call's key plus the step and the item: leaseline-post-check-1-<step>-<item>.
Change it
| Ask | Compiles to |
|---|---|
| Resubmit any listing that has been gone for two weeks. | Post.submit |
| Tell me the day a followed link turns nofollow. | Monitor.create |
| Check my launch posts too, not only directories. | Post.check |
import { $, workflow } from 'startup-builder'
export default workflow({
name: 'listing-drift-check',
verb: 'Post.check',
input: { posts: "'listings' | string[]" },
trigger: { schedule: 'weekly' },
maxCost: 0.1,
execute: async (ctx, { posts = 'listings' }) => {
const list = await ctx.step('list', () => $.Collection.query({ from: 'post', where: posts === 'listings' ? "kind = 'listing' and status = 'published'" : `$id in ${posts}` }))
const read = await ctx.step('read', () => $.Page.audit({ urls: list.map((p) => p.url), checks: ['links'], maxCost: 0.02 }))
const drifted = diffListings(list, read) // the fields you submitted: a declared rule
if (drifted.length) await ctx.step('record', () => $.Post.update({ changes: drifted }))
return { checked: list.length, live: read.filter((r) => r.linksTo).length, drifted }
},
})Extend it with extends: and base(), or compose your own from these steps.
Works with
Plays that order it: Agent registries, Directory listings, Marketplace listings.
It serves Earn first links, and runs in Launch and search, Borrowed. It deepens Launches.
For agents
For agents: the guide, its approvals, retries and lookups
| Fact | Value |
|---|---|
| Guide | Post.check |
| Guide approval | limits |
| Goal | Check that all my directory listings are still up and followed, spend up to 50¢, and show me the plan first. It may change counts, the order of steps, soft filters and the route a read takes, and it stops early when the goal is met. It never relaxes your words, raises its own budget, answers its own approval, calls a Play or acts outside your standing approval. |
| Trigger | every.week(($) => $.Post.check({ posts: |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | leaseline-post-check-1 |
| Lookups | A find: input asks unless exactly one record fits |
| Step |
|---|
| 1. List the live listings |
| 2. Read each listing page |
| 3. Compare with what you submitted |
| 4. Record each drift |
The amounts live once, on the hub (approvals).
Who does each step, and how it is checked
| Step | Function | Doer | Checker | Assurance | Inputs |
|---|---|---|---|---|---|
| 1. List the live listings | code | Code | Code checks each result against its declared type | measured | $0.posts |
| 2. Read each listing page | code | Code | Code checks each result against its declared type | measured | $1.urls |
| 3. Compare with what you submitted | code | Code | Code checks each result against its declared type | measured | $1, |
| 4. Record each drift | code | Code | Code reads the result back from where it landed | measured | $3.drifted |
The doer is never the checker, and the weakest assurance on the path is the whole call’s.
Under the hood
Under the hood: the Systems, Nouns and Events it touches
| Kind | What it touches |
|---|---|
| Systems | Social |
| Nouns | Collection, Page, Post |
| Events | Collection.queried, Post.updated |
Reference
Inputs, outputs, its Event and its errors
| Name | Type | Default | Meaning |
|---|---|---|---|
posts | 'listings' | string[] | 'listings' | Every live listing, or the posts you name. |
second | boolean | true | Read a page that looks gone a second time, on another route, before reporting it. |
maxCost | number | 0.10 | The most this call spends each time it runs, in USD. It stops before a step would cross it and returns what it has, with a resume link. |
Emits Post.checked. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).