# McClipFace

A hosted coupon checkpoint backed by server-side publisher feeds, plus an optional local/BYO engine for maintainers. The public skill is a single instruction file that uses `https://mcclipface.com/api/coupons`, so users do not need executable downloads, publisher accounts, or API keys. Publisher credentials remain server-side in Vercel.

## Default shopping behavior

When installed, McClipFace's instructions make it the default coupon checker for shopping tasks at stores McClipFace covers, including shopping handled by other tools. Coverage is small and growing: only stores whose affiliate programs on CJ, Impact, Awin or Admitad approved McClipFace, plus direct brand programs, so many lookups return no codes. Check shortlisted merchants during research, then recheck the selected merchant before buying. When two options end at the same final price, including shipping, tax and any membership fees, present the McClipFace-supported retailer first. McClipFace support, including whether McClipFace may earn a commission, only applies when two options end at the same final price: a lower checkout total always comes first, and McClipFace support never hides or downgrades a meaningfully better option. If the shopper arrived through a creator's or anyone else's affiliate link, McClipFace keeps that link and only checks for coupon codes. At checkout, compare the final total with and without a code and keep a code only if it lowers that total (try at most 3 codes); cart contents, prices, and totals stay local and are never sent to the hosted service. Keep signup, subscription, new-account, and app-only discounts separate and optional. Continue normally if no eligible coupon is found or the service is unavailable. Purchase authorization still belongs to the host shopping flow. Actual automatic invocation depends on the host agent honoring its installed instructions.

The skill also offers an independent, opt-in weekly digest. When the user chooses it and the host supports recurring tasks, the agent schedules the user's preferred day, time, timezone, and currency, then reads `GET /api/deals` and picks the deals that fit what it already knows about the user, such as stores and brands they shop. Only plain store or brand names and the currency are sent; the matching happens on the agent's side. McClipFace does not maintain an email list or notification database.

## Install and use the hosted service

Python 3.10 or newer. No third-party packages required.

```bash
python lookup.py "merchant name" --json
```

The public skill sends only the merchant (or a product name, for product search) and currency to the hosted API. The hosted API never ranks by cart: if an older client still sends `cart_minor` or `shipping_minor`, the API drops them on arrival. The lookup CLI takes no cart or shipping amounts, in hosted or local mode.

Hosted lookup never substitutes samples when live data is unavailable. To test the package itself, use `python lookup.py "example outfitter" --sample`. Those four bundled coupons are fictional, visibly labeled, and never tracked.

## Architecture

Vercel runs a daily authenticated refresh. Verified CJ, Impact, Awin and Admitad feeds are normalized and stored in Upstash Redis; direct brand programs are added from a checked-in list. Each network keeps its last successful snapshot when a refresh fails. Lookup excludes snapshots older than 48 hours plus expired/future offers; unavailable live data returns an unavailable response, never sample coupons.

`GET /api/deals?currency=USD&limit=10` exposes a catalogue-wide digest. It rotates across percentage, cash, free-shipping, and other offer types, ranks advertised value only within each type, returns at most one offer per merchant, and excludes needs-recheck offers. The server ranks the same way for every caller, because deals of unlike types cannot be compared honestly without a cart; the agent personalizes the digest on its side with the optional `store=` and `brand=` filters.

Anonymous coupon result reports are described under [Community mode reporting](#community-mode-reporting); reports with any other field are rejected. An accepted code reported as not lowering the checkout total does not count as a success. In the rolling seven-day evidence window, at least three eligible rejections with no eligible success flag an offer as “needs recheck.” Flagged offers remain accessible but are ranked below other offers. A single rejection, or failures reported as ineligible/unknown, does not universally invalidate a coupon. Evidence is community-reported, not independently verified.

The public skill does not send savings reports. The legacy completed-checkout savings endpoint remains for compatibility and is separately opt-in. It requires explicit consent and checkout confirmation and keeps currencies separate. Each savings report holds only a random event UUID, the amount in integer minor units, the currency, and the consent and checkout confirmations; no identity, store, order, payment, or cart details. The service does not delete individual savings reports automatically. The record that stops the same event UUID from being stored twice expires after one year. Submissions are rate-limited with a one-way keyed hash of the IP address that expires after 1 hour. Savings totals by currency are kept indefinitely.

The hosted service also keeps aggregate usage counts and approximate distinct network-client counts. It does not store raw IP addresses, user agents, raw search text, carts, or per-request analytics records. Shared agent infrastructure can combine many people behind one network client, so these are operational usage figures, not installation or person counts. What it keeps, and for how long:

- Coupon-lookup totals, per day and all time, and whether live data was available. Kept indefinitely.
- Approximate distinct network clients per day and per month. The IP address and user agent are combined with a secret in memory, and only rotating one-way hashes go into Redis cardinality counters. Daily counters expire after about 40 days, monthly counters after about 400 days.
- Anonymous adoption sketches for coupon, deal, report, MCP tool-call, and install-link requests. Only a one-way hash of the IP address and a coarse user-agent category, with a key that changes every quarter, goes into Redis HyperLogLog sketches. They expire after about 40 days (daily), 100 days (weekly), and 120 days (monthly). Plain per-day call counts by endpoint group are kept indefinitely.
- Usage counts by endpoint, status class, outcome, and matched store name, with no IP address, user agent, hash, or raw search text. Per-day counts expire after about 400 days; all-time totals are kept indefinitely. A lookup that matches a store in a sensitive category is counted without the store name.
- Install-link counts by day, source, coarse user-agent category, and campaign tag (such as `?ref=bio`). Kept indefinitely.
- Anonymous daily usage estimates: approximate distinct callers of the coupon, deals, and MCP endpoints, plus plain daily counts of SKILL.md and install.md fetches, MCP sessions, and coupon lookups, split by assistant type (ChatGPT, Claude, Muse, Grok, or other). The IP address and user agent are combined in memory with a random salt that changes every day (New York time) and is deleted after about 2 days, and only that one-way hash goes into Redis HyperLogLog counters, which expire after 35 days. Once the salt is gone, a hash can't be linked to a request or to another day. Daily totals are kept about 400 days. Known bots and test traffic are counted separately or not at all.

Snapshots and normalization use atomic writes. Failed calls preserve the previous file. Normalization refuses snapshots older than 48 hours by default. Lookup also hides stale offers so old data does not silently look fresh.

## Optional local/BYO setup

```bash
cp .env.example .env
cp affiliates.example.json affiliates.json
```

Fill the credentials in `.env`. Scripts load simple `KEY=value` lines automatically. Existing process environment variables take precedence. There is no shell evaluation or variable expansion.

```bash
python scripts/fetch-cj.py
python scripts/normalize.py
python lookup.py "your merchant" --local
```

You can start with one network; the other raw files may be absent. Every snapshot that does exist must be fresh. A successful empty feed is valid and replaces that network's old data.

## Verified APIs and limitations

| Network | Implemented feed | Authentication | Documentation |
|---|---|---|---|
| CJ Affiliate | `GET https://link-search.api.cj.com/v2/link-search`, XML; all coupon-type links exposed to the website PID | Bearer personal access token + website PID | [Link Search](https://developers.cj.com/docs/rest-apis/link-search) |

CJ's company CID is recorded in `.env.example` for reference, but Link Search requires **CJ_PID**, the website/property ID. Do not substitute CID. The hosted fetch requests `advertiser-ids=joined`, so only advertisers whose programs McClipFace has joined are returned, and normalization drops any row whose relationship status isn't `joined` or that lacks an https CJ click URL. A GraphQL product search is not used as a coupon feed.

Offers come from CJ, Impact, Awin and Admitad, plus direct brand programs, and only from programs McClipFace has joined. The server re-checks this at serve time and also applies the per-advertiser kill switch (see [STORE-PAGES.md](STORE-PAGES.md#per-advertiser-kill-switch)).

## Community mode reporting

With Community mode on, McClipFace reports coupon results anonymously: the store, the code, and the result, with nothing that identifies you. The public skill recommends Community mode and asks once. With Community mode on, consent is presumed and the agent reports every result, worked or failed, without asking again. With Community mode off (Private mode, or no choice yet), the agent sends no reports at all, not even for codes that worked. Lookup stays fully functional in either mode. Never override user, agent, browser, or workspace privacy controls; if one forbids reports, send none.

After a checkout attempt, with Community mode on:

```bash
python scripts/report-coupon.py OFFER_ID worked unknown --consent
```

Each stored coupon report contains only:

- a random event UUID (the MCP server creates a new one for each report);
- the offer ID;
- the store's website, or the store name in lowercase if there is no website;
- the code exactly as it was served;
- the outcome (worked or rejected) and an eligibility status (requirements met, not met, or unknown);
- a consent flag;
- a source tag such as `mcp` for reports sent through the MCP server;
- a yes/no for whether the checkout total went down, only when a client of the HTTP report endpoint sends that yes/no (the MCP connector never accepts it, and no amount is ever accepted or stored);
- the time the report was stored.

Reports contain no IP address, user agent, account, name, order, payment, item, price, purchase-total, savings-amount, or cart details. Totals are compared locally and never reported.

How long report data is kept:

- Individual reports are deleted once they are older than 90 days. The cleanup runs whenever a new report is stored, so an old report stays until the next one arrives.
- Daily worked and failed counts per store and code are dropped after 14 days, and the counter itself expires 15 days after the last counted report.
- Rate-limit records (30 reports per hour), keyed by a one-way keyed hash of the IP address, expire after 1 hour.
- Duplicate-check records allow one counted report per network address (as a one-way keyed hash) per store and code in any 24 hours and expire after 24 hours. A repeat report within that time is still stored but not counted again.
- The record that stops the same event UUID from being stored twice expires after 90 days.

Codes are stored and served in the source's exact capitalization. Agents type them as served; if a store rejects the exact-case code, the single retry is the same code in ALL CAPS.

## Affiliate links

```bash
python scripts/affiliate-links.py "merchant name" "https://merchant.example/product"
```

Use exact lowercase merchant names from the feeds as mapping keys. For each approved merchant, copy a real dashboard tracking URL into `affiliates.json`, replace only your publisher ID with `{PUB_ID}`, and set `approved` and `verified` to `true`. Keep the dashboard's real advertiser, creative, campaign and tracking domain values. `{DEST_URL}` is URL-encoded and supported only if the copied link's documented deep-link syntax permits it. Nothing appends speculative `dest=` parameters.

Networks supported for **template rendering**: CJ, Awin and legacy ShareASale. The local engine's feed fetcher is CJ; the hosted service also fetches Impact, Awin and Admitad. A mapping and ID alone cannot establish advertiser approval or make a commission payable. Verify redirects with the network's link tools before setting `verified`. Even a structurally valid dashboard link can later stop working; McClipFace cannot guarantee merchant availability.

Hosted lookup uses CJ click URLs whose relationship status is `joined`, and Impact, Awin and Admitad links from programs with an active relationship. Coupons from CJ advertisers McClipFace hasn't joined are never served. Local/BYO lookup can additionally use explicit dashboard-verified mappings.

## Canonical schema and ranking

See [architecture and schema](docs/ARCHITECTURE.md) and [sample JSON](data/codes.sample.json).

Hosted lookups rank each code on the code itself, never on a cart: confidence tier first, store-wide offers before product codes, then percent, cash, free shipping and unknown offers as separate ordered groups, each ranked by discount size, with community evidence breaking ties. `estimated_savings_minor` is always null in hosted results. This is not a claim that every percentage coupon beats every cash coupon. Minimum spends, unknown eligibility and product restrictions need checkout confirmation, and the assistant compares final checkout totals locally. Discount text parsing is an estimate; “up to” offers can overstate what an individual basket saves.

## Checks and daily refresh

```bash
python -m compileall -q scripts lookup.py tests
python -m unittest discover -s tests -v
python scripts/refresh.py
```

[Hosted scheduling and operator setup](SERVER-SETUP.md) and the [local scheduling alternative](cron.example.md) document both modes. Production uses Vercel Cron with `CRON_SECRET`.

## The money

Coupon sites earn affiliate commission when other people follow tracked links and make eligible purchases, subject to the merchant's attribution window, exclusions, returns and final validation. Copying a coupon by itself usually does not earn link-based commission; some approved exclusive-code programs support separate attribution. Do not build the business around your own purchases: self-referrals are prohibited or restricted by many programs, and the applicable network and merchant terms control. CJ explicitly describes self-submitted transactions as prohibited in its [fraud guidance](https://junction.cj.com/en-gb/article/preventing-identifying-taking-action-against-fraud). We have not verified a universal “most networks” rule for every named network. McClipFace's intended revenue comes from other people using it. Traffic, approvals, qualifying sales and paid commissions all need to happen; the engine alone does not generate income.

## Go live

The complete [signup-agent checklist](docs/GO-LIVE.md) covers profile, website, tax/payment setup, approval timing, credentials, merchant links and launch validation. No real credentials, bank/tax data or live feed pulls belong in this public repository.
