Agent research API
Mosaic’s v1 product surface is six tools that AI agents call directly (ADR 0026).
Each tool is one REST operation in the operation catalog, and its MCP tool is generated from the
same entry, so inputs, outputs, and error codes are identical on both transports. The default MCP
catalog at https://api.mosaic.dev/mcp is these six tools, and its tools/list advertises only
the implemented ones: today all six. An unimplemented tool would not be listed or callable,
so an agent is never offered a tool that fails on every call.
| Tool | REST | Status |
|---|---|---|
get_meta_company_ads |
POST /v1/meta/company-ads |
Implemented locally; not deployed |
get_research_result |
GET /v1/research-requests/{requestId} |
Implemented locally; not deployed |
retrieve_brand |
POST /v1/brands/retrieve |
Implemented locally; paid fresh work once configured |
find_creator_socials |
POST /v1/creators/socials |
Implemented locally, Mosaic’s own signals (unpriced) |
search_youtube_sponsorships |
POST /v1/youtube/sponsorships |
Implemented locally; paid fresh work once configured |
get_usage |
GET /v1/usage |
Implemented locally; not deployed |
retrieve_brand and search_youtube_sponsorships run fresh paid work exactly like
get_meta_company_ads once the deployment publishes their tariff, which is a configuration
change (proposed: 10 and 5 Work Credits). Until then, and on any deployment that has not
published them, they answer from data Mosaic already holds for free and refuse fresh work with
tariff_unpublished (see Unpublished tariffs). find_creator_socials
answers free from Mosaic’s own signals; its external provider discovery has no tariff.
Authentication, the error envelope, Request-Id, and rate limits follow the
API reference. Monitoring operations listed there are frozen: their data
and REST routes remain, and they are not in the default MCP catalog. get_work_credit_account
and get_workspace_billing are no longer MCP tools; get_usage replaces them, and their REST
operations (GET /v1/work-credits, GET /v1/billing) remain.
Calling pattern
Section titled “Calling pattern”agent ── get_meta_company_ads(pageId, maxAgeMs, maxCredits, key) ──▶ Mosaic ◀── succeeded (cache hit, 0 credits) → read ads, page with get_research_result ◀── pending (fresh work, credits held) → poll get_research_result(requestId) ◀── resolution_required (candidate Pages) → call again with the chosen pageIdThere is no quote step. A call either answers from cache for free, starts fresh work under a fixed tariff, or asks the agent to choose a Page.
Acquisition inputs
Section titled “Acquisition inputs”The four acquisition tools (get_meta_company_ads, retrieve_brand, find_creator_socials,
search_youtube_sponsorships) share three inputs.
| Input | Rule |
|---|---|
maxAgeMs |
Oldest acceptable source observation, in milliseconds, measured from when Mosaic observed the source. 0 requires fresh work. Each tool sets its own default and maximum. |
maxCredits |
The most Work Credits this call may hold and charge. Defaults to the tool’s published tariff, or 0 while it is unpublished. 0 means cache-only: a cache hit succeeds and paid work is never dispatched. Fresh work needs maxCredits at least the tariff. |
idempotencyKey |
Required. REST sends it as the Idempotency-Key header. Repeating a call with the same key and inputs returns the original answer and never charges twice; the same key with different inputs is 409 conflict. |
A replay sets the Idempotent-Replayed: true header in REST and
_meta["dev.mosaic/idempotent-replayed"] in MCP. Page-size inputs such as limit are not part of
the replay identity.
Freshness, cache hits, and pending work
Section titled “Freshness, cache hits, and pending work”- Cache hit. When a completed observation of the same source and scope is no older than
maxAgeMs, the call answers synchronously withcacheHit: trueandcreditsCharged: 0. Observations from any Workspace’s research count; the requesting Workspace is never revealed to others. - Fresh work. Otherwise Mosaic holds the tariff before any provider work starts and answers
status: "pending"with arequestIdandpollAfterMs. Meta runs measured 7–166 seconds. - Settlement. Bounded work that completes, including a valid empty result, settles the hold:
creditsChargedequals the tariff. Failed or interrupted work releases the hold and answersstatus: "failed"withcreditsCharged: 0. A provider start whose outcome is unknown keeps the hold for at most 24 hours from dispatch, then releases it without charge. - Unpublished tariff. A tool whose tariff is not published yet answers cache hits as usual
and refuses fresh work with
409 tariff_unpublished, before any hold or record.get_usagelists the tariff asunpublished. Like the spending refusals, it is a business state, not an outage: repeating the same call cannot succeed. - Polling, pagination, and replays are free.
get_research_resultnever charges.
Result fields every answer carries
Section titled “Result fields every answer carries”| Field | Meaning |
|---|---|
status |
succeeded, pending, failed, or resolution_required |
observedAt |
When Mosaic observed the source for this answer; null until it succeeds |
coverage |
The bounded scope searched, whether it completed or hit a cap, and truncated |
classifierVersion |
The pinned classifier that produced each classification |
cacheHit |
true when the answer reused an earlier observation |
creditsCharged |
Credits settled for this call: 0 for cache hits, pending work, failures, and resolution |
creditsHeld |
Credits currently held for pending work |
An empty or failed result is never proof of absence. coverage states what was searched;
failed means Mosaic could not observe the source.
get_meta_company_ads
Section titled “get_meta_company_ads”POST /v1/meta/company-ads · MCP get_meta_company_ads · write scope · tariff 25 Work Credits per fresh call
Returns the active US Meta Ads Library ads of one advertiser Page, each classified from Meta’s explicit branded-content attribution.
| Field | Type | Notes |
|---|---|---|
pageId |
numeric string | A Meta Page ID. Exactly one of pageId, companyName, domain. |
companyName |
string, 1–200 | Resolved against canonical Brand names and Page names Mosaic has observed. Case-insensitive exact match; no fuzzy matching. |
domain |
hostname | Resolved against canonical Brands’ primary company domains. Social, hosting, and link-in-bio hosts are rejected. |
maxAgeMs |
integer, 0–604 800 000 | Default 259 200 000 (72 hours). Maximum 7 days. |
maxCredits |
integer, 0–1 000 000 | Default 25, the current tariff. 0 is cache-only. Fresh work needs at least 25. |
limit |
integer, 1–100 | Ads on the first page. Default 25. |
idempotencyKey |
string, 1–255 | Required. REST header Idempotency-Key. |
Resolution
Section titled “Resolution”A pageId is researched directly. A companyName or domain is resolved first, using only data
Mosaic already holds:
| Outcome | When | Answer |
|---|---|---|
exact |
Exactly one active canonical Brand matches, and it has exactly one effective confirmed Meta Page | Research that Page |
ambiguous |
Several Brands or Pages match, or the match is only an observed Page name | status: "resolution_required" with candidate Pages |
not_found |
Nothing matches | status: "resolution_required" with no candidates and a message that the agent must supply pageId |
Each candidate carries its basis: catalog_brand_confirmed_page (a staff- or
onboarding-confirmed Page of a canonical Brand) or observed_page_name (a Page Mosaic has seen
with that exact name, which proves nothing about ownership). Resolution never charges, holds,
or records a request, and the idempotency key is not consumed. Call again with the chosen
pageId. Mosaic has no Page search yet; for an unknown company, the agent must find the Page ID in the
Meta Ad Library and pass pageId.
Research subject
Section titled “Research subject”The first call for a Page creates a durable research subject: { id, source: "meta", pageId, pageName, pageUrl, canonicalBrandId }. canonicalBrandId is set only when the Page is an
effective confirmed Meta target of exactly one active canonical Brand; otherwise it is null.
Research never creates a Brand, Matcher, confirmed Source Target, Context.dev retrieval, tracking,
or Event. Explicit partnerships of a linked subject also become global Relationship evidence;
those of an unlinked subject stay in this answer and its receipt.
Attribution
Section titled “Attribution”Each ad reports the parties exactly as Meta displayed them and one of three outcomes:
attribution |
basis |
Rule |
|---|---|---|
explicit_partnership |
requested_page_displayed_partner_tagged |
The requested Page displayed the ad; a distinct Page is the branded-content party |
explicit_partnership |
partner_displayed_requested_page_tagged |
A distinct Page displayed the ad; the requested Page is the branded-content party |
brand_only |
no_branded_content_party |
The requested Page displayed the ad; Meta reported no branded-content party |
brand_only |
requested_page_tagged_itself |
The requested Page displayed the ad and is also its own branded-content party |
unknown |
display_page_missing |
The display Page is missing or unreadable |
unknown |
branded_content_malformed |
The branded-content field is present but unreadable |
unknown |
branded_content_state_unrecorded |
The row predates recording of the branded-content field’s state |
unknown |
requested_page_not_a_party |
Neither the display nor the branded-content Page is the requested Page |
brand_only means no explicit partnership was observed in Meta’s attribution fields, not that
none exists. A missing or malformed field is always unknown. Classification uses only typed
Page fields: ad copy, images, and links never decide it. partner is the other party of an
explicit partnership; when the requested Page is a company, the partner is the creator. It
carries creatorId when Mosaic’s catalog already links that Page to a Creator.
Brand-only ads are retained as minimal rows, so they carry identity, parties, and start date but
no creative. Explicit partnerships also carry the ad’s text, title, and link. Ads hidden by
suppression or erasure are omitted and counted in coverage.counts.hidden.
Answer
Section titled “Answer”{ "data": { "object": "research_result", "id": "5b0c…", "workspaceId": "1f3a…", "kind": "meta_company_ads", "status": "succeeded", "cacheHit": true, "creditsCharged": 0, "creditsHeld": 0, "maxCredits": 25, "tariff": { "name": "meta-brand-research-v1", "credits": 25 }, "observedAt": "2026-10-13T18:02:11.000Z", "classifierVersion": "meta-attribution-2026-10-14", "pollAfterMs": null, "resolution": { "input": { "kind": "page_id", "value": "620732004665539" }, "outcome": "exact", "candidates": [], "message": null }, "subject": { "id": "0d2e…", "source": "meta", "pageId": "620732004665539", "pageName": "Skylight", "pageUrl": "https://www.facebook.com/620732004665539", "canonicalBrandId": null }, "coverage": { "scope": { "country": "US", "activeStatus": "ACTIVE", "providerResultCap": 2000 }, "status": "complete", "truncated": false, "returnedAdCount": 97, "counts": { "explicitPartnership": 12, "brandOnly": 84, "unknown": 1, "hidden": 0 } }, "failure": null, "createdAt": "2026-10-14T17:20:00.000Z", "ads": { "data": [ { "archiveId": "1682112446211506", "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1682112446211506", "startedAt": "2026-09-30T00:00:00.000Z", "attribution": "explicit_partnership", "basis": "partner_displayed_requested_page_tagged", "requestedPageRole": "tagged", "displayPage": { "pageId": "571154359564610", "name": "Target Does It Again" }, "taggedPage": { "pageId": "620732004665539", "name": "Skylight" }, "partner": { "pageId": "571154359564610", "name": "Target Does It Again", "creatorId": null }, "creative": { "text": "…", "title": null, "linkUrl": "https://…" } } ], "nextCursor": "eyJ2Ijox…" } }}Ads are ordered explicit partnerships first, then brand-only, then unknown, each by archive ID.
ads is null unless status is succeeded. startedAt is the ad’s start date when Meta
reported it; Meta shows US commercial ads only while active, so observedAt is when Mosaic saw
the ad, not when a partnership began.
Every status answers 200. A pending answer carries id, creditsHeld: 25, and
pollAfterMs. A resolution_required answer has id: null, subject: null, the candidates
under resolution, and a resolution.message that tells the agent to choose a candidate or
supply pageId.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
| 400 | invalid_input |
Not exactly one of pageId, companyName, domain; a malformed value; a social or hosting domain; maxAgeMs out of range |
| 404 | not_found |
The Page cannot be researched (for example, it is suppressed) |
| 409 | conflict |
The idempotency key was used with different inputs |
| 409 | credit_limit_below_quote |
Fresh work is needed and maxCredits is below the tariff (including 0, cache-only); spending cap max_credits |
| 409 | insufficient_credits |
Fresh work is needed and the Workspace cannot fund the tariff; spending cap work_credit_balance |
| 409 | billing_frozen |
New paid work is frozen pending billing review; spending cap billing_review. Cache hits still succeed |
| 409 | collection_in_progress |
Fresh work for this Page is already running. Retry after it finishes with maxAgeMs > 0 to reuse it free |
| 429 | rate_limited |
Too many requests |
| 503 | collection_disabled |
No cached result is fresh enough and fresh Meta collection is not enabled or has no tariff; cache hits still succeed |
The three spending refusals carry the cap in error.details, with every value a string, and
nothing was held or dispatched:
{ "error": { "code": "insufficient_credits", "status": 409, "message": "This work needs 25 Work Credits; 4 are available. Nothing was charged or dispatched. Do not retry until a human buys a credit pack.", "requestId": "…", "details": { "cap": "work_credit_balance", "action": "buy_credit_pack", "retryable": "false", "requiredCredits": "25", "availableCredits": "4" } }}details.cap |
details.action |
Other keys |
|---|---|---|
work_credit_balance |
buy_credit_pack |
requiredCredits, availableCredits |
max_credits |
raise_max_credits |
requiredCredits, maxCredits |
billing_review |
contact_support |
requiredCredits |
overage_cap (legacy subscriptions only) |
raise_overage_cap |
requiredCredits, availableCredits |
retryable is always "false": none of these refusals is lifted by repeating the call. Over
MCP the same details appear in the tool error with a stop hint.
Unpublished tariffs
Section titled “Unpublished tariffs”A tool whose tariff get_usage reports as unpublished never holds, charges, or dispatches
provider work. When an answer needs fresh work, the call is refused before anything is recorded,
whatever maxCredits says, and the idempotency key is not consumed:
{ "error": { "code": "tariff_unpublished", "status": 409, "message": "No cached Context.dev profile of this domain is within maxAgeMs, and fresh retrieval has no published tariff, so nothing was charged or dispatched. …", "requestId": "…", "details": { "product": "brand_profile", "tariff": "unpublished", "action": "use_cached_result", "retryable": "false" } }}product is the get_usage tariff product (brand_profile, creator_socials, or
youtube_sponsorship_search). The refusal is 409 on every tool, like the spending refusals:
nothing is held, and retryable is "false" because repeating the call cannot succeed until the
price is published. Over MCP the same details appear in the tool error with one static stop
hint. Cached results keep answering for free.
get_research_result
Section titled “get_research_result”GET /v1/research-requests/{requestId} · MCP get_research_result · read scope · free
Returns the same research_result object for a request this Workspace created, whichever
research tool created it, including its current status and one page of results. Its kind
names the tool: meta_company_ads, brand_profile, creator_socials, or
youtube_sponsorship_search. Inputs: requestId, cursor (from ads.nextCursor,
candidateAccounts.nextCursor, or videos.nextCursor), and limit (1–100, default 25). A
cursor is valid only for the same request. A request from
another Workspace and a missing request both answer 404 not_found. A malformed cursor is
400 invalid_input.
A failed answer carries failure.reason: acquisition_failed (the provider run failed),
coverage_incomplete (the run stopped before a bounded result), or start_unconfirmed (the
provider start could not be confirmed within 24 hours).
retrieve_brand
Section titled “retrieve_brand”POST /v1/brands/retrieve · MCP retrieve_brand · write scope · cached profiles free; fresh retrieval at the published tariff (proposed 10 Work Credits), unpublished by default
Returns a Context.dev brand profile for a company domain, with provenance on every value. It is enrichment: the profile never confirms an identity, and calling the tool never creates or changes a canonical Brand, its curated profile, a Matcher, or a Source Target (CONTEXT invariant 12).
| Field | Type | Notes |
|---|---|---|
domain |
hostname | A company domain. Exactly one of domain, pageId. Social, hosting, and link-in-bio hosts are rejected. |
pageId |
numeric string | A Meta Page, such as get_meta_company_ads’ subject.pageId. It resolves only to the confirmed primary domain of the one canonical Brand it is an effective confirmed Meta target of. |
maxAgeMs |
integer, 0–31 536 000 000 | Default 7 776 000 000 (90 days). Maximum 365 days. Measured from Context.dev’s own observation of the values. |
maxCredits |
integer, 0–1 000 000 | Defaults to the published tariff, or 0 while it is unpublished. 0 is cache-only. Fresh retrieval needs at least the tariff. |
idempotencyKey |
string, 1–255 | Required. REST header Idempotency-Key. |
A pageId that no exact identity match links to one canonical Brand with a primary domain answers
status: "resolution_required" with resolution.outcome not_found (or ambiguous, listing each
Brand’s domain), no charge, and a message to call again with domain. Mosaic never infers a
domain from a Page or a name.
Answer
Section titled “Answer”The common result fields, kind: "brand_profile", and:
subject:{ id, source: "domain", domain, canonicalBrandId }.canonicalBrandIdis set when the domain is the primary company domain of exactly one active canonical Brand.profile:domain;identity(catalog_brandwhencanonicalBrandIdis set, elseunconfirmed; the values are Context.dev candidates either way);nameanddescription(each a field ornull); andindustries,logos({ role, url }), andsocialLinks({ network, url }), each a list of fields. A field is{ value, provenance: { provider: "context.dev", snapshotId, retrievedAt, sourcePath } }.coverage:{ provider: "context.dev", lookup: "by_domain", status: "complete", truncated: false }.observedAtand everyretrievedAt: when Context.dev observed the values (its cache age applied to Mosaic’s retrieval time), not when Mosaic stored them.snapshotIdnames the retained snapshot or fresh-retrieval receipt the values came from.profileandcoveragearenullwhilestatusispendingorfailed.- Social links to a suppressed or erased Meta Page or YouTube Channel are omitted whenever the
result is read, and reappear if a suppression is reversed. Accounts are recognized by their
native ID (
facebook.com/<Page ID>,youtube.com/channel/UC…); a vanity URL is not matched. Erasure also removes them from the stored receipt, including when the Page or Channel is erased while the retrieval is in flight; the retrieval is still charged.
Cache and fresh work
Section titled “Cache and fresh work”Any retained successful Context.dev profile of the domain observed within maxAgeMs answers
synchronously with cacheHit: true and creditsCharged: 0, whichever Workspace’s research, staff
curation, or onboarding retrieved it. Polling and replays are free.
Otherwise, with a published tariff, the call holds the tariff and answers status: "pending"
with creditsHeld and pollAfterMs; Mosaic’s worker then calls Context.dev with the same
maxAgeMs (so Context.dev’s own cache cannot answer with an older profile; 0 forces a refresh),
allowing it 60 seconds (cold retrievals measured a 14.5-second median, with some past 25 seconds), so poll
get_research_result:
| Context.dev outcome | Result |
|---|---|
| A profile (Context.dev bills it, whether or not it served from its cache) | succeeded; the hold settles: creditsCharged = tariff |
| 400, 404, or 422 (no company), 408 (its timeout), 401, 429, or a malformed or partial envelope | failed, failure.reason: "acquisition_failed", no charge |
| No answer: the connection failed, Mosaic’s wait ended, or a 5xx | failed, failure.reason: "start_unconfirmed", no charge |
| Not sent: the deployment can no longer call Context.dev | failed, failure.reason: "acquisition_failed", no charge |
Context.dev has no idempotency key, so each fresh request is sent to Context.dev at most once:
an unanswered request is never sent again and never charged. Call again with a new key to try
once more. A fresh profile is retained as a research receipt that any Workspace can reuse as a
cache hit; it is never applied to a canonical Brand’s profile. While another fresh retrieval of
the same domain is running, the call is 409 collection_in_progress; retry after it with
maxAgeMs > 0 for free.
Without a published tariff, the call is 409 tariff_unpublished: it holds, dispatches, and
records nothing.
Errors
Section titled “Errors”As get_meta_company_ads (including the spending refusals and collection_in_progress), plus
409 tariff_unpublished (above). 400 invalid_input covers a missing or doubled reference and
social or hosting hosts.
find_creator_socials
Section titled “find_creator_socials”POST /v1/creators/socials · MCP find_creator_socials · write scope · free from Mosaic’s own signals; provider discovery unpriced
Returns a creator’s public social accounts. Staff-confirmed accounts and candidate accounts are separate lists. Mosaic never merges identities from candidates (CONTEXT invariant 14), never promotes a candidate to confirmed, and never returns contact details such as emails, business inquiry links, websites, or management lines (ADR 0025).
| Field | Type | Notes |
|---|---|---|
creatorId |
UUID | A canonical Creator. Exactly one of creatorId, pageId, channelId, url. |
pageId |
numeric string | A Meta Page, such as an explicit partnership’s partner.pageId from get_meta_company_ads. |
channelId |
UC… string |
A canonical YouTube Channel ID. |
url |
string, 1–255 | A facebook.com Page URL, or a youtube.com/channel/UC… or youtube.com/@handle URL. A handle resolves only to an account Mosaic observed with exactly that handle. Other platforms are 400 invalid_input. |
maxAgeMs |
integer, 0–7 776 000 000 | Default 2 592 000 000 (30 days), maximum 90 days. Bounds reuse of provider discovery; 0 asks for fresh provider discovery. |
maxCredits |
integer, 0–1 000 000 | Default 0 while the tariff is unpublished. |
limit |
integer, 1–100 | Candidates on the first page. Default 25. |
idempotencyKey |
string, 1–255 | Required. REST header Idempotency-Key. |
An unknown @handle answers resolution_required with no charge. A governed (suppressed or
erased) subject, or an unknown creatorId, is 404 not_found.
Answer
Section titled “Answer”The common result fields, kind: "creator_socials", and:
subject:{ id, source: "meta_page" | "youtube_channel" | "creator", externalId, url }.creator:{ id, displayName }of the canonical Creator staff confirmed for the subject, ornull.confirmedAccounts: the Creator’s staff-confirmed Source Accounts (Meta Pages and YouTube Channels) and staff-approved social links, each{ platform, nativeId, nativeIdKind, url, confirmation: { kind: "source_account" | "social_link", confirmedAt } }.candidateAccounts: one page of candidates,{ data, nextCursor }. Each candidate carriesplatform(facebook,instagram,tiktok,x,youtube),nativeIdandnativeIdKind(page_id,channel_id, orhandle),url,sourceSubject(the researched or confirmed account whose evidence found it),evidenceUrl(a public page showing the evidence),discoveryMethod,observedAt,evidenceCount, and a categoricalbasis.coverage:methodssearched,providerDiscovery(not_rununtil the tariff is published),statusandtruncated(cappedwhen the description-link scan hit its bound), andcountsof confirmed, candidate, and governance-hiddenaccounts.
basis |
Meaning | Strength |
|---|---|---|
profile_link_back |
The candidate’s own profile links to the researched account | Linked evidence |
explicit_outbound_link |
The researched account’s profile, or its own video descriptions, link to the candidate | Linked evidence |
provider_reported |
An external profile-discovery provider reported it (not composed yet) | Provider claim |
name_handle_similarity |
Only its name or handle equals the researched account’s, after removing case and punctuation | Weak; corroborate before relying |
Candidates are ordered by basis in that order, then by evidenceCount. There are no scores or
percentages: none of these is identity confirmation, and only a staff decision moves an account to
confirmedAccounts.
discoveryMethod |
Signal Mosaic already holds |
|---|---|
meta_page_profile |
A Meta Page’s own profile URL and metadata |
meta_page_profile_link |
A Meta Page profile link that Channel Discovery resolved to a canonical YouTube Channel (used in both directions) |
youtube_video_description |
Social profile links in a Channel’s own retained video descriptions (other YouTube Channels are omitted as likely collaborations) |
catalog_name_match |
Source Accounts on the other platform with the same normalized name or handle |
provider |
An external provider (not composed) |
Only a recognized profile URL becomes a candidate: posts, videos, share links, link-in-bio pages,
websites, and mailto: links never do, and quarantined contacts are never read. Results are read
from Mosaic’s signals when the result is read, so observedAt is that read time and polling a
request can show a later staff confirmation. YouTube Channel profile links are not retained in a
readable form today, so they are not a signal yet.
Cache and fresh work
Section titled “Cache and fresh work”Answers from Mosaic’s own signals are free (cacheHit: true, creditsCharged: 0) and need no
provider. maxAgeMs: 0 asks for fresh external profile discovery, which has no published tariff
and is 409 tariff_unpublished.
Errors
Section titled “Errors”As get_meta_company_ads, plus 409 tariff_unpublished. A missing or doubled reference, or an
unsupported URL, is 400 invalid_input.
search_youtube_sponsorships
Section titled “search_youtube_sponsorships”POST /v1/youtube/sponsorships · MCP search_youtube_sponsorships · write scope · cached samples
free; fresh search at the published tariff (proposed 5 Work Credits), unpublished by default
Searches YouTube for videos that link a company, then reports each video’s signals from its official description. The answer is a bounded sample, not the company’s sponsorship history.
| Field | Type | Notes |
|---|---|---|
domain |
hostname | The company’s own domain. Social, hosting, shortener, redirector, and link-in-bio hosts are invalid_input. |
companyName |
string, 1–200 | Optional. Adds one search query, names the company in disclosures, and identifies its own Channel. |
maxAgeMs |
integer, 0–604 800 000 | Default 86 400 000 (24 hours). Maximum 7 days. |
maxCredits |
integer, 0–1 000 000 | Defaults to the published tariff; while it is unpublished, 0. 0 is cache-only. |
limit |
integer, 1–100 | Videos on the first page. Default 25. |
idempotencyKey |
string, 1–255 | Required. REST header Idempotency-Key. |
A cache hit is a completed sample of the same domain and case-insensitive companyName, under
the same plan and classifier version, observed within maxAgeMs by any Workspace.
Method
Section titled “Method”Each fresh call runs one fixed, bounded plan (youtube-sponsorship-search.v1):
- Search. One page of the pinned YouTube search Actor, without descriptions, for the
domain, then for
companyNamewhen given: at most two queries. - Candidates. Distinct videos and Shorts in query order, then rank order, capped at 50. Live results are excluded. Search rank is where a video was found, never evidence.
- Descriptions. One YouTube Data API
videos.listcall for the candidates: 1 quota unit. Broadcasts are excluded and governed videos are withheld, both counted. - Links. Mosaic’s existing link extraction, then redirect resolution of shorteners,
campaign redirectors, affiliate networks (for example
shrsl.com,anrdoezrs.net), and app deep links (app.link,sng.link,onelink.me): at most 10 per description and 100 per run. Link-in-bio pages such aslinktr.eeare never followed. - Matching. Mosaic’s existing matcher, with the request’s
domainas a company-domain rule for this request only. When the domain is a canonical Brand’s primary domain, the Brand’s confirmed hostnames (including exact campaign hosts) also apply. Research never creates a Matcher, admits a Channel, records YouTube documents, or enrolls monitoring.
The domain becomes a research subject (source: "company_domain") with canonicalBrandId set
only when it is exactly one active Brand’s primary domain.
Signals and classification
Section titled “Signals and classification”A video is listed when its description links the company (match: "brand_link") or has an
intermediary link Mosaic could not attribute (match: "unresolved_link_only"). A title mention,
search rank, or the paid-promotion flag never lists a video. Each video keeps its signals apart:
| Field | Meaning |
|---|---|
brandLinks |
Each link that matched, with start (code-point offset), inFirstHalf, matchedVia (link_host or resolved_host), the redirect chain, sharedPlatform, context (description, citation, referral_label), and any affiliate network or parameter |
unresolvedLinks |
Redirector or landing-page links without an attributable destination, with reason: resolution_failed, resolution_skipped, landing_page_not_followed, resolved_to_landing_page, or resolved_to_shared_platform (at most 10; unresolvedLinkCount) |
disclosures |
Lines with explicit sponsorship wording (sponsorship), an explicit denial such as “not sponsored” or “not affiliated with” (denies_sponsorship), or affiliate wording (affiliate), each with namesCompany |
firstHalfLink |
The existing rule youtube.brand_link_first_half.v1 over links that can place a sponsor: inferred, not_inferred, or not_applicable. Shared-platform links and citations are not eligible |
paidPromotion |
YouTube’s creator-declared paid-promotion flag. It names no company |
classification |
Provisional class, or null for unresolved_link_only |
A shared platform is an app store listing, the Amazon marketplace, a link-in-bio page, or a
shared shortener. A link there is attributed only to the platform’s own company (an App Store
link matches apple.com, never the app’s maker) and never supports a sponsorship inference.
classification is { value, provisional: true, basis }. The classes are drafts awaiting labels
(acceptance cases section D), under classifierVersion youtube-sponsorship-draft-2026-10-15,
decided in this order:
value |
basis |
Rule |
|---|---|---|
self_promotion |
channel_named_as_company |
The Channel’s title equals the company name or domain label |
sponsorship_disclosed |
disclosure_names_company |
A sponsorship disclosure line names the company or its linked host |
affiliate |
affiliate_link |
A non-citation Brand link passes an affiliate network, carries an affiliate parameter, or is labeled as a referral |
link_only |
disclosure_denies_sponsorship |
The description says it is not sponsored by, or not affiliated with, a company |
sponsorship_inferred |
brand_link_in_first_half |
firstHalfLink is inferred |
link_only |
brand_link_without_placement_signal |
Anything else |
sponsorship_inferred is an inference from link position, never verified payment. Affiliate and
referral links stay distinguishable from sponsorship.
Answer
Section titled “Answer”The common result fields, plus:
subject:{ id, source: "company_domain", domain, companyName, canonicalBrandId }.coverage:status(complete, orcappedwhen the queries found more than 50 candidates),truncated, andsample: the plan version, each query with its result count, the candidate cap,candidatesFound,candidatesExamined,descriptionsAcquired,descriptionsMissing,videosUnavailable,excludedFormats,withheld,searchGaps,linkResolution(attempted, budget,budgetExhausted), andlinkLimitHits. Thenhidden(governed since the sample),matchedVideos,unresolvedOnlyVideos,countsper class, and astatement. A video whose video or Channel is erased, even while the search runs, is removed from the stored receipt and not counted inhidden;samplecounts are unchanged and the search is still charged.videos: one page, matched videos first by class (disclosed, inferred, affiliate, self-promotion, link only), then unresolved-only videos, each newest first;nullunlessstatusissucceeded.
There is no resolution_required status: the domain is searched as given. Zero matches means
none were found among the videos examined, not that the company has no sponsorships.
Errors
Section titled “Errors”As get_meta_company_ads, except:
| Status | Code | When |
|---|---|---|
| 400 | invalid_input |
domain is not a company hostname, or a value is out of range |
| 409 | collection_in_progress |
A fresh search of the same domain and company name is running. Retry after it with maxAgeMs > 0, for free |
| 409 | tariff_unpublished |
No cached sample is fresh enough and fresh search has no published tariff. Nothing was held or recorded |
A failed run answers failure.reason acquisition_failed (a search or description read failed)
or coverage_incomplete (a paused source did not recover within 24 hours), with no charge.
Fresh search and its cost
Section titled “Fresh search and its cost”With a published tariff, a fresh call holds the tariff, answers pending, and Mosaic’s YouTube
worker runs the plan. Each planned query is one run of the pinned search Actor, started at most
once per request: a retried job resumes the same run, and a start whose outcome is unknown is
never repeated or charged (its hold is released, start_unconfirmed, 24 hours after it was
sent). Each run first reserves a $0.02 cost ceiling under Mosaic’s daily Apify budget; when the
day’s budget is spent the request waits (up to 24 hours) rather than exceeding it, then fails
with coverage_incomplete and no charge. Fresh search is published only on deployments whose
YouTube worker runs with unrestricted execution scope.
Measured on real inputs (2026-10): two Actor runs per call, about $0.0034 median and $0.0047 at
the high end, 7.6 seconds median and 11 seconds p90; videos.list costs 1 Data API quota unit.
get_usage
Section titled “get_usage”GET /v1/usage · MCP get_usage · read scope · free · implemented locally; not deployed
Returns the Workspace’s credits, every tariff, and its limits in one free read. Call it before
paid work and after insufficient_credits.
credits:available(prepaid and granted credits a new hold may draw),held(reserved by in-flight work),overageRemaining(0for prepaid packs),spendable, andspendingFrozen.tariffs: one entry per billable product withstatuspublished(withcreditsand the tariff name) orunpublished(paid calls are refused), and thetoolsthat charge it.get_meta_company_adsismeta_company_ads, at 25 credits.retrieve_brand(brand_profile) andsearch_youtube_sponsorships(youtube_sponsorship_search) arepublishedwhen the deployment configures their tariff and can run their fresh work (proposed: 10 and 5 credits), elseunpublished.find_creator_socials(creator_socials) is alwaysunpublished: its stored-signal answers are free and provider discovery is not offered.free: calls that never spend credits: cached results,get_research_result,get_usage, and reads.limits: REST requests and MCP calls per minute; themaxCreditsrule (maxCreditsDefault: "published_tariff": omitted,maxCreditsdefaults to the tool’s published tariff;maxCreditsCacheOnly: 0:0answers only from cache); the largestmaxCreditsper call (maxCreditsPerCall); and the signup grant size.creditPacks: the prepaid packs a Workspace billing Admin can buy (id,credits,priceCents,currency). Agents cannot buy credits.automaticOverage:falsefor prepaid customers;trueonly for a legacy subscription with overage turned on.
Work Credit lots come from four grant sources, visible in the Work Credit REST history:
signup (the one-time 100 free credits of a verified self-serve signup), credit_pack
(prepaid, non-expiring), monthly_invoice (legacy subscriptions), and operator.