ViewFi Docs ← Documentation Home

ViewFi campaign build requirements

Authoritative spec for building a ViewFi campaign through the MCP connector. Served to connected models as viewfi://docs/campaign-requirements.

Keep this file in sync with the create_campaign input schema in server/mcp/tools/campaigns.ts — that schema is what actually validates, this document is what the model reads before calling it.


Scope of this connector

This connector builds and configures campaigns. It does not expose viewer data: there are no tools returning contact details, individual claim rows, or any per-person record. Analytics are aggregate only. Do not attempt to retrieve viewer identities through it.

It also cannot delete anything. Deletions happen in the dashboard.


Which brand seat am I acting on?

Settle this before creating or changing anything. An API key has one of two scopes, and get_connection_info tells you which you have.

Organisation-scoped key — reaches every brand seat in the organisation. There is no default seat. These tools require an explicit brandSeatId:

get_brand_seat, set_brand_colors, get_brand_seat_wallet, list_campaigns, create_campaign, list_ad_tags, get_brand_seat_analytics, get_brand_seat_ad_analytics

Call list_brand_seats and confirm with the user which seat they mean. Do not guess, and do not assume the first seat in the list. Omitting brandSeatId returns an error saying so — it never falls back to a default.

Brand seat-scoped key — fixed to one seat. brandSeatId is optional and supplying a different seat's id is rejected, not ignored.

Tools addressed by campaignId never need brandSeatId: the campaign already determines its seat, and access is checked against it.

Campaigns are created in the seat you name, and create_campaign echoes back brandSeatId and brandSeatName — check them against what the user asked for.


Lifecycle

Every campaign is created as draft. create_campaign always sets this and ignores any status passed in.

draft  →  test  →  live

Move between states with update_campaign. Do not attempt to create a campaign directly in live.

Test simulates wallet connections and blockchain transfers — no real value moves. Live distributes real rewards.


Required on every campaign

Field Type Notes
title string 1–255 chars
campaignType enum see table below
videoSource youtube | cloudflare_stream | none defaults to youtube
videoId string YouTube video ID or Cloudflare Stream UID. Required unless videoSource: 'none'
templateId integer FK into the templates table. Call list_templates for valid ids — it is not a slug like minimal.
campaignExpirationDate ISO date string
claimDuration integer seconds the viewer must watch; defaults to 4

Optional on any type: description, logoUrl, splashImageUrl, redeemRewardUrl. All URL fields must be absolute URLs.

A note on splashImageUrl

This is the image shown before the video plays. If left empty on a YouTube campaign, the platform auto-fills it with the YouTube thumbnail. Set it explicitly when the brand has its own cover art — otherwise the campaign ships with a YouTube still.

It is not the end-screen image; those are separate fields set in the dashboard.


Type-specific requirements

Pass the short name in the left column. The connector maps three of these to longer values the database stores; you never need to write the stored value yourself, and list_campaigns accepts either.

campaignType Stored as Also required
airdrop airdrop arcBlockchain, arcNetwork, arcAmountPerClaim, arcBudgetAmount
coupon coupon_campaign couponType — then see below
voucher voucher_campaign voucherType — then see below
reward_link reward_link rewardLinkType, plus a URL or a bulk upload
contest contest_campaign contestPrizeTitle, contestDrawDate, contestWinnerCount
ugc_coupon ugc_coupon robloxTotalRewards, robloxClaimUrlTemplate
mint_nft mint_nft rewardConfig (NFT metadata)

Enumerated values

Coupons

Reward links

Vouchers

voucherValue is a display string, not a number — "$10 OFF", "20% OFF". Optional extras: voucherDescription (redemption instructions), voucherProvider, voucherExpirationDate (ISO), voucherTermsConditions.

Contests

contestManagerEmails is an optional array of addresses notified when winners are drawn. contestDrawDate is an ISO date and should be after campaignExpirationDate in practice — draw after entries close.

Roblox (ugc_coupon)

COPPA compliance is enabled automatically for this type. robloxClaimUrlTemplate is the deeplink template used to hand the code back to the game.


Ordering

For any bulk type, the sequence matters:

  1. create_campaign → returns the campaign id
  2. bulk_upload_* with that id
  3. update_campaign to move draft → test
  4. Verify in test
  5. update_campaign to move test → live

Moving a bulk campaign to live before step 2 produces a live campaign with zero inventory, which fails at claim time rather than at configuration time.


Branding and colours

Read viewfi://templates. It returns every active template and its palette, alongside the brand seat's brandColors. With an organisation-scoped key it returns brandColorsByBrandSeat instead — a palette per seat, so use the entry matching the seat you are building for, not the first one.

Templates are authoritative. A template's palette is an internally consistent set — primary, accent, text, border, glass and shadow are derived together. Brand colours are not layered over it: overriding three keys while the other five stay derived from the template produces a mixed palette, and makes template changes look broken because the brand colours win every time.

So use brandColors to choose the template, not to override it:

  1. If brandColors is set, pick the template whose palette is closest to it.
  2. If brandColors is null, pick the one closest to the brand logo — a red logo should not get a blue template.

Either way, say which template you chose and why, so the operator can correct a wrong guess.

set_brand_colors records the palette on the seat so later campaigns inherit it instead of re-deriving it each time. It merges, so setting one colour does not clear the others.


Terms and conditions

termsAndConditions is the campaign's own terms, shown in a modal when the viewer ticks the consent box.

Resolution order in the widget:

  1. termsAndConditions
  2. voucherTermsConditions — voucher campaigns only
  3. the generic platform terms at /terms

Only the first two are labelled " Campaign Terms". If neither is set the link reads Platform Terms, because showing a brand's name over ViewFi's generic terms misrepresents whose terms the viewer is agreeing to.

Set termsAndConditions whenever a brand has supplied its own. Line breaks are preserved and the modal scrolls, so paste the full text rather than summarising.


Publishing

get_campaign_share_links returns the public widget URL, the embed iframe, and the branded subdomain URL when one is configured.

A campaign only becomes reachable once it leaves draft, and the tool says so rather than handing back a URL that 404s. publicId is assigned by the platform — a campaign without one has no shareable link yet.


Auth providers

Set with liveModeAuthProviders / testModeAuthProviders on create or update. Live campaigns support google and email only; everything else is test-mode only. Defaults to ["google"] when omitted.

Auth providers are additive on live campaigns: they can be added after going live, but not removed, because a viewer who authenticated with a provider that later disappears cannot finish their claim.


Images

upload_campaign_image takes an asset the user already has — a URL or base64 — and assigns it to a slot. Prefer it always.

Which slot

target Field When the viewer sees it
splash splashImageUrl Before the video plays
end_screen endScreenImageUrl After the video finishes, before they claim
reward rewardAssetUrl After they claim, on the success card
logo logoUrl Throughout

splash and end_screen are 16:9 and displayed full-bleed. reward is square.

Never put the reward image on the end screen. It spoils the post-claim reveal, and brand artwork for a claimed reward often carries copy that has not been approved for a viewer who has not claimed yet.

End screen artwork resolution

If endScreenImageUrl is unset, the player falls back in this order, so most campaigns need nothing set at all:

  1. endScreenImageUrl — explicit override
  2. endImageVerticalUrl / endImageHorizontalUrl / endImageUrl — existing dedicated end images
  3. the campaign's social card image (16:9)
  4. splashImageUrl
  5. the YouTube or Cloudflare Stream thumbnail

Set endScreenImageUrl only when the brand wants a different frame at the end from the one at the start.

End screen wording

endScreenClaimText overrides the end-screen button. Left unset, it defaults per campaign type — "UNLOCK VOUCHER", "UNLOCK CODE", "ENTER NOW" and so on. Do not set it to anything containing "reward": several brands reject that wording, and the per-type defaults already avoid it.

generate_campaign_image creates new artwork with OpenAI. It costs money per call and produces unreviewed creative. Do not call it as part of building a campaign, and do not offer to replace artwork the user has already supplied. Call it only when the user explicitly asks you to generate an image. It is rejected on live campaigns.

If it errors with "model does not exist", image generation is misconfigured on the server — say so rather than retrying, and carry on without the image.


Permissions

Mutating tools require a read_write API key. With a read key, the following are rejected: create_brand_seat, create_campaign, update_campaign, duplicate_campaign, bulk_upload_coupon_codes, bulk_upload_reward_links, bulk_upload_vouchers, create_ad_tag.

Scope is separate from permission — see "Which brand seat am I acting on?" above. A brand seat key reaches one seat; an organisation key reaches every seat in its organisation and can create more. Neither can reach another organisation's seats, regardless of the caller's org membership.

Documentation

Getting Started Campaign Management Claude / MCP Connector Advertising & DSP CTV Integration Roblox Integration Fortnite Integration API Reference Developer Resources Troubleshooting