Skip to content

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

You getInput that selects itPriceSuccess rate
A rival’s ads on Facebook and Instagram{"advertiser":"rentrival.example","venues":["facebook","instagram"]}About 0.2¢ a search99.1% of 277,000 calls
Every ad on a phrase{"keyword":"rent tracking"}About 0.2¢ a search99.9% of 309,000 calls
A rival’s Google ads{"advertiser":"rentrival.example","venues":["google-search"]}About 0.2¢ a search99.95% of 173,000 calls
A rival’s LinkedIn ads{"advertiser":"rentrival.example","venues":["linkedin"]}About 0.16¢ a search100% of 15,900 calls
TikTok ads on a phrase{"keyword":"rent tracking","venues":["tiktok"]}About 0.2¢ a search99.6% of 7,200 calls
How many ads, with no rows{"advertiser":"rentrival.example","countOnly":true}About 0.5¢ a search99.8% of 994 calls
A rival’s past search ad copy{"advertiser":"rentrival.example","activeOnly":false,"venues":["google-search"]}About 0.3¢ a search100% of 2,000 calls

Inputs

NameTypeDefaultMeaning
advertiserHostname | stringNone.A rival’s domain or page name. Pass this or keyword.
keywordstringNone.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 supportsWhere to look. Each site is its own search, priced each.
countriesstring[]your home MarketISO 3166-1 codes where the ads ran.
activeOnlybooleantrueOnly ads running now. false adds ads that stopped, with their last-seen date.
countOnlybooleanfalseReturn total and no rows.
limitnumber50Ads a page, at most 100.
afterCursorNone.The cursor from the last page.
maxAgestring'7d'A search younger than this returns at $0.
maxCostnumber0.05The most this call may spend, in USD. Over it, the call is refused before it spends, or stops early with what it has.

Outputs

FieldTypeWhat null means
adsAd[]Never null: empty when none ran, at $0; absent with countOnly.
ads[].advertiser{ name: string; hostname: Hostname | null }Never null; hostname: null when the page names no site.
ads[].venueVenueIdNever null.
ads[].format'text' | 'image' | 'video' | 'carousel'Never null.
ads[].headlinestring | nullThe format has no headline.
ads[].bodystring | nullThe format has no body copy.
ads[].ctastring | nullNo button.
ads[].landingUrlstring | nullThe library does not show it.
ads[].mediaUrlstring | nullA text ad, or the library does not serve the file.
ads[].firstSeenstring | null, in dateThe library does not show a start date.
ads[].lastSeenstring | null, in dateThe library does not show it.
ads[].daysRunningnumber | null, in daysNo start date, so unknown; never zero.
ads[].transparencyUrlstring | nullThe site’s library has no page for it.
ads[]ObservedEach ad also carries observedAt and method.
totalnumber | null, in adsThe library reports no total.
afterCursorThe last page: there are no more rows.
costCost, in USDNever null. In try mode usd is 0 and maxUsd is what the call would have cost.

Read the result

FieldValueWhat it meansWhat to do
daysRunningThe longest-running adAn 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)
daysRunningUnder 14 on most adsThe advertiser is still testing; nothing has proven itself yet.Read again in 4 weeks and keep the ones still running. (our planning number)
total0 on your category’s wordsNobody 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)
landingUrlA page per audienceThe 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-Key with 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.found with { advertiser, keyword, venues, ads, total }

Performed by

Paid Media Manager

Price

About 0.2¢ a search. Free: a search younger than maxAge. maxCost defaults 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-busy and 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 after cursor, 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

CheckErrorFixThen call
A search engine, answer engine or network the call does not read, such as ChatGPT sent to Keyword.measurevenue-unsupportedPick 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
JSON
{
  "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

Text
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 valueAlways returnsWhat it is for
maxCost: 0.0001Refuses with max-costAny priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap.
rate-limited.testRefuses with rate-limitedAny call naming this hostname answers 429 with Retry-After, so your retry logic runs.
sources-busy.testRefuses with sources-busyAny 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.

TypeScript
await $.Monitor.create({
  verb: 'Ad.find',
  input: { advertiser: 'rentrival.example' },
  change: 'new-ad',
  every: '1w',
})

Used by

None.