Use Claude to manage your ViewFi campaigns, pull analytics, and bulk-upload rewards through natural language.
ViewFi exposes a Model Context Protocol (MCP) server. Once connected, you can ask Claude things like:
Operations are authenticated via an API key, whose scope is either a whole organisation or a single brand seat.
There are two kinds of key. Choose before generating — a key's scope is fixed for its lifetime.
| Organisation key (recommended) | Brand seat key | |
|---|---|---|
| Reaches | Every brand seat in the organisation, including ones added later | Exactly one brand seat |
| Addressing | Tools take a brandSeatId argument |
No argument — the seat is implied |
| Generated from | Account page (/profile) |
Brand seat → Integrations |
| Use when | You run several brands or clients yourself | You are giving an outside agency access to one client |
Organisation key:
/profile)vfsk_...) — it is shown only onceYou must be an owner or admin of the organisation to generate one.
Brand seat key: Dashboard → Brand Seats → [seat] → Integrations → "Or create a key limited to this brand seat only".
Existing brand seat keys keep working exactly as before — there is nothing to migrate.
With an organisation key there is no default brand seat. Tools that act on one require
brandSeatIdand will return an error naming the omission rather than guessing. Ask Claude to runlist_brand_seatsfirst.
Edit your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS) and add:
{
"mcpServers": {
"viewfi": {
"url": "https://video-rewards.com/mcp",
"headers": {
"Authorization": "Bearer vfsk_YOUR_KEY_ID.YOUR_SECRET"
}
}
}
}
Then fully quit Claude Desktop — Cmd+Q on macOS, not just closing the window — and reopen it. The tool list is cached and will not refresh otherwise. The dashboard's "Generate API Key" success screen also gives you a copy-paste-ready snippet.
If the connector does not appear, your Claude Desktop build may predate native remote-server support, which older versions lacked. Bridge it with
mcp-remote:{ "mcpServers": { "viewfi": { "command": "npx", "args": [ "-y", "mcp-remote", "https://video-rewards.com/mcp", "--header", "Authorization: Bearer vfsk_YOUR_KEY_ID.YOUR_SECRET" ] } } }
One command — no config file to edit:
claude mcp add --transport http viewfi https://video-rewards.com/mcp \
--header "Authorization: Bearer vfsk_YOUR_KEY_ID.YOUR_SECRET"
Check it connected with claude mcp list. Add --scope project to share the
connector with a repo via .mcp.json, or --scope user for every project on
your machine.
Neither ChatGPT nor Claude.ai web can send an Authorization header: both
support OAuth or no-auth connectors only, so the Bearer setup above will not
work in either. Both use a connect URL instead, which carries the credential
in the path and needs no authentication configured.
Create one from the dashboard: account page → your organisation's key → Create next to Connect URL. It is shown once. Rotate replaces it and kills the previous URL immediately; Revoke disables it while leaving the API key working.
curl -X POST \
https://app.viewfi.io/api/organizations/<organizationId>/integrations/api-keys/<keyId>/connect-url \
-H "Cookie: <your dashboard session>"
For a brand-seat key, substitute
/api/brand-seats/<brandSeatId>/integrations/api-keys/<keyId>/connect-url.
Returns, once:
{ "connectUrl": "https://video-rewards.com/mcp/c/vfct_<keyId>.<secret>" }
DELETE the same endpoint to revoke.
In ChatGPT: add the URL as a custom MCP connector with no authentication. Developer Mode requires a Business, Enterprise or Edu plan.
In Claude.ai: Settings → Connectors → Add custom connector, paste the URL, leave authentication unset, save. Connections are brokered through Anthropic's servers rather than your machine, so the endpoint must be publicly reachable — it is.
Treat the URL as a password. Anyone holding it has that key's permissions. It is a separate secret from the API key, so a leak can be rotated without rotating the key itself.
| Tool | What it does |
|---|---|
list_campaigns |
Lists all campaigns for your brand seat. Filter by status or type. |
get_campaign |
Returns full details of one campaign. |
create_campaign |
Creates a new campaign in draft state. Supports all campaign types. |
update_campaign |
Partial update. Status transitions enforced (draft → test → live). |
duplicate_campaign |
Clones a campaign as a new draft. |
list_templates |
Valid template ids and palettes. templateId is an integer FK, so call this first. |
get_campaign_share_links |
Public widget URL, embed code, and branded URL. Reports whether the campaign is actually reachable. |
upload_campaign_image |
Uploads and assigns a splash, reward or logo image (Read + Write). |
generate_campaign_image |
Costs money per call. Generates artwork with OpenAI and assigns it (Read + Write). See below. |
generate_campaign_imageThis one spends money and produces unreviewed creative, so it is deliberately constrained:
OPENAI_API_KEY set on the server, with image generation available
to that account. Billing is on the ViewFi account, not yours.upload_campaign_image whenever the brand has supplied an asset.If it fails with "model does not exist", the configured image model is not
available to the server's OpenAI account. Override it with the
OPENAI_IMAGE_MODEL environment variable.
| Tool | What it does |
|---|---|
bulk_upload_coupon_codes |
Upload up to 5,000 unique coupon codes. |
bulk_upload_reward_links |
Upload up to 5,000 reward URLs. |
bulk_upload_vouchers |
Upload up to 5,000 voucher codes with optional value. |
get_reward_inventory_stats |
Total / claimed / available counts for a campaign. |
| Tool | What it does |
|---|---|
get_campaign_analytics |
Daily breakdown: views, completions, claims, avg watch time. |
get_campaign_widget_analytics |
Organic funnel (page views → claims). |
get_campaign_ad_analytics |
Paid funnel grouped by ad tag. |
get_brand_seat_analytics |
Aggregate across all your campaigns. |
get_brand_seat_ad_analytics |
Aggregate paid analytics with date range. |
All analytics are aggregate only. The connector exposes no per-viewer data — no contact details, no individual claim rows. Viewer-level data stays in the dashboard behind a session login.
| Tool | What it does |
|---|---|
get_connection_info |
What this key is connected to: organisation, scope, permissions. |
get_brand_seat |
Details of a brand seat. |
list_brand_seats |
Brand seats in your organization, flagged with whether this key can use each. |
create_brand_seat |
Provision a new brand seat (Read + Write). |
set_brand_colors |
Brand palette inherited by that seat's campaigns (Read + Write). |
get_brand_seat_wallet |
EVM wallet address + USDC balance for airdrop campaigns. |
With an organisation key, get_brand_seat, set_brand_colors,
get_brand_seat_wallet, list_campaigns, create_campaign, list_ad_tags
and the two brand seat analytics tools all require brandSeatId. Tools
addressed by campaignId do not — the campaign already identifies its seat.
| Tool | What it does |
|---|---|
list_ad_tags |
All ad tags for your brand seat. |
get_ad_tag_analytics |
Impressions, clicks, completions, CPM. |
create_ad_tag |
Create a new ad tag (Read + Write). |
The authoritative, always-current spec lives at
docs/campaign-requirements.md and is served to
connected models as viewfi://docs/campaign-requirements. It is kept in sync
with the create_campaign input schema.
Two things worth knowing before your first build:
templateId is an integer, not a slug. Call list_templates for valid
ids — passing "minimal" cannot insert into an integer column.draft. Use update_campaign to move it to
test, then live, and get_campaign_share_links to get a shareable URL.The connector also exposes read-only resources Claude can pull contextually:
viewfi://docs/campaign-requirements — the campaign build spec (required
fields, per-type requirements, valid enums, lifecycle). Connected models are
instructed to read this before creating a campaign, so they don't have to be
told the rules by hand. Source: docs/campaign-requirements.mdviewfi://templates — every active template with its palette, plus the brand
seat's brandColors. Connected models are instructed to read this when
choosing a template, so campaign artwork matches the brand rather than
defaulting blindly. With an organisation key it returns a palette per brand
seat (brandColorsByBrandSeat).viewfi://brand-seats/current — what this key is scoped to: the organisation,
plus the brand seat if the key is limited to oneviewfi://campaigns — list of all your campaigns (summary)viewfi://campaigns/{id} — full campaign detailsviewfi://campaigns/{id}/analytics — daily analytics rowslist_brand_seats returns every brand seat in the organization for both key types, so even a seat-scoped key can see sibling seat names and ids (but not their campaigns). If you issue keys to separate external clients, note that they will be able to see each other's seat names.create_brand_seat is organisation-wide for every Read + Write key, including brand seat keys. A seat-scoped key cannot read or modify another seat, but it can create new seats in its organisation. If you issue a Read + Write seat key to an external party, they can add seats to your organisation. Issue a read-only seat key if that matters — read keys cannot call it./api, so /mcp/c/... is never written to application logs. That is not a guarantee about upstream infrastructure: Railway, Cloudflare and any corporate proxy between you and us may log full URLs. Treat a connect URL as a credential that has been through more hands than a header would, prefer the Bearer header where the client supports it, and rotate on any suspicion.lastUsedAt for each key so you can spot inactive or unauthorized keys."Missing or malformed API key" — The header isn't being sent. Verify your config exactly matches Authorization: Bearer vfsk_<keyId>.<secret>.
"Invalid or revoked API key" — The key was revoked, or you copied only the secret without the vfsk_<keyId>. prefix. Generate a new key.
"This API key has read-only permissions" — You're calling a write tool with a read-only key. Generate a Read + Write key for mutating operations.
"brandSeatId is required" — You are using an organisation key, which has no
default brand seat. Ask Claude to run list_brand_seats and pass the id of the
one you mean.
"This API key is scoped to brand seat N" — You are using a brand seat key and tried to act on a different seat. Use an organisation key from the account page to work across several seats.
403 Forbidden from a script or custom client — that is Cloudflare, not ViewFi: an auth failure from us is a 401 with a JSON body, never a 403. Cloudflare fronts these domains and scores clients on IP reputation and TLS fingerprint as well as headers, so this can hit one machine and not another.
We could not reproduce it on demand — python-requests, aiohttp and curl
user agents all reached the server normally from our test host — but it has
been reported in the field. If you see it, send a complete browser-like header
set, which resolves it in practice:
headers = {
"Authorization": f"Bearer {key}",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"User-Agent": "curl/8.4.0",
}
If that does not clear it, send us the cf-ray header from the 403 response
and we can have the request unblocked.
Tool not found, or brandSeatId not being asked for — MCP clients cache
tool lists, and the ViewFi server does not push list-changed notifications. Fully
quit the client (Cmd+Q on macOS — closing the window is not enough) and
reopen it. This matters after switching between a brand seat key and an
organisation key, because the two advertise different tool shapes.
Questions? Email [email protected].