Ad .find
Read a rival’s ads, or every ad on your category’s words, on Facebook, Instagram, Google, LinkedIn and TikTok, with how long each ran.
- Price
- 0.2¢ a search
- Success rate
- 99.9% of 309,000 calls
- Freshness
- Fresh for 7 days
- Emits
Ad.found- Capability
- Market data
POST https: //api.sb/ {your-domain}/ ads/ find
Options and prices
Inputs
| Name | Type | Default | Meaning |
|---|---|---|---|
advertiser | Hostname | string | None. | A rival’s domain or page name. Pass this or keyword. |
keyword | string | None. | Every ad on this exact phrase. Facebook, Instagram and TikTok only. |
venues | ('facebook' | 'instagram' | 'google-search' | 'google-display' | 'youtube' | 'linkedin' | 'tiktok')[] | every Venue the input supports | Where to look. Each site is its own search, priced each. |
countries | string[] | your home Market | ISO 3166-1 codes where the ads ran. |
activeOnly | boolean | true | Only ads running now. false adds ads that stopped, with their last-seen date. |
countOnly | boolean | false | Return total and no rows. |
limit | number | 50 | Ads a page, at most 100. |
after | Cursor | None. | The cursor from the last page. |
maxAge | string | '7d' | A search younger than this returns at $0. |
maxCost | number | 0.05 | The most this call may spend, in USD. Over it, the call is refused before it spends, or stops early with what it has. |
Outputs
| Field | Type | What null means |
|---|---|---|
ads | Ad[] | Never null: empty when none ran, at $0; absent with countOnly. |
ads[].advertiser | { name: | Never null; hostname: when the page names no site. |
ads[].venue | VenueId | Never null. |
ads[].format | 'text' | 'image' | 'video' | 'carousel' | Never null. |
ads[].headline | string | null | The format has no headline. |
ads[].body | string | null | The format has no body copy. |
ads[].cta | string | null | No button. |
ads[].landingUrl | string | null | The library does not show it. |
ads[].mediaUrl | string | null | A text ad, or the library does not serve the file. |
ads[].firstSeen | string | null, in date | The library does not show a start date. |
ads[].lastSeen | string | null, in date | The library does not show it. |
ads[].daysRunning | number | null, in days | No start date, so unknown; never zero. |
ads[].transparencyUrl | string | null | The site’s library has no page for it. |
ads[] | Observed | Each ad also carries observedAt and method. |
total | number | null, in ads | The library reports no total. |
after | Cursor | The last page: there are no more rows. |
cost | Cost, in USD | Never null. In try mode usd is 0 and maxUsd is what the call would have cost. |
Read the result
| Field | Value | What it means | What to do |
|---|---|---|---|
daysRunning | The longest-running ad | An advertiser keeps paying only for an ad that works, so the oldest live ad is their proven message. | Read its promise and its offer; test the same promise in your own words, never their copy. (the ad-library rule) |
daysRunning | Under 14 on most ads | The advertiser is still testing; nothing has proven itself yet. | Read again in 4 weeks and keep the ones still running. (our planning number) |
total | 0 on your category’s words | Nobody pays to reach these buyers there: either the money is not there or the field is open. | Price it first: forecast the terms with Keyword.forecast before you spend. (the paid playbook) |
landingUrl | A page per audience | The rival splits buyers by size or role, and each page speaks to one. | Audit those pages with Page.audit and compare their offers with yours. (our planning number) |
Effects
- Action class
None: it reads, so no approval rule names it
- Modes
try: Runs the read and returns real rows; nothing is saved and nothing is billed.send: Returns a pending Action at once; its Event carries the rows when the read finishes.do: Waits for the read and returns the rows, billed per result.
- Idempotency
The same
Idempotency-Keywith the same input replays the first answer at $0; with other input it returns 409.- Saved or shared
Nothing is written to your records: outside ads are read, never saved as your Ads. Searches are shared across startups for 7 days.
- Emits
Ad.foundwith{ advertiser,keyword, venues, ads, total } - Performed by
- Price
About 0.2¢ a search. Free: a search younger than
maxAge.maxCostdefaults to 5¢. 3 rivals on 5 sites: 15 × 0.2¢ a search is about 3¢.- Reliability
The first route succeeds 99.9% of 309,000 calls. If it fails, the call refuses with
sources-busyand nothing is spent.- Freshness
A reading younger than 7 days returns at $0. Readings are shared across startups for the same subject and locale.
- Batch and pages
Up to 100 ads a page. Pages with an
aftercursor, 25 rows a page by default and 100 at most. It answers in one response.- Rate
The shared rate rules: 10 calls a second per key, and an array input counts as one call.
Refuses when
| Check | Error | Fix | Then call |
|---|---|---|---|
A search engine, answer engine or network the call does not read, such as ChatGPT sent to Keyword.measure | venue-unsupported | Pick one the call’s venue input lists; AI answers are Keyword.ask, posts are Keyword.search. | Keyword.ask |
Every data call can also meet the shared data-call refusals: filter-unsupported, input-missing, limit-exceeded, max-cost, rate-limited, sources-busy.
The venue-unsupported payload
{
"type": "https://startupbuilder.dev/errors/venue-unsupported" ,
"title": "The call does not read that search engine or network",
"status": 422,
"detail": "A search engine, answer engine or network the call does not read, such as ChatGPT sent to `Keyword.measure`." ,
"fix": "Pick one the call’s `venue` input lists; AI answers are `Keyword.ask`, posts are `Keyword.search`." ,
"next": {
"verb": "Keyword.ask",
"input": {
"keyword": "best rent tracking software for a 40-unit property manager",
"venues": [
"chatgpt"
]
}
},
"instance": "https://api.sb/leaseline.app/actions/action_1"
}When it waits
Returns in one response; nothing waits.
Example
Which ads is rentrival.example running on Facebook and LinkedIn, and
which has run longest? Spend up to 1¢. To retry, send the same Idempotency-Key: the retry returns the first answer for $0. A new key reads again.
Test values, and watching it
A sk_test_ key answers these the same way every time, at $0 (every test value).
| Test value | Always returns | What it is for |
|---|---|---|
maxCost: | Refuses with max-cost | Any priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap. |
rate-limited.test | Refuses with rate-limited | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
sources-busy.test | Refuses with sources-busy | Any call naming this hostname answers 503 as if every route were busy, and nothing is spent. |
Watch it on a schedule: a Monitor runs this call and records an Event only when the reading changes.
await $.Monitor.create({
verb: 'Ad.find',
input: { advertiser: 'rentrival.example' },
change: 'new-ad',
every: '1w',
})Used by
- Workflows: Competitor gap · Pick who to sell to first
- Jobs: Run ads · Size up competitors
Related
None.