OpenAdLibrary

Research native ads and competitors with OpenAdLibrary.

Community: Submitted by a user or imported; check the owner before granting accessOnlineNo sign-inGlobalFreeRead-only

What it can do

  • Search Ads: Search individual ads (one result per creative) by free text, advertiser domain, landing domain, country, ad format, ad network, search-arbitrage lander and active/inactive status. Each re
  • Render Research Results: Display a prepared or refined OpenAdLibrary result set with captured previews, saved boards, advertiser following and feed likes. First obtain structured results from search_l
  • Get Brand: Profile one advertiser domain: total distinct ads (capped at 10,000), total placements, first and last seen, placement counts by country and by ad network, and up to 100 of its most recentl

What data it sees

Do you need an account

No: the server works without sign-in

Research native ads and competitors with OpenAdLibrary. Search captured ads, inspect creatives and advertiser activity, and organize findings into saved research boards. Connect your account to work with Creative Studio designs and your own Track campaign reports. Captured advertising activity does not indicate competitor profitability.

How to connect

How to connect OpenAdLibrary to Claude

The server address to paste into Claude settings:

https://openadlibrary--hello-91i2.run.tools

  1. Open Claude (claude.ai or the desktop app).
  2. Go to Settings → Connectors.
  3. Click Add custom connector.
  4. Paste the server address copied below into Remote MCP server URL and click Add.
  5. In a chat, click + → Connectors and switch the new connector on.

Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.

Authorization

After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.

How to connect OpenAdLibrary to ChatGPT

The server address to paste into ChatGPT settings:

https://openadlibrary--hello-91i2.run.tools

  1. Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
  2. Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
  3. Open Settings → Connectors and click Create.
  4. Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
  5. Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
  6. In a new chat open + → Apps/Connectors and enable the connector.

OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.

Authorization

On first use ChatGPT opens the service's sign-in window. Sign in and approve access.

How to connect OpenAdLibrary to Cursor

The server address to paste into Cursor settings:

https://openadlibrary--hello-91i2.run.tools

Fastest: click Open in Cursor below and confirm the prompt.

Manually:

  1. In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
  2. Paste the JSON copied below into ~/.cursor/mcp.json (per project: .cursor/mcp.json in the repo root).
  3. Save the file. The server appears in the MCP list; authorize it there if asked.

Authorization

If the service needs sign-in, an authorize button appears next to the server in the MCP list.

Server tool list (60)

Raw names from tools/list. Only developers need these.

search_adsSearch individual ads (one result per creative) by free text, advertiser domain, landing domain, country, ad format, ad network, search-arbitrage lander and active/inactive status. Each result has the ad's id, headline, advertiser, format, network, traffic source, placement count, countries, active flag and first/last-seen dates; get_creative adds body text, image and landing domain. With `search`, results come from a window of at most 3,000 of the most recently seen ads matching the text (and `adFormat` / `status`); the country, network, advertiser/landing-domain and search-arbitrage filters are applied inside that window, so a common word combined with a narrow filter can return few ads even when more exist, `total` counts that filtered window, and `sort` reorders only within it. Without `search`, a total of 10,000 means 10,000 or more. `search` matches words in ad text, advertiser names, domains and landing-page text, supports OR and -term exclusions, and does not detect search arbitrage: use `searchArb`/`arbNetwork` for that. This search displays its structured results in the OpenAdLibrary app with captured previews. Summarize findings alongside the app; imageUrl is a widget asset path, not a Markdown image URL. Use render_research_results only when presenting a refined result set.
render_research_resultsDisplay a prepared or refined OpenAdLibrary result set with captured previews, saved boards, advertiser following and feed likes. First obtain structured results from search_library or search_ads. Those searches already display their results; call this tool when presenting a different selection or another data tool’s results. This presentation tool performs no research request and consumes no allowance. Counts represent observations, never competitor profitability.
get_brandProfile one advertiser domain: total distinct ads (capped at 10,000), total placements, first and last seen, placement counts by country and by ad network, and up to 100 of its most recently seen ads in the search_ads summary format. It matches ads whose resolved advertiser is the domain or whose landing domain is the domain (www-insensitive); the country and network counts cover the brand's newest 20,000 placements. Ad-tech and Microsoft hosts, and domains with no ads, return a not-found (404) error. For several domains at once use batch_brands; to find a domain from a brand name use search_advertisers.
list_brandsRank advertiser brands (domains) by how many distinct ads they run, optionally only among ads matching free text (`search`), countries or ad networks; each row has domain, name, creative count and last-seen date. With no filters the ranking covers the whole corpus (the same ranking list_advertisers gives for sort=creatives); with any filter it is computed from the newest 120,000 matching ad observations, so counts describe recent activity rather than all-time totals. `search`, `geoCountry` and `adNetwork` are the only filters. For other ranking metrics or a date range use list_advertisers; to find a brand's domain from its name use search_advertisers.
get_creativeFetch one ad by its public id (the `id` on ads returned by search_ads, walk_ads_keyset, sync_ads_changes, get_brand, get_publisher and get_landing_page_ads). Returns the same summary as search_ads plus the ad's body text, image path, landing domain and device types. The image path is relative to https://openadlibrary.com (/api/public/assets/...). It returns no click URL, redirect chain or landing-page content; an unknown or moderation-rejected id returns a not-found (404) error.
list_networksCount the ad networks among the most recently seen ads (at most 100, set by `sampleSize`), optionally only ads matching `search` or `geoCountry`, ranked by ads in that sample. It is a quick recency sample, not a corpus-wide list or market share: networks absent from the newest ads do not appear. Pass a returned name to search_ads' `adNetwork` filter or to get_network_profile; get_publisher_network_matrix lists the networks seen across a much larger recent sample (the newest 120,000 observations).
walk_ads_keysetWalk every ad in the corpus with an opaque cursor, ordered by first-seen date, newest first (search_ads' `newest` sort orders by last-seen instead). It takes no filters; use search_ads to filter. Pass the returned `nextCursor` as `after` to continue: the walk is finished only when `nextCursor` is null, and a page can hold fewer than `limit` ads, or none, before then.
sync_ads_changesReturn ads whose record was created or updated at or after `since`, oldest change first, for keeping a local copy in sync; omit `since` to start from the earliest record. Continue a run by passing `nextCursor` as `after` (null means caught up), and use the returned `serverTime` as the next run's `since`. Because `since` is inclusive, an ad at the boundary can come back again, so treat results as upserts keyed by `id`. `since` must be a full ISO-8601 timestamp with a time zone, such as a previous `serverTime`.
batch_brandsLook up headline stats for 1-25 advertiser domains in one call: for each, its name, total distinct ads, total placements, last-seen date, and how many of its most recent ads were loaded (capped at 200). Domains are lowercased, www-stripped, reduced to their registrable domain (lp.foo.com becomes foo.com) and de-duplicated, and each matches ads whose resolved advertiser or landing domain is that domain. A domain reported not found had no ads, is an ad-tech or junk host, or its lookup failed; one domain never fails the whole batch. This tool returns no first-seen dates, country or network breakdowns, or ad details; use get_brand for one domain's full profile.
list_advertisersAdvertiser leaderboard: `sort` = placements (default), creatives (distinct ads), running (earliest first-seen first) or recent (most recently seen first); each row has rank, domain, name, total creatives, total placements and last-seen date. Filter by country, ad network, activity date range, or `vertical` (the site section where the ad was seen, not an industry). Counts come from an hourly rollup; a filtered request the rollup cannot express is ranked from the full ClickHouse store instead (and from the newest 120,000 matching observations only if that fails), and an unfiltered request returns a retryable 503 error while the rollup is unavailable. To find a brand's domain use search_advertisers; to rank brands among ads matching free text use list_brands.
list_new_advertisersList advertisers seen on the platform for the first time within the last `windowDays` days, newest first-seen first, each with domain, name, first-seen time and `recentCreatives` (distinct ads in the window). An advertiser seen at any time before the window is excluded, even if it has just returned. This is first appearance anywhere on the platform; list_new_entrants instead tracks advertisers new to individual publishers. A retryable 503 error means the underlying rollup is temporarily unavailable.
search_advertisersFind advertiser domains by brand name or partial domain: the way to turn a name such as "Chase" into the domain that get_brand, batch_brands and list_similar_advertisers take. Returns up to `limit` matches with domain and name: an exact match first, then names or domains that start with the query, then ones that contain it, with advertisers that have more ads ranked first within each group. Right after a server restart, before that index is warm, it returns an unranked substring match instead (up to 30 rows). Ad-tech and Microsoft hosts are excluded, and a query shorter than 2 characters returns no results. It returns no ads or stats.
list_similar_advertisersFind likely competitors of one advertiser domain: other advertisers ranked by a 0-1 score blending shared ad networks (40%), shared site sections where their ads ran (25%), the same industry (20%) and shared landing domains (15%). The features come from the newest 120,000 ad observations, so an advertiser without recent ads, or any lookup shortly after a server restart, returns an empty list rather than an error. Returns domain, name and score for up to `limit` advertisers; if you only have a brand name, get its domain with search_advertisers.
get_trendingRank entities by how their activity changed between the current window and the previous window of equal length, from the latest precomputed momentum snapshot. `dimension` sets what is ranked: advertiser = advertiser domains, network = ad-network names, vertical = individual ads (each label is an ad id usable with get_creative), geo = landing domains; it does not rank verticals or countries. Each item has `label`, `direction`, `count` (ad observations in the current window) and `momentum` = (current - prior) / max(prior, 1), so 0.5 means +50%; `direction=new` returns only entities with no prior-window activity. `timeframe` picks the 7d or 30d snapshot (default: the latest). An empty `items` list means no snapshot exists for that choice or nothing in it matched the direction and filters (ad-tech domains are always filtered out).
get_share_of_voiceAdvertiser share of voice: rank advertisers (by domain) by their share of distinct ads within a chosen population, with each advertiser's ad and placement counts and an "Others" bucket so shares sum to about 100%. The cells are always advertisers: `dimension` and `scopeValue` choose the population (e.g. dimension=geo with scopeValue=US ranks advertisers within US ads), and dimension=advertiser or an omitted `scopeValue` means all ads. `geoCountry`, `vertical`, `adNetwork`, `industryCategory` and the date range add filters; on the scoped dimension itself, the matching filter replaces `scopeValue` rather than narrowing it. Shares come from the newest 120,000 matching ad observations, so they describe recent activity, and a cell's placement count can read 0 when that advertiser misses the separate placement ranking. The tool does not return shares per network, country or vertical.
get_perf_benchmarkLook up a modeled CPC (USD), CTR (%) and CPM (USD) benchmark for native or display ads. It reads a fixed, precomputed table of medians from an offline model, not measurements of any campaign, and returns the most specific row it has: format + country + IAB tier-1 category, else format + country, else the format as a whole. `matchedScope` says which matched (cell, geo, format or none), `sampleSize` how many observations back it, and `source` the table version. `iabTier1` (an id such as IAB13) narrows the result only when `geo` is also given.
get_copy_insightsSummarize ad-copy messaging (headline, body and image text) for the whole library or for one advertiser: the 40 most frequent 1-3-word phrases, counts for a fixed list of 20 English call-to-action phrases (such as "shop now" and "learn more"), and counts for five emotional-trigger categories (urgency, curiosity, value, fear, social). Every count is the number of ads whose copy contains the item as a substring (so "claim" also counts "disclaimer"), from an unordered sample of up to 5,000 ads, and items with a zero count are left out. With scope=advertiser, `value` is a domain matched against the ad's advertiser or landing domain; a missing or ad-tech domain returns empty lists. For phrases weighted toward long-running ads use get_copy_patterns; for term momentum over time use get_keyword_trends.
get_keyword_trendsReturn the 100 most frequent 1-3-word phrases in ad copy (headline, body and image text) among ads seen in the current window, each with `count` (ads containing it) and `momentum` = (count - prior count) / max(prior count, 1) versus the previous window of equal length. The list is ordered by frequency, not by momentum, so the fastest-rising terms can appear anywhere in it. Counts come from an unordered sample of up to 5,000 ads per window with English stopwords removed, and `timeframe=ALL` has no prior window, so every momentum is 0. For one advertiser's messaging use get_copy_insights.
get_trending_domainsList rising domains (hosts whose number of distinct ads grew versus the previous window of equal length), sorted by `momentum` = (current - prior) / max(prior, 1), each with `count` (distinct ads in the current window); falling and flat domains are not returned. `type=landing` uses the landing domain recorded on each ad and `type=offer` the final domain of captured landing pages, both excluding ad-tech and Microsoft hosts; `type=tracker` uses the same captured final domains as `offer` without that exclusion, so it is not a list of redirect or tracking hops. Each window reads at most the 5,000 most recent ad observations (landing) or landing captures (offer, tracker), so counts are sample-based. Returns bare hosts, never URLs.
list_publishersRank publisher sites, each named by its registrable domain (cnn.com rather than edition.cnn.com), by distinct ads (`creatives`, the default), distinct advertisers or `placements`. Each row has host, name, the three counts and `industry`, the most common IAB tier-1 category among its ads. Counts cover every non-rejected observation on record and come from a ranking refreshed on a schedule, so the newest ads may not be counted yet; ads from social platforms are grouped under the host "social". Pass a host to get_publisher, get_publisher_monetization or list_similar_publishers.
get_publisherProfile one publisher site by its registrable domain (msn.com, not www.msn.com or ntp.msn.com): distinct ads, placements, first and last seen, countries, devices, ad networks, top advertisers (up to 50), direct-buy share and IAB category mix, plus up to `adsLimit` of its most recently seen ads. Every figure is computed from the newest 120,000 ad observations across all publishers, so it describes recent activity only, and `firstSeenAt` / `daysRunning` are bounds of that sample. A publisher with no observations in that sample returns a not-found (404) error even if it ran ads earlier.
get_publisher_monetizationShow how one publisher site monetizes, keyed by its registrable domain (msn.com, not www.msn.com): its top 50 advertisers and top 25 ad networks by distinct ads, and the direct / programmatic / unknown split of its placements with the direct share. Advertiser concentration (top-1 share, top-5 share, HHI from 0 to 1, buyer count) is included only for lower-volume sites computed on request; busy sites come from a precompute that omits it. Computed from the newest 120,000 ad observations across all publishers, so it reflects recent activity only. A publisher with no observations in that sample returns a not-found (404) error. get_publisher includes the same top-advertiser list plus countries, devices and recent ads.
list_similar_publishersFind publisher sites whose set of ad networks overlaps a seed site's (keyed by registrable domain, e.g. yahoo.com), scored 0-1 as shared networks divided by all networks the two sites use (Jaccard similarity). Computed from the newest 120,000 ad observations across all publishers; a seed site absent from that sample, or without network data, returns an empty list rather than an error. For advertisers use list_similar_advertisers.
get_publisher_network_matrixCross-tabulate ad networks against publisher sites: each cell is a (network, publisher) pair with its number of distinct ads, largest first, capped at `limit` cells. The response also lists every network and every publisher present in the data, which can be long. Computed from the newest 120,000 ad observations across all publishers, so it reflects recent activity only.
get_network_profileProfile one ad network by name (case-insensitive exact match, e.g. "Taboola"): distinct ads, advertisers and publishers, its top 20 advertisers and top 20 publishers by distinct ads, and first and last observation times. Figures come from a bounded recent sample of at most 120,000 ad observations, so `firstSeenAt` is the network's earliest observation within that sample, not when it first appeared. An unknown name returns a profile with zero counts rather than an error. Names are the `network` values shown on ads in search_ads results.
get_supply_chain_publisherList the sellers a publisher domain authorizes in its ads.txt: one row per seller domain and relationship (DIRECT or RESELLER), with `count`, the number of seller accounts listed for that pair, sorted by seller domain (at most 1,000 rows). An empty list means that site's ads.txt has not been crawled; an invalid domain returns a 400 error. For the ad networks actually observed on the site use get_publisher_monetization.
get_supply_chain_exchangeList the participants an exchange or SSP declares in its sellers.json: one row per participant domain and seller type (PUBLISHER, INTERMEDIARY or BOTH), with `count`, the number of seller entries for that pair, sorted by domain (at most 1,000 rows). Seller ids and names are not included. An empty list means that exchange's sellers.json has not been crawled; an invalid domain returns a 400 error.
list_new_entrantsFeed of advertisers and IAB categories that newly appeared, or spiked anomalously in volume, on one or more publishers, from the latest precomputed snapshot. Each item has `label` (an advertiser domain or IAB category label), `kind`, `firstSeenAt`, `publisherCount` (how many publishers it is new or spiking on) and `spike`, ordered by publisherCount. `signal` picks new entrants, spikes or both, and `timeframe` pins the 7d or 30d snapshot (default: the latest). An empty `items` list means no snapshot exists or nothing in it matched `signal`. For advertisers appearing on the platform for the first time, use list_new_advertisers.
get_copy_patternsMine the 1-3-word headline phrases most associated with long-running, high-placement ads, from up to 5,000 in-scope ads with the most placements; phrases are ranked by a winner score that grows with each carrying ad's days running and placements. Returns up to `limit` themes, each with `creativeCount` (ads whose headline contains the phrase) and up to five sample headlines. `scope=vertical` needs `scopeValue` set to an exact IAB Tier-1 category name (e.g. "Personal Finance"), and `scope=advertiser` needs a domain, matched against the ad's advertiser or landing domain; a missing `scopeValue` or an internal failure returns an empty list.
list_competitive_setsList the competitive sets: one per IAB Tier-1 content category (for example Personal Finance), each grouping every advertiser assigned to that category, largest first. Each set has an opaque `ref`, a `label` (the category name), a `size` (advertisers assigned to it; get_competitive_set_members can list fewer, because it drops filtered hosts) and up to five prominent member domains. These are broad category groups, not a brand's direct competitors: for competitors of one advertiser use list_similar_advertisers. Pass `ref` to get_competitive_set_members for the members.
get_competitive_set_membersPage through the advertisers in one competitive set (an IAB Tier-1 category group), identified by the `ref` from list_competitive_sets. Returns each member's domain and name, most prominent first; the response has no total, so the last page is the first one with fewer than `pageSize` members. An unknown ref returns a not-found (404) error.
list_network_entitiesBrowse the ad-tech entity directory (networks, exchanges, SSPs and other intermediaries), sorted alphabetically by name, with each entity's opaque `entityRef`, `name`, `role`, `primaryDomain` and `requestCount` (captured network requests attributed to that entity). `search` is a case-insensitive substring match on name or primary domain, and `pageSize` sets the page size (max 100). For one ad network's advertisers and publishers use get_network_profile.
get_brand_hierarchyLook up recorded corporate relationships for an advertiser domain, one level deep, as domains and names only. If the domain has a recorded parent, the result is that `parent` plus the parent's other subsidiaries in `children` (the domain's siblings, excluding itself); otherwise `parent` is null and `children` lists the domain's own subsidiaries (up to 200). An empty result means no relationship is recorded for the domain, not that it has none; an invalid domain returns a not-found (404) error.
list_landing_pagesList destination domains from captured landing pages, most recently captured first, each with `captures` (completed captures that have a screenshot) and `screenshotUrl` from the latest capture. `scope` is an optional free-text filter over the final domain, page title and landing-page text. Only the 1,000 most recently captured domains are considered, so `total` is at most 1,000, and one domain can appear twice when captures recorded it both with and without www. `screenshotUrl` is a site-relative path (/api/v1/landing-screenshots/...) that returns the image only when requested with the caller's API key, so it does not work as a plain link. No tool in this server returns destination URLs or redirect chains.
get_landing_pageSummarize captured landing pages for one destination domain (www-insensitive): `captures` (completed captures with a screenshot), `linkedAdCount` (how many of those captures are linked to an ad observation, not a count of distinct ads), first and last capture times, and `screenshotUrl` from the latest capture. `screenshotUrl` is a site-relative path that returns the image only when requested with the caller's API key. A domain with no completed captures returns a not-found (404) error. For the ads that point to the domain use get_landing_page_ads.
get_landing_page_adsList ads associated with a destination domain, most recently seen first, with a paginated total of distinct ads: ads whose recorded landing domain, resolved advertiser domain, or captured final landing-page domain equals it (www-insensitive). Results use the same summary format as search_ads. Ad-tech and Microsoft hosts (e.g. taboola.com, msn.com) return a not-found (404) error. Unlike search_ads' `landingDomain` filter, a substring match on the recorded landing domain only, this also matches the final domain reached after redirects.
open_ad_libraryOpen OpenAdLibrary from ChatGPT navigation or beside the conversation. Browse captured ads, boards, follows and saved research with your current account policy. Optional boardId or creativeId opens that item. This does not save, follow, like, spend AI funds or visit live ads.
open_studio_designsOpen your saved still-image designs in a native ChatGPT app view. Preview a design and open Creative Studio on the website for visual editing. This creates no design, spends no AI allowance and publishes no advertising.
open_track_workspaceOpen your measured Track campaign report and configuration views from ChatGPT navigation or alongside a conversation. Starts at the last seven UTC days. Opening does not change campaign routing, serving, bids or budgets.
read_app_settingsRead the connected account’s native ChatGPT display preferences and their native settings schema. Does not change allowances, permissions or data.
update_app_settingsSave requested native ChatGPT display preferences to the connected user’s account. Partial updates preserve other preferences. This does not change billing policy, permissions, feed learning or monitoring.
search_mentionsFind accessible research boards and saved original designs for ChatGPT’s composer mention picker. Uses bounded account lists without running corpus research on each keystroke. Selected resources are read under the connected account’s current permissions.
get_research_trendsRead daily captured observation counts and newest observed publishers for a bounded batch of visible creatives. Uses the same service as website Library cards, separately from search. Unavailable data stays unavailable; these counts do not measure profitability.
get_research_feed_optionsSearch the website’s feed topic vocabulary and available advertiser/filter choices. Use returned topic IDs in feed interests. These are selection choices, not evidence of advertiser performance. Omitting q returns the complete topic vocabulary.
set_research_feed_archivedArchive or restore one owned feed using its current revision from get_research_feed. Archiving preserves settings and likes. Monitoring capacity still applies. A stale revision is rejected.
duplicate_research_feedCreate a separate owned feed using the selected feed’s settings through the website’s duplication service. Current monitoring capacity still applies. Reuse operationId for retries to avoid extra copies.
get_research_feedRead one owned feed’s current preferences and revision before editing. Feed preferences include the user’s chosen interests and followed advertisers.
update_research_feedApply requested preference changes using the revision from get_research_feed. Omitted preferences are preserved. A stale revision is rejected; read current settings before preparing another update. Never enable passive learning without the user’s request.
browse_research_feedRead one personalized feed using its current settings and feedback. Uses live shared research allowances. Cursors preserve the feed session; this does not record passive exposure or like ads.
compare_research_adsCompare selected captured creatives using server-verified observations. Enforces the account’s current advertiser comparison capacity and research allowance. Measurements are captured placements, never competitor profits.
export_research_adsExport selected captured ads as a CSV. The server enforces the current daily export-row allowance and excludes private artifacts. Reuse operationId for retries. Returns an expiring download to the widget only.
search_libraryPreferred search for connected accounts. Searches and displays results in the OpenAdLibrary app with captured previews. Uses the same search handler, filters, access rules and live allowance as the website. Supply query using Library filter names; returns structured results and the effective query for pagination and saving. Summarize findings alongside the app; imageUrl is a widget asset path, not a Markdown image URL. Use render_research_results only for a refined result set. Only observed placements, never live ad clicks or profitability claims.
find_similar_creativesFind visual or semantic matches using the Library similarity service and existing access checks. Fallback match modes are disclosed. This is research, not permission to reuse competitor assets.
update_research_boardRename an editable board or change its visibility to PRIVATE or ORG. ORG shares it with the current organization; require explicit user intent before sharing. Server ownership checks apply.
get_research_stateRead current saved status and advertiser monitoring for the supplied visible ads. Optionally check membership in one accessible board. Private account read; never guesses state when permissions are unavailable.
list_research_boardsList your own and shared organization boards in the current workspace, with pagination and optional name search. Shared boards owned by another user are read only. Use get_research_board to open a board.
create_research_boardCreate a private research board under the live saved-board allowance. Only create on explicit user request; creation is not retry-idempotent.
get_research_boardRead one accessible board and a page of its saved ads. Returns a redacted research projection without raw redirect URLs or private asset links.
save_ad_to_boardSave a visible creative to an owned or otherwise editable board. Existing saves remain idempotent. Server ownership and live inventory allowances apply.
remove_ad_from_boardRemove only the selected ad from the selected editable board; keep the captured ad and other boards.